Artikelen

Leer hoe u tests uitvoert in Laravel met eenvoudige voorbeelden, met behulp van PHPUnit en PEST

Als het gaat om geautomatiseerde tests of unit-tests, zijn er in elke programmeertaal twee tegengestelde meningen:

  • verloren tijd
  • Je kunt niet zonder

Met dit artikel zullen we dus proberen de eerste te overtuigen, vooral door te laten zien hoe gemakkelijk het is om aan de slag te gaan met geautomatiseerd testen in Laravel.

Laten we het eerst hebben over het ‘waarom’, en laten we dan enkele voorbeelden bekijken van het hoe.

Waarom we geautomatiseerd testen nodig hebben

Geautomatiseerde tests voeren delen van de code uit en rapporteren eventuele fouten. Dat is de eenvoudigste manier om ze te beschrijven. Stel je voor dat je een nieuwe functie in een app uitrolt, en dat een persoonlijke robotassistent de nieuwe functie handmatig gaat testen, terwijl hij ook test of de nieuwe code geen van de oude functies verbreekt.

Dit is het belangrijkste voordeel: alle functies automatisch opnieuw testen. Dit lijkt misschien extra werk, maar als je de “robot” niet vertelt om het te doen, moeten we het ook handmatig doen, toch? 

Of er kunnen nieuwe functies worden uitgebracht zonder te testen of ze werken, in de hoop dat gebruikers bugs zullen melden.

Geautomatiseerde tests kunnen ons verschillende voordelen bieden:

  • Bespaar handmatige testtijd;
  • Ze stellen u in staat tijd te besparen op zowel de nieuwe geïmplementeerde functie als op de geconsolideerde functies door regressie te vermijden;
  • Vermenigvuldig dit voordeel met alle nieuwe functies en alle reeds geïmplementeerde functies;
  • De voorgaande drie punten zijn van toepassing op elke nieuwe versie;
  • ...

Probeer je applicatie over een jaar of twee voor te stellen, met nieuwe ontwikkelaars in het team die de code die in voorgaande jaren is geschreven niet kennen, of zelfs niet weten hoe ze deze moeten testen. 

Onze eerste geautomatiseerde tests

Om het eerste uit te voeren geautomatiseerd testen in Laravel, hoeft u geen code te schrijven. Ja, dat lees je goed. Alles is al geconfigureerd en voorbereid in de pre-installatiedefinite van Laravel, inclusief het allereerste basisvoorbeeld.

U kunt proberen een Laravel-project te installeren en onmiddellijk de eerste tests uitvoeren:

laravel new project
cd project
php artisan test

Dit zou het resultaat in uw console moeten zijn:

Als we naar de pre kijkendefiavond van Laravel /tests, we hebben twee bestanden:

tests/Feature/ExampleTest.php:

class ExampleTest extends TestCase
{
    public function test_the_application_returns_a_successful_response()
    {
        $response = $this->get('/');
 
        $response->assertStatus(200);
    }
}

U hoeft geen enkele syntaxis te kennen om te begrijpen wat hier aan de hand is: laad de startpagina en controleer of de statuscode HTTP is "200 OK'.

Ook bekend als de naam van de methode test_the_application_returns_a_successful_response() wordt leesbare tekst wanneer u de testresultaten bekijkt, eenvoudigweg door het onderstrepingssymbool te vervangen door een spatie.

tests/Eenheid/VoorbeeldTest.php:

class ExampleTest extends TestCase
{
    public function test_that_true_is_true()
    {
        $this->assertTrue(true);
    }
}

Lijkt een beetje zinloos, controleren of dit waar is? 

We zullen iets later specifiek over unit-tests praten. Voor nu moet u begrijpen wat er in het algemeen bij elke test gebeurt.

  • Elk testbestand in de map /tests is een PHP-klasse die de TestCase uitbreidt van PHPEenheid
  • Binnen elke klasse kunt u meerdere methoden maken, meestal één methode om per situatie te testen
  • Binnen elke methode zijn er drie acties: de situatie voorbereiden, vervolgens actie ondernemen en vervolgens verifiëren (bevestigen) of de uitkomst is zoals verwacht

Structureel is dat alles wat u hoeft te weten, al het andere hangt af van de exacte dingen die u wilt testen.

Om een ​​lege testklasse te genereren, voert u eenvoudigweg deze opdracht uit:

php artisan make:test HomepageTest

Het bestand wordt gegenereerd tests/Feature/HomepageTest.php:

class HomepageTest extends TestCase
{
    // Replace this method with your own ones
    public function test_example()
    {
        $response = $this->get('/');
 
        $response->assertStatus(200);
    }
}

Laten we nu eens kijken wat er gebeurt als een testcode mislukt in Laravel

Laten we nu kijken wat er gebeurt als de testbeweringen niet het verwachte resultaat opleveren.

Laten we de voorbeeldtests hiernaar wijzigen:

class ExampleTest extends TestCase
{
    public function test_the_application_returns_a_successful_response()
    {
        $response = $this->get('/non-existing-url');
 
        $response->assertStatus(200);
    }
}
 
 
class ExampleTest extends TestCase
{
    public function test_that_true_is_false()
    {
        $this->assertTrue(false);
    }
}

En nu, als we het commando uitvoeren php artisan test opnieuw:

 FAIL  Tests\Unit\ExampleTest
⨯ that true is true
 
 FAIL  Tests\Feature\ExampleTest
⨯ the application returns a successful response
 
---
 
• Tests\Unit\ExampleTest > that true is true
Failed asserting that false is true.
 
at tests/Unit/ExampleTest.php:16
   12▕      * @return void
   13▕      */
   14▕     public function test_that_true_is_true()
   15▕     {
➜  16▕         $this->assertTrue(false);
   17▕     }
   18▕ }
   19▕
 
• Tests\Feature\ExampleTest > the application returns a successful response
Expected response status code [200] but received 404.
Failed asserting that 200 is identical to 404.
 
at tests/Feature/ExampleTest.php:19
   15▕     public function test_the_application_returns_a_successful_response()
   16▕     {
   17▕         $response = $this->get('/non-existing-url');
   18▕
➜  19▕         $response->assertStatus(200);
   20▕     }
   21▕ }
   22▕
 
 
Tests:  2 failed
Time:   0.11s

Er zijn twee mislukte tests, gemarkeerd als FAIL, met onderstaande uitleg en pijlen die naar de exacte reeks tests wijzen die zijn mislukt. Op deze manier worden fouten aangegeven.

Voorbeeld: Registratieformuliercode testen in Laravel

Stel dat we een formulier hebben en we moeten verschillende gevallen testen: we controleren of het mislukt met ongeldige gegevens, we controleren of het lukt met de juiste invoer, enz.

Het officiële starterspakket van Laravel Breeze inclusief i het testen van de functionaliteit daarin. Laten we vanaf daar enkele voorbeelden bekijken:

tests/Feature/RegistrationTest.php

use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
 
class RegistrationTest extends TestCase
{
    use RefreshDatabase;
 
    public function test_registration_screen_can_be_rendered()
    {
        $response = $this->get('/register');
 
        $response->assertStatus(200);
    }
 
    public function test_new_users_can_register()
    {
        $response = $this->post('/register', [
            'name' => 'Test User',
            'email' => 'test@example.com',
            'password' => 'password',
            'password_confirmation' => 'password',
        ]);
 
        $this->assertAuthenticated();
        $response->assertRedirect(RouteServiceProvider::HOME);
    }
}

Hier hebben we twee tests in één klasse, omdat ze allebei verband houden met het registratieformulier: één controleert of het formulier correct is geladen en een andere controleert of het indienen goed werkt.

Laten we vertrouwd raken met nog twee methoden om het resultaat te verifiëren, nog twee beweringen: $this->assertAuthenticated()$response->assertRedirect(). U kunt alle beschikbare beweringen controleren in de officiële documentatie van PHPEenheid e Laravel-reactie . Merk op dat er enkele algemene beweringen over dit onderwerp voorkomen $this, terwijl anderen het specifieke controleren $responsevan de routeoproep.

Een ander belangrijk ding is de use RefreshDatabase;statement, met de streek, ingevoegd boven de klasse. Dit is nodig wanneer testacties de database kunnen beïnvloeden, zoals in dit voorbeeld, loggen voegt een nieuw item toe aan de database usersdatabase tabel. Hiervoor moet u een aparte testdatabase maken, die wordt bijgewerkt php artisan migrate:freshelke keer dat de tests worden uitgevoerd.

U heeft twee opties: fysiek een afzonderlijke database maken of een SQLite-database in het geheugen gebruiken. Beide zijn geconfigureerd in het bestand phpunit.xmlstandaard geleverddefinita met Laravel. Concreet heb je dit onderdeel nodig:

<php>
    <env name="APP_ENV" value="testing"/>
    <env name="BCRYPT_ROUNDS" value="4"/>
    <env name="CACHE_DRIVER" value="array"/>
    <!-- <env name="DB_CONNECTION" value="sqlite"/> -->
    <!-- <env name="DB_DATABASE" value=":memory:"/> -->
    <env name="MAIL_MAILER" value="array"/>
    <env name="QUEUE_CONNECTION" value="sync"/>
    <env name="SESSION_DRIVER" value="array"/>
    <env name="TELESCOPE_ENABLED" value="false"/>
</php>

Zie de DB_CONNECTIONDB_DATABASEOp welke wordt commentaar gegeven? Als u SQLite op uw server heeft, is de eenvoudigste actie het simpelweg verwijderen van commentaar op die regels. Uw tests zullen dan worden uitgevoerd op basis van de database in het geheugen.

In deze test zeggen we dat de gebruiker succesvol is geauthenticeerd en doorgestuurd naar de juiste homepage, maar we kunnen ook de daadwerkelijke gegevens in de database testen.

Naast deze code:

$this->assertAuthenticated();
$response->assertRedirect(RouteServiceProvider::HOME);

Wij kunnen ook gebruiken de databasetestbeweringen en doe zoiets als dit:

$this->assertDatabaseCount('users', 1);
 
// Or...
$this->assertDatabaseHas('users', [
    'email' => 'test@example.com',
]);

Voorbeeld van de inlogpagina

Laten we nu nog een voorbeeld bekijken van een inlogpagina met Laravel Breeze

tests/Feature/AuthenticationTest.php:

class AuthenticationTest extends TestCase
{
    use RefreshDatabase;
 
    public function test_login_screen_can_be_rendered()
    {
        $response = $this->get('/login');
 
        $response->assertStatus(200);
    }
 
    public function test_users_can_authenticate_using_the_login_screen()
    {
        $user = User::factory()->create();
 
        $response = $this->post('/login', [
            'email' => $user->email,
            'password' => 'password',
        ]);
 
        $this->assertAuthenticated();
        $response->assertRedirect(RouteServiceProvider::HOME);
    }
 
    public function test_users_can_not_authenticate_with_invalid_password()
    {
        $user = User::factory()->create();
 
        $this->post('/login', [
            'email' => $user->email,
            'password' => 'wrong-password',
        ]);
 
        $this->assertGuest();
    }
}

Het gaat om het inlogformulier. De logica is vergelijkbaar met registratie, toch? Maar drie methoden in plaats van twee, dus dit is een voorbeeld van het testen van zowel goede als slechte scenario's. De algemene logica is dus dat je beide gevallen moet testen: wanneer dingen goed gaan en wanneer ze mislukken.

Innovatie nieuwsbrief
Mis het belangrijkste nieuws over innovatie niet. Meld u aan om ze per e-mail te ontvangen.

Wat u in deze test ziet, is ook het gebruik van Databasefabrieken : Laravel maakt nepgebruiker aan ( nogmaals, in uw bijgewerkte testdatabase ) en probeert vervolgens in te loggen, met de juiste of onjuiste inloggegevens.

Opnieuw genereert Laravel de fabriekspredefinita met valse gegevens voor de Usermodel, buiten de doos.

database/factories/UserFactory.php:

class UserFactory extends Factory
{
    public function definition()
    {
        return [
            'name' => $this->faker->name(),
            'email' => $this->faker->unique()->safeEmail(),
            'email_verified_at' => now(),
            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
            'remember_token' => Str::random(10),
        ];
    }
}

Zie je, hoeveel dingen worden door Laravel zelf voorbereid, dus zou het voor ons gemakkelijk zijn om te beginnen met testen?

Dus als we executeren php artisan testna het installeren van Laravel Breeze zouden we zoiets als dit moeten zien:

 PASS  Tests\Unit\ExampleTest
✓ that true is true
 
 PASS  Tests\Feature\Auth\AuthenticationTest
✓ login screen can be rendered
✓ users can authenticate using the login screen
✓ users can not authenticate with invalid password
 
 PASS  Tests\Feature\Auth\EmailVerificationTest
✓ email verification screen can be rendered
✓ email can be verified
✓ email is not verified with invalid hash
 
 PASS  Tests\Feature\Auth\PasswordConfirmationTest
✓ confirm password screen can be rendered
✓ password can be confirmed
✓ password is not confirmed with invalid password
 
 PASS  Tests\Feature\Auth\PasswordResetTest
✓ reset password link screen can be rendered
✓ reset password link can be requested
✓ reset password screen can be rendered
✓ password can be reset with valid token
 
 PASS  Tests\Feature\Auth\RegistrationTest
✓ registration screen can be rendered
✓ new users can register
 
 PASS  Tests\Feature\ExampleTest
✓ the application returns a successful response
 
Tests:  17 passed
Time:   0.61s

Functionele tests vergeleken met unit-tests en andere

Je hebt de submappen gezien tests/Feature e tests/Unit ?. 

Wat is het verschil tussen hen? 

Wereldwijd zijn er, buiten het Laravel/PHP-ecosysteem, verschillende soorten geautomatiseerde tests. Je kunt termen vinden als:

  • Eenheidstests
  • Functietesten
  • Integratie testen
  • Functionele testen
  • End-to-end testen
  • Acceptatietesten
  • Rooktesten
  • enz.

Het klinkt ingewikkeld, en de werkelijke verschillen tussen dit soort tests zijn soms vaag. Daarom heeft Laravel al deze verwarrende termen vereenvoudigd en in tweeën gegroepeerd: eenheid/functie.

Simpel gezegd proberen functietests de daadwerkelijke functionaliteit van uw applicaties uit te voeren: haal de URL op, roep de API aan, boots het exacte gedrag na, zoals het invullen van het formulier. Functietests voeren doorgaans dezelfde of vergelijkbare bewerkingen uit als elke projectgebruiker, handmatig, in het echte leven.

Eenheidstests hebben twee betekenissen. Over het algemeen zult u merken dat elke geautomatiseerde test ‘unit-testen’ wordt genoemd en dat het hele proces ‘unit-testen’ kan worden genoemd. Maar in de context van functionaliteit versus eenheid gaat dit proces over het afzonderlijk testen van een specifieke, niet-openbare code-eenheid. Je hebt bijvoorbeeld een Laravel-klasse met een methode die iets berekent, zoals de totale orderprijs met parameters. Daarom zou de eenheidstest aangeven of de juiste resultaten worden geretourneerd van die methode (code-eenheid), met verschillende parameters.

Om een ​​eenheidstest te genereren, moet u een vlag toevoegen:

php artisan make:test OrderPriceTest --unit

De gegenereerde code is hetzelfde als de pre-unittestdefiLaravel-systeem:

class OrderPriceTest extends TestCase
{
    public function test_example()
    {
        $this->assertTrue(true);
    }
}

Zoals je kunt zien, bestaat het niet RefreshDatabase, en dit is er één van defimeest voorkomende unit-testdefinities: het raakt de database niet, het werkt als een “black box”, geïsoleerd van de actieve applicatie.

Laten we, in een poging het eerder genoemde voorbeeld te imiteren, ons voorstellen dat we een serviceklasse hebben OrderPrice.

app/Services/OrderPriceService.php:

class OrderPriceService
{
    public function calculatePrice($productId, $quantity, $tax = 0.0)
    {
        // Some kind of calculation logic
    }
}

Dan zou de unit-test er ongeveer zo uit kunnen zien:

class OrderPriceTest extends TestCase
{
    public function test_single_product_no_taxes()
    {
        $product = Product::factory()->create(); // generate a fake product
        $price = (new OrderPriceService())->calculatePrice($product->id, 1);
        $this->assertEquals(1, $price);
    }
 
    public function test_single_product_with_taxes()
    {
        $price = (new OrderPriceService())->calculatePrice($product->id, 1, 20);
        $this->assertEquals(1.2, $price);
    }
 
    // More cases with more parameters
}

In mijn persoonlijke ervaring met Laravel-projecten zijn de overgrote meerderheid van de tests feature-tests en geen unit-tests. Eerst moet u testen of uw applicatie werkt zoals echte mensen deze zouden gebruiken.

Vervolgens kunt u dat doen als u speciale berekeningen of logica heeft definire als eenheid, met parameters, u kunt speciaal daarvoor unit-tests maken.

Soms vereist het schrijven van tests dat de code zelf wordt aangepast en geherstructureerd om deze meer ‘testbaar’ te maken: de eenheden worden opgedeeld in speciale klassen of methoden.

Wanneer/hoe testen uitvoeren?

Wat is het daadwerkelijke nut hiervan php artisan test, wanneer moet je het uitvoeren?

Er zijn verschillende benaderingen, afhankelijk van uw zakelijke workflow, maar over het algemeen moet u ervoor zorgen dat alle tests ‘groen’ (dat wil zeggen foutloos) zijn voordat u de laatste codewijzigingen naar de repository pusht.

Vervolgens werk je lokaal aan je taak en als je denkt dat je klaar bent, voer je een aantal tests uit om er zeker van te zijn dat je niets kapot hebt gemaakt. Houd er rekening mee dat uw code niet alleen fouten in uw logica kan veroorzaken, maar ook onbedoeld ander gedrag kan verbreken in de lang geleden geschreven code van iemand anders.

Als we nog een stap verder gaan, is automatisering mogelijk veel dingen. Met verschillende CI/CD-tools kun je specificeren dat tests moeten worden uitgevoerd wanneer iemand wijzigingen naar een specifieke Git-branch pusht of voordat code wordt samengevoegd in de productiebranch. De eenvoudigste workflow zou zijn om Github Actions te gebruiken, dat heb ik gedaan een apart filmpje wat het bewijst.

Wat moet je testen?

Er zijn verschillende meningen over hoe groot de zogenaamde ‘testdekking’ moet zijn: probeer elke mogelijke bewerking en case op elke pagina, of beperk het werk tot de belangrijkste onderdelen.

In feite ben ik het op dit punt eens met mensen die ervan beschuldigen dat geautomatiseerd testen meer tijd kost dan daadwerkelijk voordeel oplevert. Dit kan gebeuren als je voor elk detail tests schrijft. Dat gezegd hebbende, kan dit voor uw project vereist zijn: de belangrijkste vraag is “wat is de prijs van mogelijke fouten”.

Met andere woorden, u moet prioriteit geven aan uw testinspanningen door de vraag te stellen: “Wat zou er gebeuren als deze code mislukt?” Als uw betalingssysteem bugs bevat, heeft dit directe gevolgen voor het bedrijf. Dus als de functionaliteit van uw rollen/machtigingen wordt verbroken, is dit een enorm beveiligingsprobleem.

Ik vind het leuk hoe Matt Stauffer het op een conferentie verwoordde: “Je moet eerst die dingen testen die, als ze falen, ervoor zorgen dat je ontslagen wordt.” Dat is natuurlijk overdreven, maar je begrijpt het wel: probeer eerst de belangrijke dingen. En dan nog andere functies, als je tijd hebt.

PEST: nieuw alternatief voor PHPUnit

Alle bovenstaande voorbeelden zijn gebaseerd op de pre-testtool van Laraveldefiavond: PHPEenheid . Maar door de jaren heen zijn er andere tools in het ecosysteem verschenen en een van de nieuwste populaire is dat wel PLAAG . Gemaakt door een officiële Laravel-medewerker Nuno Maduro , heeft tot doel de syntaxis te vereenvoudigen, waardoor het schrijven van code voor tests nog sneller wordt.

Onder de motorkap draait het su PHPUnit, als extra laag, probeert alleen enkele vooraf herhaalde delen te minimaliserendefinite van de PHPUnit-code.

Laten we eens kijken naar een voorbeeld. Denk aan de pre-functietestklassedefigevestigd in Laravel? Ik zal je herinneren:

namespace Tests\Feature;
 
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
 
class ExampleTest extends TestCase
{
    public function test_the_application_returns_a_successful_response()
    {
        $response = $this->get('/');
 
        $response->assertStatus(200);
    }
}

Weet jij hoe dezelfde test eruit zou zien met PEST?

test('the application returns a successful response')->get('/')->assertStatus(200);

Ja, EEN regel code en dat is alles. Het doel van PEST is dus om de overhead te verwijderen van:

  • Klassen en methoden voor alles creëren;
  • Uitbreiding testcase;
  • Door acties op aparte lijnen te zetten: in PEST kun je ze aan elkaar koppelen.

Om een ​​PEST-test in Laravel te genereren, moet u een extra vlag opgeven:

php artisan make:test HomepageTest --pest

Op het moment van schrijven is PEST behoorlijk populair onder Laravel-ontwikkelaars, maar het is jouw persoonlijke voorkeur of je deze extra tool wilt gebruiken en de syntaxis ervan wilt leren, evenals een PHPUnit-notitie.

BlogInnovazione.it

Innovatie nieuwsbrief
Mis het belangrijkste nieuws over innovatie niet. Meld u aan om ze per e-mail te ontvangen.

Recente artikelen

De toekomst is hier: hoe de scheepvaartindustrie een revolutie teweegbrengt in de wereldeconomie

De marinesector is een echte mondiale economische macht, die is genavigeerd naar een markt van 150 miljard...

1 mei 2024

Uitgevers en OpenAI ondertekenen overeenkomsten om de informatiestroom die door kunstmatige intelligentie wordt verwerkt, te reguleren

Afgelopen maandag maakte de Financial Times een deal met OpenAI bekend. FT geeft licenties voor haar journalistiek van wereldklasse...

April 30 2024

Online betalingen: hier is hoe streamingdiensten u voor altijd laten betalen

Miljoenen mensen betalen voor streamingdiensten en betalen maandelijkse abonnementskosten. De algemene mening is dat je…

April 29 2024

Veeam biedt de meest uitgebreide ondersteuning voor ransomware, van bescherming tot respons en herstel

Coveware by Veeam zal responsdiensten op het gebied van cyberafpersingsincidenten blijven leveren. Coveware zal forensische en herstelmogelijkheden bieden...

April 23 2024