Artikler

Lær hvordan du laver tests i Laravel med enkle eksempler ved hjælp af PHPUnit og PEST

Når det kommer til automatiserede tests eller enhedstests, i et hvilket som helst programmeringssprog, er der to modsatrettede meninger:

  • Spild af tid
  • Du kan ikke undvære det

Så med denne artikel vil vi forsøge at overbevise førstnævnte, især ved at demonstrere, hvor nemt det er at komme i gang med automatiseret test i Laravel.

Lad os først tale om "hvorfor", og lad os derefter se nogle eksempler på hvordan.

Hvorfor har vi brug for automatiseret test

Automatiserede test kører dele af koden og rapporterer eventuelle fejl. Det er den nemmeste måde at beskrive dem på. Forestil dig at rulle en ny funktion ud i en app, og så ville en personlig robotassistent gå hen og manuelt teste den nye funktion, samtidig med at den testede, om den nye kode ikke brød nogen af ​​de gamle funktioner.

Dette er den største fordel: gentestning af alle funktioner automatisk. Dette kan virke som ekstra arbejde, men hvis du ikke beder "robotten" om at gøre det, bør vi alternativt gøre det manuelt, ikke? 

Eller nye funktioner kan frigives uden at teste, om de virker, i håb om at brugerne vil rapportere fejl.

Automatiserede test kan give os flere fordele:

  • Spar manuel testtid;
  • De giver dig mulighed for at spare tid både på den nye implementerede funktion og på de konsoliderede funktioner ved at undgå regression;
  • Multiplicer denne fordel med alle nye funktioner og alle funktioner, der allerede er implementeret;
  • De foregående tre punkter gælder for hver ny version;
  • ...

Prøv at forestille dig din applikation om et år eller to, med nye udviklere på holdet, som ikke kender koden skrevet i tidligere år, eller endda hvordan man tester den. 

Vores første automatiserede test

At udføre den første automatiseret test i Laravel, behøver du ikke at skrive nogen kode. Ja, du læste rigtigt. Alt er allerede konfigureret og forberedt i præinstallationendefinat af Laravel, inklusive det allerførste grundlæggende eksempel.

Du kan prøve at installere et Laravel-projekt og køre de første test med det samme:

laravel new project
cd project
php artisan test

Dette skulle være resultatet i din konsol:

Hvis vi tager et kig på predefinat af Laravel /tests, vi har to filer:

tests/Feature/ExampleTest.php :

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

Du behøver ikke kende nogen syntaks for at forstå, hvad der foregår her: indlæs startsiden og kontroller, om statuskoden HTTP è "200 OK".

Også kendt som metodenavnet test_the_application_returns_a_successful_response() bliver læsbar tekst, når du ser testresultaterne, blot ved at erstatte understregningssymbolet med et mellemrum.

tests/Unit/ExampleTest.php :

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

Det virker lidt meningsløst, at tjekke om dette er sandt? 

Vi taler specifikt om enhedstests lidt senere. For nu skal du forstå, hvad der generelt sker i hver test.

  • Hver testfil i mappen /tests er en PHP-klasse, der udvider TestCase af PHPUnit
  • Inden for hver klasse kan du oprette flere metoder, normalt én metode til at teste en situation
  • Inden for hver metode er der tre handlinger: forberedelse af situationen, derefter handling og derefter verificere (bekræfte), om resultatet er som forventet

Strukturelt er det alt, du behøver at vide, alt andet afhænger af de præcise ting, du vil teste.

For at generere en tom testklasse skal du blot køre denne kommando:

php artisan make:test HomepageTest

Filen er genereret 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);
    }
}

Lad os nu se, hvad der sker, hvis en testkode fejler i Laravel

Lad os nu se, hvad der sker, hvis testpåstandene ikke returnerer det forventede resultat.

Lad os ændre eksempeltestene til dette:

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);
    }
}

Og nu, hvis vi kører kommandoen php artisan test en gang til:

 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

Der er to fejlbeståede prøver, markeret som FAIL, med forklaringer nedenfor og pile, der peger på den nøjagtige række af test, der mislykkedes. Fejl er angivet på denne måde.

Eksempel: Test af registreringsformularkode i Laravel

Antag, at vi har en formular, og vi skal teste forskellige sager: vi tjekker om den fejler med ugyldige data, vi tjekker om det lykkes med den korrekte indtastning osv.

Det officielle startsæt af Laravel Breeze omfatter i test af funktionaliteten i den. Lad os se på nogle eksempler derfra:

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);
    }
}

Her har vi to prøver i én klasse, da de begge er relateret til tilmeldingsformularen: en kontrollerer om formularen er indlæst korrekt og en anden kontrollerer om afleveringen fungerer godt.

Lad os blive bekendt med yderligere to metoder til at verificere resultatet, yderligere to påstande: $this->assertAuthenticated()$response->assertRedirect(). Du kan kontrollere alle de tilgængelige påstande i den officielle dokumentation af PHPUnit e Laravel svar . Bemærk, at der forekommer nogle generelle påstande om emnet $this, mens andre tjekker det specifikke $responsefra ruteopkaldet.

En anden vigtig ting er use RefreshDatabase;erklæring, med stregen, indsat over klassen. Det er nødvendigt, når testhandlinger kan påvirke databasen, som i dette eksempel tilføjer logning en ny post i usersdatabase tabel. Til dette bør du oprette en separat testdatabase, som vil blive opdateret med php artisan migrate:freshhver gang testene køres.

Du har to muligheder: fysisk oprette en separat database eller bruge en SQLite-database i hukommelsen. Begge er konfigureret i filen phpunit.xmlleveres som standarddefinita med Laravel. Specifikt har du brug for denne del:

<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>

Se den DB_CONNECTIONDB_DATABASEhvilke er kommenteret? Hvis du har SQLite på din server, er den enkleste handling blot at fjerne kommentarer til disse linjer, og dine test vil køre mod den in-memory database.

I denne test siger vi, at brugeren er blevet autentificeret og omdirigeret til den korrekte hjemmeside, men vi kan også teste de faktiske data i databasen.

Ud over denne kode:

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

Vi kan også bruge databasetestpåstandene og gør sådan noget:

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

Eksempel på login-siden

Lad os nu se endnu et eksempel på en login-side med 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();
    }
}

Det handler om login-formularen. Logikken ligner registrering, ikke? Men tre metoder i stedet for to, så dette er et eksempel på at teste både gode og dårlige scenarier. Så den fælles logik er, at du skal teste begge tilfælde: når tingene går godt, og når de fejler.

Nyhedsbrev om innovation
Gå ikke glip af de vigtigste nyheder om innovation. Tilmeld dig for at modtage dem via e-mail.

Det, du ser i denne test, er også brugen af Database fabrikker : Laravel opretter falsk bruger ( igen, på din opdaterede testdatabase ) og forsøger derefter at logge ind med korrekte eller forkerte legitimationsoplysninger.

Igen genererer Laravel fabrikkens prædefinita med falske data for Usermodel uden for æsken.

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),
        ];
    }
}

Ser du, hvor mange ting er forberedt af Laravel selv, så ville det være nemt for os at begynde at teste?

Så hvis vi udfører php artisan testefter at have installeret Laravel Breeze, skulle vi se noget som dette:

 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

Funktionstest sammenlignet med enhedstest og andre

Du har set undermapperne tests/Feature e tests/Unit ?. 

Hvad er forskellen mellem dem? 

Globalt, uden for Laravel/PHP-økosystemet, er der flere typer automatiseret test. Du kan finde udtryk som:

  • Enhedstest
  • Funktionstest
  • Integrationstest
  • Funktionstest
  • End-to-end test
  • Acceptprøver
  • Røgprøver
  • etc.

Det lyder kompliceret, og de faktiske forskelle mellem disse typer test er nogle gange slørede. Det er derfor, Laravel har forenklet alle disse forvirrende udtryk og grupperet dem i to: enhed/funktion.

Kort sagt, funktionstest forsøger at udføre den faktiske funktionalitet af dine applikationer: Hent URL'en, kald API'en, efterlign den nøjagtige adfærd som at udfylde formularen. Funktionstest udfører normalt de samme eller lignende operationer, som enhver projektbruger ville gøre manuelt i det virkelige liv.

Enhedstest har to betydninger. Generelt kan du opleve, at enhver automatiseret test kaldes "unit testing", og hele processen kan kaldes "unit testing". Men i sammenhæng med funktionalitet versus enhed, handler denne proces om at teste en specifik ikke-offentlig kodeenhed, isoleret. For eksempel har du en Laravel-klasse med en metode, der beregner noget, som den samlede ordrepris med parametre. Derfor vil enhedstesten angive, om der returneres korrekte resultater fra den pågældende metode (kodeenhed), med forskellige parametre.

For at generere en enhedstest skal du tilføje et flag:

php artisan make:test OrderPriceTest --unit

Den genererede kode er den samme som før-enhedstestendefiLaravel system:

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

Som du kan se, eksisterer den ikke RefreshDatabase, og dette er en af defimest almindelige enhedstestdefinitioner: den rører ikke databasen, den fungerer som en "sort boks", isoleret fra den kørende applikation.

Prøv at efterligne det eksempel, jeg nævnte tidligere, lad os forestille os, at vi har en serviceklasse OrderPrice.

app/Services/OrderPriceService.php:

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

Så kunne enhedstesten se sådan ud:

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
}

I min personlige erfaring med Laravel-projekter er langt de fleste tests Feature-tests, ikke Unit-tests. Først skal du teste, om din applikation virker, som rigtige mennesker ville bruge den.

Dernæst, hvis du har specielle beregninger eller logik, kan du definire som en enhed, med parametre, kan du oprette enhedstests specifikt til det.

Nogle gange kræver det at skrive test, at man modificerer selve koden og omfaktorerer den for at gøre den mere "testbar": adskille enhederne i specielle klasser eller metoder.

Hvornår/hvordan udføres tests?

Hvad er den egentlige brug af dette php artisan test, hvornår skal du køre det?

Der er forskellige tilgange, afhængigt af din virksomheds arbejdsgang, men generelt skal du sikre dig, at alle test er "grønne" (dvs. fejlfri), før du skubber de endelige kodeændringer til lageret.

Derefter arbejder du lokalt på din opgave, og når du tror, ​​du er færdig, skal du køre nogle tests for at sikre, at du ikke har ødelagt noget. Husk, din kode kan forårsage fejl, ikke kun i din egen logik, men også utilsigtet bryde en anden adfærd i en andens kode skrevet for længe siden.

Tager vi det et skridt videre, er det muligt at automatisere mange ting. Med forskellige CI/CD-værktøjer kan du specificere test, der skal køres, når nogen skubber ændringer til en specifik Git-gren, eller før koden flettes ind i produktionsgrenen. Den enkleste arbejdsgang ville være at bruge Github Actions, jeg har en separat video hvilket beviser det.

Hvad skal du teste?

Der er forskellige meninger om, hvor stor den såkaldte "testdækning" skal være: prøv alle mulige operationer og sager på hver side, eller afgrænse arbejdet til de vigtigste dele.

Faktisk er det her, jeg er enig med folk, der beskylder automatiseret test for at tage mere tid end at give reelle fordele. Dette kan ske, hvis du skriver test for hver enkelt detalje. Når det er sagt, kan det være påkrævet af dit projekt: Hovedspørgsmålet er "hvad er prisen for potentielle fejl".

Med andre ord skal du prioritere din testindsats ved at stille spørgsmålet "Hvad ville der ske, hvis denne kode mislykkedes?" Hvis dit betalingssystem har fejl, vil det direkte påvirke virksomheden. Så hvis funktionaliteten af ​​dine roller/tilladelser er brudt, er dette et stort sikkerhedsproblem.

Jeg kan godt lide, hvordan Matt Stauffer udtrykte det på en konference: "Du skal først teste de ting, som, hvis de fejler, ville få dig fyret fra dit job." Det er selvfølgelig en overdrivelse, men du får ideen: prøv de vigtige ting først. Og så andre funktioner, hvis du har tid.

PEST: nyt alternativ til PHPUnit

Alle ovenstående eksempler er baseret på Laravel prætestværktøjdefiaften: PHPUnit . Men gennem årene er der dukket andre værktøjer op i økosystemet, og et af de seneste populære er SKADEDYR . Skabt af officiel Laravel-medarbejder Nuno Maduro , har til formål at forenkle syntaksen, hvilket gør det endnu hurtigere at skrive kode til test.

Under motorhjelmen kører den su PHPUnit, som et ekstra lag, prøver bare at minimere nogle forud-gentagne deledefinite af PHPUnit-koden.

Lad os se på et eksempel. Husk præfunktionstestklassendefinited i Laravel? Jeg vil minde dig om:

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);
    }
}

Ved du, hvordan den samme test ville se ud med PEST?

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

Ja, EN linje kode og det er det. Så målet med PEST er at fjerne overhead af:

  • Oprettelse af klasser og metoder til alt;
  • Test case forlængelse;
  • Ved at sætte handlinger på separate linjer: i PEST kan du kæde dem sammen.

For at generere en PEST-test i Laravel skal du angive et ekstra flag:

php artisan make:test HomepageTest --pest

Når dette skrives, er PEST ret populær blandt Laravel-udviklere, men det er din personlige præference, om du vil bruge dette ekstra værktøj og lære dets syntaks, såvel som en PHPUnit-note.

BlogInnovazione.it

Nyhedsbrev om innovation
Gå ikke glip af de vigtigste nyheder om innovation. Tilmeld dig for at modtage dem via e-mail.

Seneste artikler

Veeam har den mest omfattende support til ransomware, fra beskyttelse til respons og gendannelse

Coveware by Veeam vil fortsætte med at levere responstjenester til cyberafpresning. Coveware vil tilbyde kriminaltekniske og afhjælpende funktioner...

23 April 2024

Grøn og digital revolution: Hvordan prædiktiv vedligeholdelse transformerer olie- og gasindustrien

Forudsigende vedligeholdelse revolutionerer olie- og gassektoren med en innovativ og proaktiv tilgang til anlægsstyring...

22 April 2024

Britisk antitrust-tilsynsmyndighed rejser BigTech-alarm over GenAI

Det britiske CMA har udsendt en advarsel om Big Techs adfærd på markedet for kunstig intelligens. Der…

18 April 2024

Casa Green: energirevolution for en bæredygtig fremtid i Italien

Dekretet om "grønne huse", der er formuleret af Den Europæiske Union for at øge bygningers energieffektivitet, har afsluttet sin lovgivningsproces med...

18 April 2024