În a doua parte a acestei serii numite Laravel, BDD și You, vom începe să descriem și să construim prima noastră caracteristică folosind Behat și PhpSpec. În ultimul articol am pus totul în scenă și am văzut cât de ușor putem interacționa cu Laravel în scenariile noastre Behat.
Recent, creatorul lui Behat, Konstantin Kudryashov (a.k.a. everzet), a scris un articol foarte bun, numit Introducere de modelare prin exemplu. Fluxul de lucru pe care îl vom folosi, atunci când ne construim caracteristica, este foarte inspirat de cel prezentat de everzet.
Pe scurt, vom folosi același lucru .caracteristică
pentru a proiecta atât domeniul nostru de bază și interfața noastră de utilizator. Am simțit de multe ori că am avut o mulțime de duplicare în caracteristicile mele în acceptările mele / suite funcționale și de integrare. Când am citit sugestia lui everzet despre folosirea aceleiași caracteristici pentru mai multe contexte, totul a făcut clic pe mine și cred că este calea de plecare.
În cazul nostru, vom avea contextul nostru funcțional, care, deocamdată, va servi și ca nivel de acceptare și contextul nostru de integrare, care va acoperi domeniul nostru. Vom începe prin construirea domeniului și apoi adăugăm UI și lucrurile specifice cadrului după aceea.
Pentru a utiliza abordarea "caracteristică partajată, mai multe contexte", trebuie să facem câteva refactorizări ale configurației existente.
În primul rând, vom șterge caracteristica de întâmpinare pe care am făcut-o în prima parte, deoarece nu avem nevoie de ea și nu respectă într-adevăr stilul generic de care avem nevoie pentru a folosi mai multe contexte.
$ git rm caracteristici / funcțional / welcome.feature
În al doilea rând, vom avea caracteristicile noastre în rădăcina caracteristici
dosar, pentru a putea continua și elimina cale
atributul de la noi behat.yml
fişier. De asemenea, vom redenumi LaravelFeatureContext
la FunctionalFeatureContext
(nu uitați să schimbați și numele clasei):
implicit: suite: functional: contexte: [FunctionalFeatureContext]
În cele din urmă, doar pentru a curăța lucrurile puțin, cred că ar trebui să mutăm toate lucrurile legate de Laravel în propria lor trăsătură:
# caracteristici / bootstrap / LaravelTrait.php app) $ această-> refreshApplicație (); / ** * Creează aplicația. * * @return \ Symfony \ Component \ HttpKernel \ HttpKernelInterface * / funcția publică createApplication () $ unitTesting = true; $ testEnvironment = 'testarea'; returul necesită __DIR __. '/ ... / ... /bootstrap/start.php';
În FunctionalFeatureContext
putem folosi apoi trăsăturile și ștergem lucrurile pe care tocmai le-am mutat:
/ ** * Clasa de context Behat. * / class FunctionalFeatureContext implementează SnippetAcceptingContext utilizează LaravelTrait; / ** * Inițializează contextul. * * Fiecare scenariu devine propriul obiect context. * Puteți, de asemenea, să transmiteți argumente arbitrare constructorului de context prin behat.yml. * / funcția publică __construct ()
Trăsăturile reprezintă o modalitate excelentă de a vă curăța contextele.
Așa cum am prezentat în prima parte, vom construi o aplicație mică pentru urmărirea timpului. Prima caracteristică va fi aceea de a urmări timpul și de a genera o foaie de timp din intrările urmărite. Aici este caracteristica:
Caracteristică: Timpul de urmărire Pentru a urmări timpul petrecut pe sarcini Ca angajat trebuie să gestionez o foaie de timp cu înregistrări de timp Scenariu: Generarea foii de timp Având în vedere că am următoarele intrări de timp | sarcină | durata | | codificare | 90 | | codificare | 30 | | documentare | 150 | Când voi genera foaia de timp Apoi timpul meu total petrecut la codificare ar trebui să fie de 120 de minute Și timpul meu total petrecut pentru documentare ar trebui să fie 150 minute Și timpul meu total petrecut la întâlniri ar trebui să fie 0 minute Și timpul meu total petrecut ar trebui să fie 270 minute
Amintiți-vă că acesta este doar un exemplu. Mi se pare mai ușor să definiți trăsături în viața reală, deoarece aveți o problemă reală pe care trebuie să o rezolvați și de multe ori aveți șansa de a discuta această trăsătură cu colegii, clienții sau alte părți interesate.
Bine, să lăsăm Behat să genereze scenariul pentru noi:
$ vendor / bin / behat - dry-run - fișiere de tip "append"
Trebuie să optimizăm pașii generați doar un pic mic. Avem nevoie doar de patru pași pentru a acoperi scenariul. Rezultatul final ar trebui să pară așa:
/ ** * @ Având următoarele intrări de timp * / funcția publică iHaveTheFollowingTimeEntries (TableNode $ table) arunca noua PendingException (); / ** * @Ce generam foaia de timp * / funcția publică iGenerateTheTimeSheet () arunca noua PendingException (); / ** * @Apoi, timpul meu total petrecut pe: sarcina ar trebui să fie: de așteptat minute de duratură * / funcția publică myTotalTimeSpentOnTaskShouldBeMinutes ($ task, $ expectedDuration) arunca noua PendingException (); / ** * @Apoi, timpul meu total petrecut ar trebui să fie: de așteptat minute de durată * / funcția publică myTotalTimeSpentShouldBeMinutes ($ expectedDuration) arunca nou PendingException ();
Contextul nostru funcțional este gata să meargă acum, dar avem nevoie și de un context pentru suita noastră de integrare. În primul rând, vom adăuga suita la behat.yml
fişier:
implicit: suite: functional: contexte: [FunctionalFeatureContext] integrare: contexte: [IntegrationFeatureContext]
Apoi, putem copia doar implicit FeatureContext
:
Caracteristici $ cp / bootstrap / FeatureContext.php / bootstrap / IntegrationFeatureContext.php
Nu uitați să schimbați numele clasei la IntegrationFeatureContext
și, de asemenea, să copiați instrucțiunea de utilizare pentru PendingException
.
În cele din urmă, deoarece noi împărțim această caracteristică, putem copia doar definițiile din cele patru etape din contextul funcțional. Dacă executați Behat, veți vedea că această caracteristică este executată de două ori: o dată pentru fiecare context.
În acest moment, suntem gata să începem să completați pașii în așteptare în contextul nostru de integrare pentru a proiecta domeniul de bază al aplicației noastre. Primul pas este Având în vedere că am următoarele intrări de timp
, urmat de un tabel cu înregistrări de intrare în timp. Păstrați-o simplu, permiteți-ne să rupem doar rândurile mesei, încercați să instanțiați o intrare de timp pentru fiecare dintre ele și adăugați-le la o matrice de intrări în context:
folosiți TimeTracker \ TimeEntry; ... / ** * @Given am următoarele intrări de timp * / funcția publică iHaveTheFollowingTimeEntries (TableNode $ table) $ this-> entries = []; $ rânduri = $ table-> getHash (); foreach ($ rânduri ca $ row) $ entry = new TimeEntry; $ entry-> task = $ row ['task']; $ entry-> duration = $ rând ['durata']; $ this-> entries [] = $ intrare;
Rularea Behat va provoca o eroare fatală, deoarece TimeTracker \ TimeEntry
clasa nu există încă. Aici intră PhpSpec în scenă. La sfarsit, TimeEntry
va fi o clasă elocventă, chiar dacă nu ne îngrijorează încă. PhpSpec și ORM-urile precum Eloquent nu joacă împreună bine, dar putem folosi în continuare PhpSpec pentru a genera clasa și chiar pentru a specifica unele comportamente de bază. Să folosim generatoarele PhpSpec pentru a genera TimeEntry
clasă:
$ vendor / bin / phpspec desc "TimeTracker \ TimeEntry" $ vendor / bin / phpspec run Doriți să vă creez 'TimeTracker \ TimeEntry' pentru tine? y
După generarea clasei, trebuie să actualizăm secțiunea de autoload din secțiunea noastră composer.json
fişier:
"autoload": "classmap": "app / commands", "app / controllers", "app / models", "app / database / migrations" : "TimeTracker \\": "src / TimeTracker",
Și bineînțeles că fugi compozitor dump-autoload
.
Rularea PhpSpec ne dă verde. Rularea Behat ne dă și verde. Ce start bun!
Permiteți-i lui Behat să ne ghideze drumul, cum să mergem la pasul următor, Când voi genera foaia de timp
, imediat?
Cuvântul cheie aici este "generare", care arată ca un termen din domeniul nostru. Într-o lume a unui programator, traducerea "generării foii de pontaj" pentru a codifica ar putea însemna doar o instanțiere a TimeSheet
clasa cu o grămadă de intrări de timp. Este important să încercați să respectați limbajul din domeniu atunci când proiectăm codul nostru. În acest fel, codul nostru va ajuta la descrierea comportamentului dorit al aplicației noastre.
Identificam termenul Genera
la fel de important pentru domeniu, de aceea cred că ar trebui să avem o metodă de generare statică pe o TimeSheet
clasa care serveste ca un alias pentru constructor. Această metodă ar trebui să ia o colecție de intrări de timp și să le stocheze pe foaia de timp.
În loc să folosesc doar o matrice, cred că va avea sens să folosească Illuminate \ Suport \ Colectia
clasa care vine cu Laravel. De cand TimeEntry
va fi un model elocvent, atunci când vom interoga baza de date pentru intrările de timp, vom obține oricum una din aceste colecții Laravel. Ce zici de ceva de genul:
utilizați Iluminare \ Suport \ Colecție; utilizați TimeTracker \ TimeSheet; Folosiți TimeTracker \ TimeEntry; ... / ** * @ Când generăm foaia de timp * / funcția publică iGenerateTheTimeSheet () $ this-> sheet = TimeSheet :: generează (Colecția: make ($ this-> entries);
Apropo, TimeSheet nu va fi o clasă elocventă. Cel puțin pentru moment, avem nevoie doar de a face intrările de timp persistă, și apoi foile de timp va fi doar generate din înregistrări.
Rularea Behat va provoca încă o dată o eroare fatală, pentru că TimeSheet
nu exista. PhpSpec ne poate ajuta să rezolvăm următoarele:
$ vendor / bin / phpspec desc "TimeTracker \ TimeSheet" $ vendor / bin / phpspec run Doriți să vă creez "TimeTracker \ TimeSheet" pentru tine? y $ vendor / bin / phpspec executați $ vendor / bin / behat PHP Eroare fatală: Apel la metoda nedefinită TimeTracker \ TimeSheet :: generate ()
Încă mai avem o eroare fatală după crearea clasei, deoarece staticul Genera()
metoda încă nu există. Deoarece aceasta este o metodă statică foarte simplă, nu cred că este nevoie de o spec. Nu este altceva decât un ambalaj pentru constructor:
intrări = $ intrări; funcția publică statică generează (Collection $ entries) retur static nou (intrări $);
Acest lucru îl va face pe Behat să se întoarcă în verde, dar PhpSpec ne scântește, spunând: Argumentul 1 trecut la TimeTracker \ TimeSheet :: __ construct () trebuie să fie o instanță a Illuminate \ Support \ Collection, nici una dată
. Putem rezolva acest lucru scriind un simplu lăsa()
funcție care va fi apelată înainte de fiecare spec:
pune (nou TimeEntry); $ This-> beConstructedWith ($ intrări); funcția it_is_initializable () $ this-> shouldHaveType ('TimeTracker \ TimeSheet');
Acest lucru ne va aduce înapoi la verde peste tot. Funcția asigură faptul că foaia de timp este întotdeauna construită cu o falsă din clasa Collection.
Acum ne putem deplasa în siguranță Apoi timpul meu total petrecut pe ...
Etapa. Avem nevoie de o metodă care să ia un nume de sarcină și să returneze durata cumulată a tuturor intrărilor cu acest nume de activitate. Tradus direct de la corn de cod, acest lucru ar putea fi ceva de genul totalTimeSpentOn ($ sarcină)
:
/ ** * @Acum timpul meu total petrecut pe: sarcina ar trebui să fie: de așteptat minute de durată * / funcția publică myTotalTimeSpentOnTaskShouldBeMinutes ($ task, $ expectedDuration) $ actualDuration = $ this-> sheet-> totalTimeSpentOn ($ task); PHPUnit :: assertEquals ($ expectDuration, $ actualDuration);
Metoda nu există, așa că alerga Behat ne va da Apel la metoda nedefinită TimeTracker \ TimeSheet :: totalTimeSpentOn ()
.
Pentru a specifica metoda, vom scrie o spec. Care arată într-un fel similar cu ceea ce avem deja în scenariul nostru:
funcția it_should_calculate_total_time_spent_on_task () $ entry1 = new TimeEntry; $ entry1-> task = 'dormit'; $ entry1-> duration = 120; $ entry2 = nou TimeEntry; $ entry2-> task = 'mănâncă'; $ entry2-> duration = 60; $ entry3 = TimeEntry nou; $ entry3-> task = 'dormit'; $ entry3-> duration = 120; $ collection = Colecția :: a face ([$ entry1, $ entry2, $ entry3]); $ This-> beConstructedWith (colectare $); $ This-> totalTimeSpentOn ( 'dormit') -> shouldBe (240); $ This-> totalTimeSpentOn ( 'manca') -> shouldBe (60);
Rețineți că nu folosim machete pentru TimeEntry
și Colectie
instanțe. Aceasta este suita noastră de integrare și nu cred că este nevoie să fugi de asta. Obiectele sunt destul de simple și vrem să ne asigurăm că obiectele din domeniul nostru interacționează așa cum ne așteptăm. Există probabil multe opinii despre acest lucru, dar acest lucru are sens pentru mine.
Se deplasează de-a lungul:
$ vendor / bin / phpspec run Doriți să vă creez 'TimeTracker \ TimeSheet :: totalTimeSpentOn ()' pentru tine? y $ vendor / bin / phpspec run 25 ✘ ar trebui să calculeze timpul total petrecut pentru sarcina așteptată [integer: 240], dar a fost nul.
Pentru a filtra intrările, putem folosi filtru()
metoda pe Colectie
clasă. O soluție simplă care ne duce la verde:
funcția publică totalTimeSpentOn ($ task) $ entries = $ this-> entries-> filter (funcția ($ entry)) ($ task) return $ entry-> task === $ task;); $ durată = 0; foreach (intrări de $ ca intrare $) $ duration + = $ entry-> duration; retur $ durată;
Specul nostru este verde, dar simt că putem beneficia de unele refactorizări aici. Metoda pare să facă două lucruri diferite: să înregistreze intrările și să acumuleze durata. Să le extragem din urmă pe propria sa metodă:
funcția publică totalTimeSpentOn ($ task) $ entries = $ this-> entries-> filter (funcția ($ entry)) ($ task) return $ entry-> task === $ task;); returnați $ this-> sumDuration ($ entries); sumde funcția protejată ($ entries) $ duration = 0; foreach (intrări de $ ca intrare $) $ duration + = $ entry-> duration; retur $ durată;
PhpSpec este încă verde și acum avem trei pași verzi în Behat. Ultimul pas ar trebui să fie ușor de implementat, deoarece este oarecum similar cu cel pe care tocmai l-am făcut.
/ ** * @Apoi, timpul meu total petrecut ar trebui să fie: de așteptat minute de durată * / funcția publică myTotalTimeSpentShouldBeMinutes ($ expectedDuration) $ actualDuration = $ this-> sheet-> totalTimeSpent (); PHPUnit :: assertEquals ($ expectDuration, $ actualDuration);
Rularea Behat ne va da Apel la metoda nedefinită TimeTracker \ TimeSheet :: totalTimeSpent ()
. În loc să facem un exemplu separat în speculația noastră pentru această metodă, cum rămâne cu adăugarea acesteia la cea pe care o avem deja? S-ar putea să nu fie complet în concordanță cu ceea ce este "corect" de făcut, dar să fim puțin pragmatici:
... $ this-> beConstructedWith (colecție $); $ This-> totalTimeSpentOn ( 'dormit') -> shouldBe (240); $ This-> totalTimeSpentOn ( 'manca') -> shouldBe (60); $ This-> totalTimeSpent () -> shouldBe (300);
Lăsați PhpSpec să genereze metoda:
$ vendor / bin / phpspec run Doriți să vă creez 'TimeTracker \ TimeSheet :: totalTimeSpent ()' pentru tine? y $ vendor / bin / phpspec rula 25 ✘ ar trebui să calculeze timpul total petrecut pentru sarcina așteptată [integer: 300], dar a fost nul.
Noțiuni de bază la verde este ușor acum că avem sumDuration ()
metodă:
funcția publică totalTimeSpent () return $ this-> sumDuration ($ this-> entries);
Și acum avem o trăsătură verde. Domeniul nostru se dezvoltă încet!
Acum, ne mutăm la suita noastră funcțională. Vom proiecta interfața cu utilizatorul și vom trata toate chestiunile specifice lui Laravel care nu reprezintă preocuparea domeniului nostru.
În timp ce lucrăm în suita funcțională, putem adăuga -s
steag pentru a instrui Behat să ruleze doar funcțiile noastre prin FunctionalFeatureContext
:
$ furnizor / bin / behat -s funcțional
Primul pas va fi asemănător cu primul din contextul de integrare. În loc să facem intrările să persiste în contextul dintr-o matrice, trebuie să le facem să persiste într-o bază de date, astfel încât să poată fi recuperate mai târziu:
Folosiți TimeTracker \ TimeEntry; ... / ** * @Given am următoarele intrări de timp * / funcția publică iHaveTheFollowingTimeEntries (TableNode $ table) $ rows = $ table-> getHash (); foreach ($ rânduri ca $ row) $ entry = new TimeEntry; $ entry-> task = $ row ['task']; $ entry-> duration = $ rând ['durata']; $ Pornire, care rezida> Salvare ();
Rularea Behat ne va da eroare fatală Apelați la metoda nedefinită TimeTracker \ TimeEntry :: save ()
, de cand TimeEntry
încă nu este un model elocvent. Acest lucru este ușor de rezolvat:
namespace TimeTracker; clasa TimeEntry se extinde \ Eloquent
Dacă îl conducem din nou pe Behat, Laravel se va plânge că nu se poate conecta la baza de date. Putem rezolva acest lucru prin adăugarea a database.php
fișier la app / config / testare
, pentru a adăuga detaliile de conectare pentru baza noastră de date. Pentru proiectele mai mari, probabil că doriți să utilizați același server de baze de date pentru testele dvs. și baza de cod de producție, dar în cazul nostru vom folosi doar o bază de date SQLite de memorie. Acest lucru este foarte simplu de configurat cu Laravel:
'sqlite', 'connections' => array ('sqlite' => array ('driver' => 'sqlite', 'database' => ;
Acum, dacă conducem Behat, ne va spune că nu există time_entries
masa. Pentru a rezolva acest lucru, trebuie să facem o migrare:
$ php artisan migrează: face createTimeEntriesTable --create = "time_entries"
Schema :: creare ('time_entries', functie (tabelul Blueprint $) $ table-> increments ('id'); $ table-> string ('task'); tabel-> marcaje de timp (););
Încă nu suntem verzi, pentru că avem nevoie de o modalitate de ai instrui pe Behat să ne conducă migrațiile înainte de fiecare scenariu, deci avem de fiecare dată o ardezie curată. Prin utilizarea adnotărilor lui Behat, putem adăuga aceste două metode la LaravelTrait
trăsătură:
/ ** * @BeforeScenario * / setarea funcției publiceDatabază () $ this-> app ['artisan'] -> apel ("migrați"); / ** * @AfterScenario * / funcția publică cleanDatabase () $ this-> app ['artisan'] -> apel ('migra: reset');
Acest lucru este destul de curat și face primul pas la verde.
Următorul este Când voi genera foaia de timp
Etapa. Modul în care o văd, generând foaia de timp este echivalentul vizitei index
acțiunea resursei de intrare în timp, deoarece foaia de timp este colecția tuturor intrărilor de timp. Deci, obiectul foaie de timp este ca un container pentru toate intrările de timp și ne oferă o modalitate frumoasă de a manipula intrările. În loc să meargă / time-intrări
, în scopul de a vedea foaia de timp, cred că angajatul ar trebui să meargă la / Timp de coli
. Ar trebui să punem asta în definiția pasului nostru:
/ ** * @ Când gener de foaia de timp * / funcția publică iGenerateTheTimeSheet () $ this-> apel ('GET', '/ time-sheet'); $ this-> crawler = Crawler nou ($ this-> client-> getResponse () -> getContent (), url ('/'));
Acest lucru va provoca a NotFoundHttpException
, deoarece traseul nu este definit încă. Așa cum tocmai am explicat, cred că această adresă URL ar trebui să fie mapată către index
acțiune privind resursele de intrare în timp:
Route :: get ('time-sheet', ['ca' => 'time_sheet', 'uses' => 'TimeEntriesController @ index')];
Pentru a ajunge la verde, trebuie să generăm regulatorul:
$ php artisan controller: face TimeEntriesController $ compozitor dump-autoload
Și acolo mergem.
În cele din urmă, trebuie să accesăm cu crawlere pagina pentru a găsi durata totală a înregistrărilor de timp. Cred că vom avea un fel de tabel care să rezume duratele. Ultimii doi pași sunt atât de asemănători încât le vom implementa în același timp:
/ ** * @Acum timpul meu total petrecut pe: sarcina ar trebui să fie: de așteptat minute de durată * / funcția publică myTotalTimeSpentOnTaskShouldBeMinutes ($ task, $ expectedDuration) $ actualDuration = $ this-> crawler-> filter ('td #' . "TotalDuration") -> text (); PHPUnit :: assertEquals ($ expectDuration, $ actualDuration); / ** * @Apoi, timpul meu total petrecut ar trebui să fie: de așteptat minute de durată * / funcția publică myTotalTimeSpentShouldBeMinutes ($ expectedDuration) $ actualDuration = $ this-> crawler-> filter ('td # totalDuration') -> text; PHPUnit :: assertEquals ($ expectDuration, $ actualDuration);
Crawlerul caută un a Deoarece încă nu avem o viziune, crawlerul ne va spune asta Pentru a rezolva aceasta, să construim Vederea, deocamdată, va consta într-un simplu tabel cu valorile duratei rezumate: Dacă executați din nou Behat, veți vedea că am implementat cu succes caracteristica. Poate ar trebui să luăm o clipă pentru a ne da seama că nici măcar o dată nu am deschis un browser! Aceasta este o îmbunătățire masivă a fluxului nostru de lucru și, ca un bonus frumos, avem acum teste automatizate pentru aplicația noastră. ura! Dacă alergi Dacă executați aceste două comenzi: Veți vedea că ne întoarcem la verde, atât cu Behat cât și cu PhpSpec. ura! Am descris și proiectat prima noastră facilitate, folosind o abordare BDD. Am văzut cum putem beneficia de proiectarea domeniului principal al aplicației noastre, înainte de a ne îngrijora de interfața cu utilizatorul și de chestiunile specifice cadrului. De asemenea, am văzut cât de ușor este să interacționăm cu Laravel, în special cu baza de date, în contextele noastre Behat. În următorul articol, vom face o mulțime de refactorizări pentru a evita prea multă logică asupra modelelor noastre elocvente, deoarece acestea sunt mai greu de testat în izolare și sunt strâns legate de Laravel. Rămâneți aproape! nod cu un id de [Task_name] TotalDuration
sau durata totala
în ultimul exemplu.Lista nodurilor curente este goală.
index
acțiune. Mai întâi, preluăm colecția de intrări de timp. În al doilea rând, generăm o foaie de timp din intrări și trimitem-o spre vizualizarea (încă inexistentă).utilizați TimeTracker \ TimeSheet; utilizați TimeTracker \ TimeEntry; class TimeEntriesController extinde \ BaseController / ** * Afișează o listă a resursei. * * @response Response * / indexul funcției publice () $ entries = TimeEntry :: all (); $ foaie = TimeSheet :: genera ($ intrări); retur View :: make ('time_entries.index', compact ('sheet')); ...
Foaia de timp
Sarcină Durata totala de codificare $ sheet-> totalTimeSpentOn ("codare") documentarea $ foaie-> totalTimeSpentOn ("documentare") reuniuni $ sheet-> totalTimeSpentOn ("întâlniri") Total $ foaie-> totalTimeSpent () Concluzie
furnizor / bin / behat
pentru a rula ambele suite Behat, veți vedea că ambele sunt acum verzi. Dacă rulați PhpSpec deși, din păcate, veți vedea că specificațiile noastre sunt rupte. Avem o eroare fatală Clasa "Elocvent" nu a fost găsită în ...
. Acest lucru se datorează faptului că elocventul este un alias. Dacă te uiți înăuntru app / config / app.php
sub aliasuri, veți vedea asta Elocvent
este de fapt un alias pentru Illuminate \ Baza de date Elocvent \ model \
. Pentru ca PhpSpec să revină la verde, trebuie să importem această clasă:namespace TimeTracker; utilizați Iluminare \ Bază de Date \ Elocvent \ Model ca elocvent; clasa TimeEntry se extinde Elocvent
$ vendor / bin / phpspec execută; furnizor / bin / behat