Laravel, BDD och du Låt oss komma igång

Välkommen till denna serie om att utveckla Laravel-applikationer med hjälp av en beteendestyrd utveckling (BDD). Full stack BDD kan verka komplicerat och skrämmande. Det finns lika många sätt att göra det eftersom det finns utvecklare. 

I denna serie kommer jag att gå igenom min inställning att använda Behat och PhpSpec för att designa en Laravel-applikation från början.

Det finns många resurser på BDD i allmänhet, men Laravel-specifikt material är svårt att hitta. Därför kommer vi i denna serie att fokusera mer på Laravel-relaterade aspekter och mindre på de allmänna saker som du kan läsa om många andra platser.

Beskrivande beteende

När man beskriver beteende, vilket också kallas för att skriva berättelser och specifikationer, kommer vi att använda en utanför-in-strategi. Det innebär att varje gång vi bygger en ny funktion börjar vi skriva den övergripande användarhistoriken. Detta är normalt från kunder eller intressentperspektiv. 

Vad förväntas vi hända när vi gör det här? 

Vi får inte skriva någon kod förrän vi har ett felaktigt, rött steg till exempel, om vi inte refactoring befintlig kod. 

Ibland är det nödvändigt att lösa en liten del av en historia eller funktion (två ord som jag använder ombytligt) som vi arbetar på iterativt. Det här innebär ofta att skriva specifikationer med PhpSpec. Ibland tar det många iterationer på en integrations- eller enhetsnivå innan hela historien (på acceptnivå) passerar. Det här låter väldigt komplicerat men det är verkligen inte. Jag är en stor tro på att jag lär mig, så jag tror att allt kommer att ge större mening när vi börjar skriva en viss kod.

Vi kommer att skriva historier och specs på fyra olika nivåer:

1. Godkännande

Merparten av tiden fungerar vår funktionella svit som vårt acceptanslag. Det sätt som vi kommer att beskriva våra funktioner i vår funktionella svit kommer att likna hur vi skulle skriva accepterningsberättelser (med hjälp av en automatiserad webbläsarram) och skulle som sådan skapa en hel del dubbelarbete. 

Så länge som berättelserna beskriver beteendet från kundens synvinkel, tjänar de som godkännandeberättelser. Vi använder Symfony DomCrawler för att testa produktionen av vår applikation. Senare i serien kan vi konstatera att vi måste testa genom en faktisk webbläsare som även kan köra JavaScript. Testa genom webbläsaren lägger till några nya problem, eftersom vi måste se till att vi laddar timmars testmiljö när sviten körs.

2. Funktionell

I vår funktionella svit får vi tillgång till Laravel-ansökan, vilket är mycket bekvämt. Först och främst gör det lätt att skilja mellan miljöer. För det andra, inte genom en webbläsare gör vår testpaket mycket snabbare. När vi vill genomföra en ny funktion skriver vi en historia i vår funktionella svit med hjälp av Behat.

3. Integration

Vår integrationspaket kommer att testa beteendet hos kärndelen av vår applikation som inte nödvändigtvis behöver få tillgång till Laravel. Integrationspaketet kommer normalt att vara en blandning av Behat-historier och PhpSpec-specifikationer.

4. Enhet

Våra enhetsprov ska skrivas i PhpSpec och testa isolerade små enheter i applikationskärnan. Våra enheter, värdesobjekt etc. har alla specifikationer.

Fallet

Under hela denna serie kommer vi från och med nästa artikel att bygga ett system för spårningstid. Vi börjar med att beskriva beteendet från utsidan genom att skriva Behat-funktioner. Det interna beteendet hos vår ansökan beskrivs med PhpSpec. 

Tillsammans hjälper dessa två verktyg oss att känna oss bekväma med kvaliteten på den applikation vi bygger. Vi kommer i första hand att skriva egenskaper och specifikationer på tre nivåer: 

  1. Funktionell
  2. Integration
  3. Enhet


I vår funktionella svit ska vi genomsöka HTTP-svaren i vår applikation i ett huvudlöst läge, vilket innebär att vi inte går igenom webbläsaren. Detta gör det lättare att interagera med Laravel och göra vår funktionella svit tjäna som vårt acceptanslag också. 

Senare, om vi hamnar på ett mer komplicerat användargränssnitt och kanske behöver testa några JavaScript, kan vi lägga till en dedikerad acceptansats. Serien är fortfarande på gång, så var god att släppa dina förslag i kommentarfältet.

Vår inställning

Observera att för denna handledning antar jag att du har en ny installation av Laravel (4.2) igång. Helst använder du också Laravel Homestead, vilket är vad jag använde när jag skrev den här koden.

Innan vi börjar med något verkligt arbete, låt oss se till att vi har Behat och PhpSpec igång. Först men jag gillar att göra lite rengöring när jag startar ett nytt laravelprojekt och tar bort de saker jag inte behöver:

git rm -r app / test / phpunit.xml CONTRIBUTING.md

Om du tar bort dessa filer, se till att uppdatera din composer.json filen i enlighet med följande:

"autoload": "klasskarta": ["app / kommandon", "app / kontroller", "app / modeller", "app / databas / migreringar", "app / databas / frön"], 

Och naturligtvis:

$ komponent dump-autoload

Nu är vi redo att dra in de BDD-verktyg vi behöver. Lägg bara till en kräva-dev avsnitt till din composer.json:

"kräver": "behat / behat": "~ 3.0", "phpspec / phpspec": "~ 2.0", "phpunit / phpunit ":" ~ 4,1 ", 

"Varför drar vi i PHPUnit?" du kanske tänker? Vi kommer inte att skriva bra ol 'PHPUnit testfall i denna serie, men påståenden är ett praktiskt verktyg tillsammans med Behat. Vi kommer se det senare i den här artikeln när vi skriver vår första funktion.

Kom ihåg att uppdatera dina beroenden efter att ha ändrats composer.json:

$ komponent uppdatering --dev

Vi är nästan färdiga att installera och konfigurera saker. PhpSpec fungerar ur lådan:

$ leverantör / bin / phpspec köra 0 specifikationer 0 exempel 0ms

Men Behat behöver göra en snabb körning med --i det alternativ för att ställa upp allt:

$ vendor / bin / behat - init + d-funktioner - placera dina * .feature-filer här + d funktioner / bootstrap - placera dina kontextklasser här + f features / bootstrap / FeatureContext.php - sätt dina definitioner, transformationer och krokar här $ leverantör / bin / behat Inga scenarier Inga steg 0m0.14s (12.18Mb)

Det första kommandot skapade en glänsande nyhet FeatureContext klass, där vi kan skriva de stegdefinitioner som behövs för våra funktioner:

Skriva vår första funktion

Vår första funktion kommer att vara väldigt enkel: Vi kommer helt enkelt se till att vår nya Laravel-installation hälsar oss med en "du har kommit." på hemsidan. Jag lägger mig ganska dumt Given steg också, Med tanke på att jag är inloggad, som bara tjänar till att visa hur lätt samspelet med Laravel i våra funktioner är.

Tekniskt skulle jag kategorisera denna typ av funktion som ett funktionellt test, eftersom det interagerar med ramverket, men det fungerar också som ett acceptansprov, eftersom vi inte skulle se några olika resultat från att köra ett liknande test genom ett testverktyg för webbläsare. För tillfället kommer vi att hålla fast vid vår funktionella testpaket.

Fortsätt och skapa en welcome.feature fil och sätt in den funktioner / funktionell:

# features / functional / welcome.feature Funktion: Välkommen utvecklare Som Laravel-utvecklare För att proberly starta ett nytt projekt måste jag hälsas vid ankomst Scenario: Hälsningsutvecklare på hemsidan Eftersom jag är inloggad När jag besöker "/" så jag bör se "Du har kommit." 

Genom att sätta funktionella funktioner i a funktionell katalog, blir det lättare för oss att hantera våra sviter senare. Vi vill inte ha integrationstypfunktioner som inte kräver att Laravel måste vänta på den långsamma funktionella sviten. 

Jag gillar att hålla sakerna snygga och rena, så jag tror att vi borde ha ett dedikerat funktionskontext för vår funktionella svit som kan ge oss tillgång till Laravel. Du kan bara fortsätta och kopiera befintliga FeatureContext fil och ändra klassnamnet till LaravelFeatureContext. För att detta ska fungera behöver vi också en behat.yml konfigurationsfil. 

Skapa en i rotkatalogen av ditt projekt och lägg till följande:

standard: sviter: funktion: vägar: [% paths.base% / features / functional] context: [LaravelFeatureContext] 

Jag tycker att YAML här är ganska självförklarande. Vår funktionella svit kommer att leta efter funktioner i funktionell katalog och köra dem genom LaravelFeatureContext.

Om vi ​​försöker springa Behat vid den här tiden kommer det att berätta för oss att genomföra nödvändiga stegdefinitioner. Vi kan ha Behat lägga till de tomma byggnadsmetoderna till LaravelFeatureContext med följande kommando:

$ vendor / bin / behat --dry-run --append-snippets $ säljare / bin / behat Funktion: Välkommen utvecklare Som en Laravel-utvecklare För att proberly börja ett nytt projekt behöver jag hälsas efter arival Scenario: Greeting developer on hemsida # features / functional / welcome.feature: 6 Givet jag är inloggad # LaravelFeatureContext :: iAmLoggedIn () TODO: skriv väntande definition När jag besöker "/" # LaravelFeatureContext :: iVisit () Då ska jag se "Du har kommit. " # LaravelFeatureContext :: iShouldSee () 1 scenario (1 väntande) 3 steg (1 väntar, 2 hoppas över) 0m0.28s (12.53Mb)

Och nu, som du kan se från produktionen, är vi redo att börja genomföra det första av våra steg: Med tanke på att jag är inloggad.

PHPUnit-testfallet som skickas med Laravel låter oss göra saker som $ This-> vara ($ användaren), som loggar in i en viss användare. I slutändan vill vi kunna interagera med Laravel som om vi använde PHPUnit, så låt oss gå vidare och skriva stegdefinitionskoden "vi önskar att vi hade":

/ ** * @Given Jag är inloggad * / allmän funktion iAmLoggedIn () $ user = new User; $ This-> vara ($ user);  

Det här kommer inte att fungera naturligtvis, eftersom Behat inte har någon aning om Laravel-specifika saker, men jag ska visa dig på bara en sekund hur lätt det är att få Behat och Laravel att spela bra tillsammans.

Om du tittar i Laravel källa och hittar Illuminate \ Foundation \ Testing \ testfall klass, som är den klass som standard testet fallet sträcker sig från, kommer du att se att starta från Laravel 4.2, allt har flyttats till ett drag. De ApplicationTrait är nu ansvarig för att starta en Ansökan exempel, konfigurera en HTTP-klient och ge oss några hjälpar metoder, till exempel vara()

Det här är ganska coolt, främst för att det innebär att vi bara kan dra in det i våra Behat-kontext med nästan ingen setup krävs. Vi har också tillgång till AssertionsTrait, men detta är fortfarande knutet till PHPUnit.

När vi drar in egenskapen måste vi göra två saker. Vi behöver ha en inrätta() metod, som den iIlluminate \ Foundation \ Testing \ testfall klass, och vi behöver en createApplication () metod, som den i standard Laravel testfallet. Egentligen kan vi bara kopiera de två metoderna och använda dem direkt. 

Det finns bara en sak att märka: I PHPUnit, metoden inrätta() kommer automatiskt att ringas före varje test. För att uppnå samma i Behat kan vi använda @BeforeScenario anteckning.

Lägg till följande i din LaravelFeatureContext:

använd Illuminate \ Foundation \ Testing \ ApplicationTrait; / ** * Behörighetsklass. * / klass LaravelFeatureContext implementerar SnippetAcceptingContext / ** * Ansvarig för att tillhandahålla en Laravel app instans. * / använd ApplicationTrait; / ** * Initialiserar kontext. * * Varje scenario får sitt eget kontextobjekt. * Du kan också skicka godtyckliga argument till kontextkonstruktören genom behat.yml. * / allmän funktion __construct ()  / ** * @BeforeScenario * / public function setUp () if (! $ this-> app) $ this-> refreshApplication ();  / ** * Skapar programmet. * * @return \ Symfony \ Component \ HttpKernel \ HttpKernelInterface * / public function createApplication () $ unitTesting = true; $ testEnvironment = 'testning'; retur kräver __DIR __. '/ ... / ... /bootstrap/start.php';  

Ganska enkelt, och se vad vi får när vi kör Behat:

$ vendor / bin / behat Funktion: Välkommen utvecklare Som Laravel-utvecklare För att proberly starta ett nytt projekt behöver jag hälsas efter arival Scenario: Hälsningsutvecklare på hemsidan # features / functional / welcome.feature: 6 Eftersom jag är inloggad # LaravelFeatureContext :: iAmLoggedIn () När jag besöker "/" # LaravelFeatureContext :: iVisit () TODO: skriv väntande definition Då ska jag se "Du har kommit." # LaravelFeatureContext :: iShouldSee () 1 scenario (1 väntande) 3 steg (1 passerat, 1 väntat, 1 hoppat över) 0m0.73s (17.92Mb)

Ett grönt första steg, vilket innebär att vår inställning fungerar!

Därefter kan vi implementera När jag besöker steg. Den här är super lätt, och vi kan helt enkelt använda ring upp()metod som ApplicationTrait ger. En rad kod kommer att få oss dit:

/ ** * @ När jag besöker: uri * / offentlig funktion iVisit ($ uri) $ this-> call ('GET', $ uri);  

Det sista steget, Då ska jag se, tar lite mer och vi behöver dra in två beroenden. Vi behöver PHPUnit för påståendet och vi behöver Symfony DomCrawler för att söka efter "Du har kommit." text.

Vi kan implementera det så här:

använd PHPUnit_Framework_Assert som PHPUnit; använd Symfony \ Component \ DomCrawler \ Crawler; ... / ** * @Det ska jag se: text * / offentlig funktion iShouldSee ($ text) $ crawler = ny Crawler ($ this-> client-> getResponse () -> getContent ()); PHPUnit :: assertCount (1, $ crawler-> filterXpath ("// text () [. = '$ Text']"));  

Det här är ungefär samma kod som du skulle skriva om du använde PHPUnit. De filterXpath () del är lite förvirrande och vi kommer inte att oroa oss för det nu, eftersom det är lite utanför ramen för denna artikel. Lita bara på mig att det fungerar.

Running Var en sista gång är goda nyheter:

$ vendor / bin / behat Funktion: Välkommen utvecklare Som Laravel-utvecklare För att proberly starta ett nytt projekt behöver jag hälsas efter arival Scenario: Hälsningsutvecklare på hemsidan # features / functional / welcome.feature: 6 Eftersom jag är inloggad # LaravelFeatureContext :: iAmLoggedIn () När jag besöker "/" # LaravelFeatureContext :: iVisit () Då ska jag se "Du har kommit." # LaravelFeatureContext :: iShouldSee () 1 scenario (1 gått) 3 steg (3 passerade) 0m0.82s (19.46Mb)

Funktionen fungerar som förväntat och utvecklaren hälsas vid ankomst.

Slutsats

Det fullständiga LaravelFeatureContext borde nu se ut som detta:

app) $ this-> refreshApplication ();  / ** * Skapar programmet. * * @return \ Symfony \ Component \ HttpKernel \ HttpKernelInterface * / public function createApplication () $ unitTesting = true; $ testEnvironment = 'testning'; retur kräver __DIR __. '/ ... / ... /bootstrap/start.php';  / ** * @Given Jag är inloggad * / allmän funktion iAmLoggedIn () $ user = new User; $ This-> vara ($ user);  / ** * @ När jag besöker: uri * / offentlig funktion iVisit ($ uri) $ this-> call ('GET', $ uri);  / ** * @Then bör jag se: text * / offentlig funktion iShouldSee ($ text) $ crawler = ny Crawler ($ this-> client-> getResponse () -> getContent ()); PHPUnit :: assertCount (1, $ crawler-> filterXpath ("// text () [. = '$ Text']"));  

Vi har nu en väldigt trevlig grund att bygga på när vi fortsätter att utveckla vår nya Laravel-applikation med BDD. Jag hoppas att jag har bevisat hur lätt det är att få Laravel och Behat att leka snyggt tillsammans. 

Vi har berört många olika ämnen i den här första artikeln. Vi behöver inte oroa oss, vi kommer att ta en djupare titt på allt som serien fortsätter. Om du har några frågor eller förslag, vänligen lämna en kommentar.