Unit tests zijn cruciaal bij de ontwikkeling van software en zorgen ervoor dat de onderdelen van je applicatie geïsoleerd werken zoals verwacht. Door tests te schrijven voor specifieke code-eenheden kun je fouten al in een vroeg stadium van de ontwikkeling opsporen en herstellen, wat leidt tot betrouwbaardere en stabielere software.

In een continue integratie/continue levering (CI/CD) pijplijn kun je deze tests automatisch uitvoeren nadat je wijzigingen in de code hebt aangebracht. Dit zorgt ervoor dat nieuwe code geen fouten introduceert of bestaande functionaliteit verbreekt.

Dit artikel belicht het belang van unit testen in Laravel applicaties en laat zien hoe je unit tests schrijft voor een Laravel applicatie die wordt ingezet met behulp van Kinsta’s Applicatie Hosting dienst.

Inleiding tot PHPUnit

PHPUnit is een veelgebruikt framework binnen het PHP ecosysteem dat is ontworpen voor eenheidstesten. Het bevat een robuuste verzameling tools voor het maken en uitvoeren van tests, waardoor het een essentiële bron is voor het waarborgen van de betrouwbaarheid en kwaliteit van je codebase.

Laravel ondersteunt testen met PHPUnit en wordt geleverd met handige helper-methoden waarmee je je applicatie kunt testen.

Het instellen van PHPUnit in een Laravel project vereist minimale configuratie. Laravel biedt een vooraf geconfigureerde testomgeving, inclusief een phpunit.xml bestand en een speciale testmap voor je testbestanden.

Je kunt ook het phpunit.xml bestand aanpassen om aangepaste opties te definiëren voor een testervaring op maat. Je kunt ook een .env.testing omgevingsbestand maken in de hoofdmap van het project in plaats van het .env bestand te gebruiken.

Standaard testlayout in Laravel

Laravel biedt een gestructureerde standaard mapindeling. De hoofdmap van je Laravel project bevat een testmap met de submappen Feature en Unit . Deze layout maakt het eenvoudig om verschillende testtypen te scheiden en een schone en georganiseerde testomgeving te onderhouden.

Het phpunit.xml bestand in een Laravel project is cruciaal voor het orkestreren van het testproces, het verzekeren van consistentie in testruns en het toestaan om het gedrag van PHPUnit aan te passen aan de eisen van het project. Hiermee kun je definiëren hoe tests moeten worden uitgevoerd, inclusief het definiëren van de testsuites, het specificeren van de testomgeving en het instellen van databaseverbindingen.

Dit bestand specificeert ook dat de sessie, cache en e-mail moeten worden ingesteld op de array driver, zodat er geen gegevens van de sessie, cache of e-mail blijven bestaan tijdens het uitvoeren van tests.

Je kunt verschillende soorten testen uitvoeren op je Laravel applicatie:

  • Unit testen – richt zich op individuele onderdelen van je code, zoals klassen, methoden en functies. Deze tests blijven geïsoleerd van de Laravel applicatie en controleren of specifieke code-eenheden werken zoals verwacht. Merk op dat tests die zijn gedefinieerd in de tests/Unit directory de Laravel applicatie niet opstarten, wat betekent dat ze geen toegang hebben tot de database of andere services die het framework biedt.
  • Feature testen – valideert de bredere functionaliteit van je applicatie. Deze tests simuleren HTTP-verzoeken en reacties, zodat je routes, controllers en de integratie van verschillende componenten kunt testen. Feature tests helpen ervoor te zorgen dat verschillende onderdelen van je applicatie samenwerken zoals verwacht.
  • Browsertests – gaan verder door browserinteracties te automatiseren. De tests maken gebruik van Laravel Dusk, een browser automatiserings- en testtool, om gebruikersinteracties te simuleren, zoals het invullen van formulieren en het klikken op knoppen. Browsertests zijn cruciaal voor het valideren van het gedrag en de gebruikerservaring van je applicatie in echte browsers.

Test-driven development concepten

Test-driven development (TDD) is een aanpak voor softwareontwikkeling waarbij de nadruk ligt op testen nog voordat code wordt geïmplementeerd. Deze aanpak volgt een proces dat bekend staat als de red-green-refactor cyclus.

De testgestuurde ontwikkelingscyclus met red-green-refactor.
De testgestuurde ontwikkelingscyclus met red-green-refactor.

Hier volgt een uitleg van deze cyclus:

  • Rode fase – Schrijf een nieuwe test om functionaliteit te definiëren of een verbetering op een bestaande test voordat je de eigenlijke code implementeert. De test zou moeten falen (zoals “rood” aangeeft) omdat er geen overeenkomstige code is om hem te laten slagen.
  • Groene fase – Schrijf net genoeg code om de falende test te laten slagen, waardoor deze van rood naar groen gaat. De code zal niet optimaal zijn, maar voldoet wel aan de eisen van de bijbehorende testcase.
  • Refactorfase – Refactor de code om de leesbaarheid, onderhoudbaarheid en prestaties te verbeteren zonder het gedrag te veranderen. In deze fase kun je gemakkelijk wijzigingen in de code aanbrengen zonder je zorgen te maken over regressieproblemen, omdat de bestaande testgevallen deze opvangen.

TDD heeft verschillende voordelen:

  • Vroege opsporing van bugs – TDD helpt bugs vroeg in het ontwikkelproces op te sporen, waardoor de kosten en tijd van het oplossen van problemen later in de ontwikkelcyclus worden beperkt.
  • Verbeterd ontwerp – TDD stimuleert modulaire en losjes gekoppelde code voor een beter softwareontwerp. Het moedigt je aan om na te denken over de interface en de interacties tussen componenten voordat je ze implementeert.
  • Vertrouwen in refactoring – Je kunt code met vertrouwen refactoren, omdat je weet dat bestaande tests snel regressies identificeren die tijdens het refactoren zijn geïntroduceerd.
  • Levende documentatie – Testgevallen dienen als levende documentatie door voorbeelden te geven van hoe de code zich zou moeten gedragen. Deze documentatie is altijd actueel omdat falende tests wijzen op problemen in de code.

Bij de ontwikkeling van Laravel pas je TDD principes toe door tests te schrijven voor componenten zoals controllers, modellen en services voordat je ze implementeert.

De testomgeving van Laravel, waaronder PHPUnit, biedt handige methoden en asserties om TDD te vergemakkelijken, zodat je zinvolle tests kunt maken en de red-green-refactor cyclus effectief kunt volgen.

Basisvoorbeelden van unit testen

Deze sectie legt uit hoe je een eenvoudige test schrijft om de functionaliteit van je model te controleren.

Vereisten

Om mee te kunnen volgen, heb je het volgende nodig:

  • Voldoen aan de randvoorwaarden die worden genoemd in de Laravel bloggids.
  • Een Laravel applicatie. Deze tutorial gebruikt de applicatie die is gemaakt in de bovenstaande gids. Je kunt het lezen en de blogapplicatie maken, maar als je alleen de broncode nodig hebt om de tests te implementeren, volg dan de stappen hieronder.
  • Xdebug geïnstalleerd en geconfigureerd met coverage mode ingeschakeld.

Stel het project in

  1. Voer dit commando uit in een terminalvenster om het project te klonen.
    git clone https://github.com/VirtuaCreative/kinsta-laravel-blog.git
  2. Ga naar de projectmap en voer het commando composer install uit om de projectdependencies te installeren.
  3. Hernoem het bestand env.example naar .env.
  4. Voer het commando php artisan key:generate uit om een app key te genereren.

Tests maken en uitvoeren

Om te beginnen moet je ervoor zorgen dat je de projectcode op je machine hebt staan. Het model dat je gaat testen is het Post model gedefinieerd in het app/Http/Models/Post.php bestand. Dit model bevat verschillende invulbare attributen, zoals title, description, en image.

Jouw taak bestaat uit het maken van eenvoudige unit tests voor dit model. De ene controleert of de attributen goed zijn ingesteld, terwijl de andere de massatoewijzing onderzoekt door te proberen een niet-vulbaar attribuut toe te wijzen.

  1. Voer het commando php artisan make:test PostModelFunctionalityTest --unit uit om een nieuwe testcase te maken. De optie --unit geeft aan dat dit een unit test is en slaat het op in de map tests/Unit .
  2. Open het bestand tests/Unit/PostModelFunctionalityTest.php en vervang de functie test_example door deze code:
    public function test_attributes_are_set_correctly()
       {
           // create a new post instance with attributes
           $post = new Post([
               'title' => 'Sample Post Title',
               'description' => 'Sample Post Description',
               'image' => 'sample_image.jpg',
           ]);
    
           // check if you set the attributes correctly
           $this->assertEquals('Sample Post Title', $post->title);
           $this->assertEquals('Sample Post Description', $post->description);
           $this->assertEquals('sample_image.jpg', $post->image);
       }
    
       public function test_non_fillable_attributes_are_not_set()
       {
           // Attempt to create a post with additional attributes (non-fillable)
           $post = new Post([
               'title' => 'Sample Post Title',
               'description' => 'Sample Post Description',
               'image' => 'sample_image.jpg',
               'author' => 'John Doe',
           ]);
    
           // check that the non-fillable attribute is not set on the post instance
           $this->assertArrayNotHasKey('author', $post->getAttributes());
       }

    Deze code definieert twee testmethoden.

    De eerste maakt een instantie Post met gespecificeerde attributen en met behulp van de assertEquals assertiemethode wordt gecontroleerd of je de attributen title, description en image correct hebt ingesteld.

    De tweede methode probeert een Post instantie te maken met een extra niet-vulbaar attribuut (author) en verzekert dat dit attribuut niet is ingesteld op de modelinstantie met behulp van de assertArrayNotHasKey assertiemethode.

  3. Zorg ervoor dat je het volgende use statement toevoegt in hetzelfde bestand:
    use App\Models\Post;
  4. Voer het commando php artisan config:clear uit om de configuratiecache te wissen.
  5. Voer het volgende commando uit om deze tests uit te voeren:
    php artisan test tests/Unit/PostModelFunctionalityTest.php

    Alle tests zouden moeten slagen en de terminal zou de resultaten en de totale tijd om de tests uit te voeren moeten weergeven.

Testen debuggen

Als tests mislukken, kun je ze debuggen door de volgende stappen te volgen:

  1. Bekijk de foutmelding in de terminal. Laravel geeft gedetailleerde foutmeldingen die het probleem precies aangeven. Lees de foutmelding zorgvuldig om te begrijpen waarom de test is mislukt.
  2. Inspecteer de tests en code die je aan het testen bent om afwijkingen op te sporen.
  3. Zorg ervoor dat je de gegevens en dependencies die nodig zijn voor de test goed hebt ingesteld.
  4. Gebruik debugging tools zoals Laravel’s dd() functie om variabelen en gegevens op specifieke punten in je testcode te inspecteren.
  5. Als je het probleem hebt geïdentificeerd, breng dan de nodige wijzigingen aan en voer de tests opnieuw uit tot ze slagen.

Tests en databases

Laravel biedt een handige manier om een testomgeving op te zetten met behulp van een in-memory SQLite database, die snel is en geen gegevens opslaat tussen testruns. Volg de onderstaande stappen om de testdatabase te configureren en tests te schrijven die communiceren met de database:

  1. Open het phpunit.xml bestand en verwijder het commentaar van de volgende regels code:
    <env name="DB_CONNECTION" value="sqlite"/>
    <env name="DB_DATABASE" value=":memory:"/>
  2. Voer het commando php artisan make:test PostCreationTest --unit uit om een nieuwe testcase aan te maken.
  3. Open het bestand tests/Unit/PostCreationTest.php en vervang de methode test_example door onderstaande code:
    public function testPostCreation()
       {
           // Create a new post and save it to the database
           $post = Post::create([
               'title' => 'Sample Post Title',
               'description' => 'Sample Post Description',
               'image' => 'sample_image.jpg',
           ]);
    
           // Retrieve the post from the database and assert its existence
           $createdPost = Post::find($post->id);
           $this->assertNotNull($createdPost);
           $this->assertEquals('Sample Post Title', $createdPost->title);
       }
  4. Zorg ervoor dat je het volgende use statement toevoegt:
    use App\Models\Post;

    Momenteel breidt de klasse PostCreationTest de basisklasse PHPUnitFrameworkTestCase uit. De basisklasse wordt vaak gebruikt voor unit tests als je rechtstreeks met PHPUnit werkt, buiten Laravel, of als je tests schrijft voor een component die niet nauw gekoppeld is met Laravel. Je moet echter toegang hebben tot de database, wat betekent dat je de klasse PostCreationTest moet aanpassen om de klasse TestsTestCase uit te breiden.

    Deze laatste klasse past de PHPUnitFrameworkTestCase klasse aan op Laravel toepassingen. Het biedt extra functionaliteit en Laravel-specifieke instellingen, zoals database seeding en testomgeving configuratie.

  5. Zorg ervoor dat je het use PHPUnitFrameworkTestCase; statement vervangt door use TestsTestCase;. Vergeet niet dat je de testomgeving hebt ingesteld om een in-memory SQLite database te gebruiken. Je moet de database dus migreren voordat je de tests uitvoert. Gebruik hiervoor de property IlluminateFoundationTestingRefreshDatabase. Deze property migreert de database als het schema niet up-to-date is en reset de database na elke test om ervoor te zorgen dat de gegevens van de vorige test niet interfereren met volgende tests.
  6. Voeg het volgende use statement toe aan het bestand tests/Unit/PostCreationTest.php om deze property in je code op te nemen:
    use Illuminate\Foundation\Testing\RefreshDatabase;
  7. Voeg vervolgens de volgende regel code toe net voor de methode testPostCreation:
    use RefreshDatabase;
  8. Voer de opdracht php artisan config:clear uit om de configuratiecache te wissen.
  9. Voer het volgende commando uit om deze test uit te voeren:
    php artisan test tests/Unit/PostCreationTest.php

    De tests moeten slagen en de terminal moet de testresultaten en de totale testtijd weergeven.

Feature testen

Terwijl unit tests individuele applicatiecomponenten in isolatie controleren, controleren feature tests grotere delen van de code, zoals hoe verschillende objecten op elkaar inwerken. Feature testen is om verschillende redenen van vitaal belang:

  1. End-to-end validatie – Bevestigt dat de hele functie naadloos werkt, inclusief de interacties tussen verschillende componenten zoals controllers, modellen, views en zelfs de database.
  2. End-to-end testen – Omvat de volledige gebruikersstroom van het eerste verzoek tot de uiteindelijke reactie, wat problemen aan het licht kan brengen die unit tests misschien over het hoofd zien. Deze mogelijkheid maakt ze waardevol voor het testen van user journeys en complexe scenario’s.
  3. Garanderen van gebruikerservaring – Bootst gebruikersinteracties na, helpt bij het controleren van een consistente gebruikerservaring en of de functie werkt zoals bedoeld.
  4. Regressiedetectie – Vangt regressies en niet-werkende veranderingen op bij het introduceren van nieuwe code. Als een bestaande feature begint te falen in een functietest, geeft dit aan dat er iets kapot is gegaan.

Maak nu een feature test voor de PostController in het bestand app/Http/Controllers/PostController.php. Je richt je op de methode store, die de binnenkomende gegevens valideert en berichten aanmaakt en opslaat in de database.

De test simuleert een gebruiker die een nieuw bericht aanmaakt via een webinterface en zorgt ervoor dat de code het bericht opslaat in de database en de gebruiker na het aanmaken doorverwijst naar de pagina Posts Index. Volg hiervoor deze stappen:

  1. Voer het commando php artisan make:test PostControllerTest uit om een nieuwe testcase aan te maken in de map tests/Features.
  2. Open het bestand tests/Feature/PostControllerTest.php en vervang de methode test_example door deze code:
    use RefreshDatabase; // Refresh the database after each test
    
       public function test_create_post()
       {
           // Simulate a user creating a new post through the web interface
           $response = $this->post(route('posts.store'), [
               'title' => 'New Post Title',
               'description' => 'New Post Description',
               'image' => $this->create_test_image(),
           ]);
    
           // Assert that the post is successfully stored in the database
           $this->assertCount(1, Post::all());
    
           // Assert that the user is redirected to the Posts Index page after post creation
           $response->assertRedirect(route('posts.index'));
       }
    
       // Helper function to create a test image for the post
       private function create_test_image()
       {
           // Create a mock image file using Laravel's UploadedFile class
           $file = UploadedFile::fake()->image('test_image.jpg');
    
           // Return the path to the temporary image file
           return $file;
       }

    De test_create_post functie simuleert een gebruiker die een nieuwe post maakt door een POST verzoek te doen aan de posts.store route met specifieke attributen, inclusief een mock afbeelding die is gegenereerd met behulp van Laravel’s UploadedFile klasse.

    De test bevestigt vervolgens dat de code het bericht met succes heeft opgeslagen in de database door de telling van Post::all() te controleren. Er wordt gecontroleerd of de code de gebruiker doorverwijst naar de pagina Posts Index na het maken van een bericht.

    Deze test zorgt ervoor dat de functionaliteit voor het aanmaken van berichten werkt en dat de applicatie de database-interacties en omleidingen na het indienen van berichten correct afhandelt.

  3. Voeg de volgende use verklaringen toe aan hetzelfde bestand:
    use App\Models\Post;
    use Illuminate\Http\UploadedFile;
  4. Voer de opdracht php artisan config:clear uit om de configuratiecache te wissen.
  5. Voer deze opdracht uit om deze test uit te voeren:
    php artisan test tests/Feature/PostControllerTest.php

    De test zou moeten slagen en de terminal zou de testresultaten en de totale tijd om de test uit te voeren moeten tonen.

Testdekking bevestigen

Testdekking verwijst naar hoeveel van de codebase je unit-, feature- of browsertests controleren, uitgedrukt als percentage. Het helpt je bij het identificeren van niet-geteste gebieden in je codebase en de niet-geteste gebieden die mogelijk bugs bevatten.

Tools zoals PHPUnit’s code coverage functie en Laravel’s ingebouwde coverage rapport genereren rapporten die laten zien welke delen van je codebase door je tests worden gecontroleerd. Dit proces geeft belangrijke informatie over de kwaliteit van je tests en helpt je te focussen op gebieden die mogelijk extra getest moeten worden.

Een rapport genereren

  1. Verwijder de bestanden tests/Feature/ExampleTest.php en tests/Unit/ExampleTest.php, omdat je ze niet hebt gewijzigd en ze fouten kunnen veroorzaken.
  2. Voer het commando php artisan test --coverage uit in een terminalvenster. Je zou een uitvoer moeten krijgen zoals hieronder:
    Schermopname van de uitvoering van het commando php artisan test --coverage. Het toont het totale aantal testen dat geslaagd is en de tijd die nodig was om de resultaten uit te voeren. Het toont ook elk onderdeel in je codebase en het code coverage percentage.
    Het uitvoeren van de opdracht php artisan test –coverage.

    Het code coverage rapport toont de testresultaten, het totaal aantal testen dat is geslaagd en de tijd om de resultaten uit te voeren. Het toont ook elk onderdeel in je codebase en het code coverage percentage. De percentages geven aan welk deel van de code je tests dekken.

    Bijvoorbeeld, Models/Post heeft 100% dekking, wat betekent dat alle methoden en regels code van het model zijn gedekt. Het rapport voor codedekking toont ook de Total coverage– de totale codedekking voor de hele codebase. In dit geval dekken de tests slechts 65,3% van de code.

  3. Voer het commando php artisan test --coverage --min=85 uit om een minimale dekkingsdrempel op te geven. Deze opdracht stelt een minimale drempel van 85% in. Je zou de volgende uitvoer moeten krijgen:
    Schermafbeelding van het dekkingsrapport met een minimumdrempel van 85%. De onderste regel geeft aan dat de test mislukt omdat hij de minimumdrempel niet haalt.
    Test met een minimum drempel van 85%.

    De testsuites mislukken omdat de code niet voldoet aan de ingestelde minimumdrempel van 85%.

    Hoewel het bereiken van een hogere codedekking – vaak 100% – het doel is, is het belangrijker om de kritieke en complexe onderdelen van je applicatie grondig te testen.

Samenvatting

Door de in dit artikel beschreven best practices te omarmen, zoals het schrijven van zinvolle en uitgebreide tests, het volgen van de rood-groen-refactor cyclus in TDD en het benutten van de testfuncties van Laravel en PHPUnit, kun je robuuste applicaties van hoge kwaliteit maken.

Bovendien heb je de optie om je Laravel applicatie te hosten met de snelle, veilige en betrouwbare infrastructuur van Kinsta. Bovendien kun je de Kinsta API gebruiken om deployments te starten binnen je CI/CD pijplijnen via platforms zoals GitHub Actions, CircleCI en meer.

Jeremy Holcombe Kinsta

Content & Marketing Editor bij Kinsta, WordPress Web Developer en Content Writer. Buiten alles wat met WordPress te maken heeft, geniet ik van het strand, golf en films. En verder heb ik last van alle problemen waar andere lange mensen ook tegenaan lopen ;).