Laravel, BDD en jij laten we beginnen

Welkom bij deze serie over het ontwikkelen van Laravel-applicaties met behulp van een gedraggestuurde ontwikkelingsbenadering (BDD). Full-stack BDD kan ingewikkeld en intimiderend lijken. Er zijn net zoveel manieren om het te doen als er ontwikkelaars zijn. 

In deze serie zal ik je laten zien hoe ik Behat en PhpSpec gebruik om een ​​Laravel-applicatie helemaal opnieuw te ontwerpen.

Er zijn veel bronnen op BDD in het algemeen, maar Laravel-specifiek materiaal is moeilijk te vinden. Daarom zullen we ons in deze serie meer richten op de Laravel-gerelateerde aspecten en minder op de algemene dingen die je op veel andere plaatsen kunt lezen..

Gedrag beschrijven

Bij het beschrijven van gedrag, ook bekend als het schrijven van verhalen en specificaties, gebruiken we een outside-in benadering. Dit betekent dat we elke keer dat we een nieuwe functie bouwen, beginnen met het schrijven van het algemene gebruikersverhaal. Dit gebeurt normaal vanuit het perspectief van de klant of de stakeholders. 

Wat verwachten we dat er gebeurt als we dit doen? 

Het is ons niet toegestaan ​​om een ​​code te schrijven totdat we een mislukte, rode stap hebben, tenzij we de bestaande code herformuleren. 

Soms is het nodig om een ​​klein deel van een verhaal of een kenmerk (twee woorden die ik uitwisselbaar gebruik) iteratief op te lossen waarop we werken. Dit betekent vaak schrijfspecificaties met PhpSpec. Soms duurt het vele iteraties op een integratie- of eenheidsniveau voordat het hele verhaal (op acceptatieniveau) voorbij is. Dit klinkt allemaal heel ingewikkeld, maar dat is het echt niet. Ik ben een groot voorstander van leren door te doen, dus ik denk dat alles logischer zal zijn zodra we beginnen met het schrijven van een echte code.

We schrijven verhalen en specificaties op vier verschillende niveaus:

1. Acceptatie

Meestal zal onze functionele suite onze acceptatielaag zijn. De manier waarop we onze functies in onze functionele suite zullen beschrijven, zal sterk lijken op hoe we acceptatie-verhalen zouden schrijven (met behulp van een geautomatiseerd browser-framework) en zou op die manier veel duplicatie creëren. 

Zolang de verhalen het gedrag vanuit het oogpunt van de cliënt beschrijven, dienen ze als acceptatieverslagen. We zullen de Symfony DomCrawler gebruiken om de uitvoer van onze applicatie te testen. Later in de serie zullen we misschien merken dat we een daadwerkelijke browser moeten testen die ook JavaScript kan uitvoeren. Testen via de browser voegt een aantal nieuwe zorgen toe, omdat we ervoor moeten zorgen dat we de uurentestomgeving laden wanneer de suite wordt uitgevoerd.

2. Functioneel

In onze functionele suite hebben we toegang tot de Laravel-applicatie, wat erg handig is. Allereerst maakt dit het eenvoudig om onderscheid te maken tussen omgevingen. Ten tweede, het niet doorlopen van een browser maakt onze testsuite een stuk sneller. Wanneer we een nieuwe functie willen implementeren, zullen we een verhaal schrijven in onze functionele suite met behulp van Behat.

3. Integratie

Onze integratiesuite test het gedrag van het kerngedeelte van onze applicatie die niet noodzakelijk toegang tot Laravel hoeft te hebben. De integratie suite zal normaal een mix zijn van Behat-verhalen en PhpSpec specificaties.

4. Eenheid

Onze unit tests zullen worden geschreven in PhpSpec en zullen geïsoleerde kleine eenheden van de applicatiekern testen. Onze entiteiten, waardeobjecten etc. zullen allemaal specificaties hebben.

De zaak

In deze hele reeks, te beginnen met het volgende artikel, zullen we een systeem voor het volgen van de tijd bouwen. We beginnen met het gedrag van buitenaf te beschrijven door de functies van Behat te schrijven. Het interne gedrag van onze applicatie zal worden beschreven met PhpSpec. 

Samen helpen deze twee hulpmiddelen ons om ons op ons gemak te voelen met de kwaliteit van de applicatie die we aan het bouwen zijn. We schrijven voornamelijk functies en specificaties op drie niveaus: 

  1. functioneel
  2. integratie
  3. Eenheid


In onze functionele suite crawlen we de HTTP-antwoorden van onze applicatie in een headless-modus, wat betekent dat we niet door de browser gaan. Dit maakt het gemakkelijker om met Laravel te communiceren en onze functionele suite ook als acceptatie-laag te dienen. 

Later, als we een ingewikkeldere gebruikersinterface hebben en misschien ook wat JavaScript moeten testen, kunnen we een speciale acceptatiesuite toevoegen. Deze serie is nog steeds bezig-in-uitvoering, dus voel je vrij om je suggesties in de comments sectie te plaatsen.

Onze installatie

Merk op dat ik voor deze tutorial aanneem dat je een nieuwe installatie van Laravel (4.2) hebt geïnstalleerd. Bij voorkeur gebruik je ook Laravel Homestead, dat is wat ik gebruikte toen ik deze code schreef.

Voordat we aan de slag gaan met echt werk, laten we ervoor zorgen dat we Behat en PhpSpec in gebruik hebben. Eerst wil ik echter een beetje opschonen wanneer ik een nieuw laravel-project start en de dingen die ik niet nodig heb, verwijder:

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

Als u deze bestanden verwijdert, moet u uw. Bijwerken composer.json bestand dienovereenkomstig:

"autoload": "classmap": ["app / opdrachten", "app / controllers", "app / modellen", "app / database / migraties", "app / database / seeds"], 

En uiteraard:

$ composer dump-autoload

Nu zijn we klaar om de BDD-tools in te nemen die we nodig hebben. Voeg gewoon een toe vereisen-dev sectie naar uw composer.json:

"require": "laravel / framework": "4.2. *", "require-dev": "behat / behat": "~ 3.0", "phpspec / phpspec": "~ 2.0", "phpunit / phpunit ":" ~ 4.1 ", 

"Waarom trekken we PHPUnit in?" denk je misschien? We gaan geen goede oude PHPUnit-testcases in deze serie schrijven, maar de beweringen zijn een handige tool samen met Behat. We zullen dat later in dit artikel zien wanneer we onze eerste functie schrijven.

Vergeet niet om uw afhankelijkheden na het wijzigen bij te werken composer.json:

$ composer update --dev

We zijn bijna klaar met het installeren en instellen van dingen. PhpSpec werkt uit de doos:

$ vendor / bin / phpspec run 0 specs 0 voorbeelden 0ms

Maar Behat moet een snelle run doen met de --in het optie om alles in te stellen:

$ vendor / bin / behat --init + d functies - plaats hier uw * .feature-bestanden + d functies / bootstrap - plaats hier uw contextklassen + f functies / bootstrap / FeatureContext.php - plaats hier uw definities, transformaties en hooks $ vendor / bin / behat Geen scenario's Geen stappen 0m0.14s (12.18Mb)

Het eerste commando creëerde een glimmende nieuwe FeatureContext klasse, waar we de stapdefinities kunnen schrijven die nodig zijn voor onze functies:

Onze eerste functie schrijven

Onze eerste functie zal heel eenvoudig zijn: we zullen er eenvoudig voor zorgen dat onze nieuwe Laravel-installatie ons begroet met een "U bent gearriveerd". op de startpagina. Ik deed een beetje dom Gegeven stap ook, Ik ben ingelogd, wat alleen maar laat zien hoe gemakkelijk interactie met Laravel in onze functies is.

Technisch gezien zou ik dit type feature categoriseren als een functionele test, omdat het interageert met het framework, maar het dient ook als een acceptatietest, omdat we geen andere resultaten zouden zien dan het uitvoeren van een vergelijkbare test via een browsertesttool. Voor nu houden we vast aan onze functionele testsuite.

Ga je gang en maak een welcome.feature bestand en zet het in features / functionele:

# features / functioneel / welcome.feature Feature: Welcoming developer Als ontwikkelaar van Laravel Om een ​​nieuw project proberly te kunnen beginnen, moet ik bij aankomst worden begroet Scenario: Groetontwikkelaar op de startpagina Gezien ik ben ingelogd Wanneer ik "/" bezoek zou moeten zien "U bent gearriveerd." 

Door de functionele functies in een functioneel directory, is het voor ons gemakkelijker om onze suites later te beheren. We willen geen integratietypefuncties waarvoor Laravel niet hoeft te wachten op de langzame functionele suite. 

Ik hou ervan dingen netjes en schoon te houden, dus ik denk dat we een speciale functiecontext moeten hebben voor onze functionele suite die ons toegang geeft tot Laravel. Je kunt gewoon doorgaan en de bestaande kopiëren FeatureContext bestand en verander de klassenaam in LaravelFeatureContext. Om dit te laten werken hebben we ook een behat.yml configuratiebestand. 

Maak er een in de hoofdmap van uw project en voeg het volgende toe:

standaard: suites: functioneel: paden: [% paths.base% / kenmerken / functioneel] contexten: [LaravelFeatureContext] 

Ik denk dat de YAML hier behoorlijk voor zichzelf spreekt. Onze functionele suite zoekt naar functies in de functioneel map en voer ze door de LaravelFeatureContext.

Als we op dit moment Behat proberen uit te voeren, zal het ons vertellen om de noodzakelijke stapdefinities te implementeren. We kunnen Behat de lege steigermethoden aan de LaravelFeatureContext met het volgende commando:

$ vendor / bin / behat --dry-run --append-snippets $ vendor / bin / behat Feature: Welcoming developer Als ontwikkelaar van Laravel Om een ​​nieuw project proberly te kunnen beginnen moet ik begroet worden bij arival Scenario: Groetontwikkelaar op homepage # features / function / welcome.feature: 6 Gegeven ik ben ingelogd # LaravelFeatureContext :: iAmLoggedIn () TODO: schrijf in afwachting van definitie Wanneer ik "/" # LaravelFeatureContext :: iVisit () bezoek, zou ik moeten zien "U bent gearriveerd. " # LaravelFeatureContext :: iShouldSee () 1 scenario (1 in behandeling) 3 stappen (1 in behandeling, 2 overgeslagen) 0m0.28s (12.53Mb)

En nu, zoals u kunt zien aan de uitgang, zijn we klaar om het eerste van onze stappen te implementeren: Ik ben ingelogd.

Met de PHPUnit-testcase die met Laravel wordt meegeleverd, kunnen we dingen doen als $ This-> zijn ($ user), die een bepaalde gebruiker inlogt. Uiteindelijk willen we met Laravel kunnen communiceren alsof we PHPUnit gebruiken, dus laten we doorgaan en de stapdefinitiecode "we willen dat we hadden" schrijven:

/ ** * @Given Ik ben ingelogd * / public function iAmLoggedIn () $ user = new User; $ This-> zijn ($ user);  

Dit zal natuurlijk niet werken, aangezien Behat geen idee heeft van specifieke Laravel-zaken, maar ik zal je in een oogopslag laten zien hoe eenvoudig het is om Behat en Laravel samen leuk te laten spelen.

Als je een kijkje neemt in de bron van Laravel en de Verlichten \ Foundation \ Testing \ testcase klasse, wat de klasse is waarvan de standaard testcase zich uitstrekt, u zult zien dat vanaf Laravel 4.2 alles naar een eigenschap is verplaatst. De ApplicationTrait is nu verantwoordelijk voor het opstarten van een Toepassing bijvoorbeeld, het opzetten van een HTTP-client en geef ons een paar hulpmethoden, zoals worden()

Dit is best gaaf, vooral omdat het betekent dat we het gewoon in onze Behat-contexten kunnen binnenhalen met bijna geen setup vereist. We hebben ook toegang tot de AssertionsTrait, maar dit is nog steeds gekoppeld aan PHPUnit.

Wanneer we het kenmerk trekken, moeten we twee dingen doen. We moeten een hebben opstelling() methode, zoals die in deVerlichten \ Foundation \ Testing \ testcase klasse, en we hebben een createApplication () methode, zoals die in de standaard Laravel-testcase. Eigenlijk kunnen we die twee methoden gewoon kopiëren en direct gebruiken. 

Er valt maar één ding op te merken: in PHPUnit, de methode opstelling() wordt voor elke test automatisch gebeld. Om hetzelfde te bereiken in Behat, kunnen we de @BeforeScenario aantekening.

Voeg het volgende toe aan uw LaravelFeatureContext:

gebruik Illuminate \ Foundation \ Testing \ ApplicationTrait; / ** * Behat contextklasse. * / class LaravelFeatureContext implementeert SnippetAcceptingContext / ** * Verantwoordelijk voor het leveren van een Laravel-appinstantie. * / gebruik ApplicationTrait; / ** * Initialiseert context. * * Elk scenario krijgt een eigen contextobject. * Je kunt willekeurige argumenten ook aan behat.yml doorgeven aan de contextconstructor. * / public function __construct ()  / ** * @BeforeScenario * / public function setUp () if (! $ this-> app) $ this-> refreshApplication ();  / ** * Creëert de applicatie. * * @return \ Symfony \ Component \ HttpKernel \ HttpKernelInterface * / public function createApplication () $ unitTesting = true; $ testEnvironment = 'testen'; return vereist __DIR __. '/ ... / ... /bootstrap/start.php';  

Vrij eenvoudig, en kijk wat we krijgen als we Behat gebruiken:

$ vendor / bin / behat Feature: Welcoming developer Als Laravel-ontwikkelaar Om een ​​nieuw project proberly te beginnen moet ik begroet worden bij arival Scenario: Groetontwikkelaar op homepage # features / functioneel / welcome.feature: 6 Gegeven ik ben ingelogd # LaravelFeatureContext :: iAmLoggedIn () Wanneer ik "/" # LaravelFeatureContext :: iVisit () TODO bezoek: schrijf een in behandeling zijnde definitie. Dan zou ik moeten zien "U bent gearriveerd." # LaravelFeatureContext :: iShouldSee () 1 scenario (1 in behandeling) 3 stappen (1 geslaagd, 1 in behandeling, 1 overgeslagen) 0m0.73s (17.92Mb)

Een groene eerste stap, wat betekent dat onze installatie werkt!

Vervolgens kunnen we het Wanneer ik bezoek stap. Deze is supergemakkelijk en we kunnen gewoon de () aanroepenmethode die het ApplicationTrait biedt. Eén regel code zal ons daar brengen:

/ ** * @Wanneer ik bezoek: uri * / openbare functie iVisit ($ uri) $ this-> call ('GET', $ uri);  

De laatste stap, Dan zou ik het moeten zien, duurt iets meer en we moeten twee afhankelijkheden inhalen. We hebben PHPUnit nodig voor de bewering en we zullen de Symfony DomCrawler nodig hebben om te zoeken naar "U bent gearriveerd". tekst.

We kunnen het als volgt implementeren:

gebruik PHPUnit_Framework_Assert als PHPUnit; gebruik Symfony \ Component \ DomCrawler \ Crawler; ... / ** * @Then I should see: text * / public function iShouldSee ($ text) $ crawler = new Crawler ($ this-> client-> getResponse () -> getContent ()); PHPUnit :: assertCount (1, $ crawler-> filterXpath ("// text () [. = '$ Text']"));  

Dit is vrijwel dezelfde code als je zou schrijven als je PHPUnit zou gebruiken. De filterXpath () deel is een beetje verwarrend en we zullen ons er nu geen zorgen over maken, omdat het een beetje buiten de reikwijdte van dit artikel valt. Vertrouw me gewoon dat het werkt.

Een laatste keer Beynchatten is goed nieuws:

$ vendor / bin / behat Feature: Welcoming developer Als Laravel-ontwikkelaar Om een ​​nieuw project proberly te beginnen moet ik begroet worden bij arival Scenario: Groetontwikkelaar op homepage # features / functioneel / welcome.feature: 6 Gegeven ik ben ingelogd # LaravelFeatureContext :: iAmLoggedIn () Wanneer ik "/" # LaravelFeatureContext :: iVisit () bezoek, zou ik moeten zien "U bent gearriveerd." # LaravelFeatureContext :: iShouldSee () 1 scenario (1 geslaagd) 3 stappen (3 geslaagd) 0m0.82s (19.46Mb)

De functie werkt zoals verwacht en de ontwikkelaar wordt bij aankomst begroet.

Conclusie

Het complete LaravelFeatureContext zou er nu ongeveer zo uit moeten zien:

app) $ this-> refreshApplication ();  / ** * Creëert de applicatie. * * @return \ Symfony \ Component \ HttpKernel \ HttpKernelInterface * / public function createApplication () $ unitTesting = true; $ testEnvironment = 'testen'; return vereist __DIR __. '/ ... / ... /bootstrap/start.php';  / ** * @Given Ik ben ingelogd * / public function iAmLoggedIn () $ user = new User; $ This-> zijn ($ user);  / ** * @Wanneer ik bezoek: uri * / public function iVisit ($ uri) $ this-> call ('GET', $ uri);  / ** * @Then zou ik moeten zien: text * / public function iShouldSee ($ text) $ crawler = new Crawler ($ this-> client-> getResponse () -> getContent ()); PHPUnit :: assertCount (1, $ crawler-> filterXpath ("// text () [. = '$ Text']"));  

We hebben nu een hele leuke basis om op voort te bouwen terwijl we onze nieuwe Laravel-applicatie blijven ontwikkelen met behulp van BDD. Ik hoop dat ik je heb bewezen hoe gemakkelijk het is om Laravel en Behat samen te laten spelen. 

We hebben in dit eerste artikel veel verschillende onderwerpen behandeld. Maak je geen zorgen, we zullen alles dieper kijken naarmate de reeks verdergaat. Als u vragen of suggesties heeft, laat dan een reactie achter.