Artikler

Lær hvordan du gjør tester i Laravel med enkle eksempler, ved å bruke PHPUnit og PEST

Når det gjelder automatiserte tester eller enhetstester, i et hvilket som helst programmeringsspråk, er det to motsatte meninger:

  • tapt tid
  • Du klarer deg ikke uten

Så med denne artikkelen vil vi prøve å overbevise førstnevnte, spesielt ved å demonstrere hvor enkelt det er å komme i gang med automatisert testing i Laravel.

La oss først snakke om "hvorfor", og så se noen eksempler på hvordan.

Hvorfor vi trenger automatisert testing

Automatiserte tester kjører deler av koden og rapporterer eventuelle feil. Det er den enkleste måten å beskrive dem på. Tenk deg å rulle ut en ny funksjon i en app, og så ville en personlig robotassistent gå og manuelt teste den nye funksjonen, samtidig som den testet om den nye koden ikke brøt noen av de gamle funksjonene.

Dette er hovedfordelen: retesting av alle funksjoner automatisk. Dette kan virke som ekstraarbeid, men hvis du ikke ber "roboten" om å gjøre det, bør vi alternativt gjøre det manuelt, ikke sant? 

Eller nye funksjoner kan slippes uten å teste om de fungerer, i håp om at brukere vil rapportere feil.

Automatiserte tester kan gi oss flere fordeler:

  • Spar tid for manuell testing;
  • De lar deg spare tid både på den nye implementerte funksjonen og på de konsoliderte funksjonene ved å unngå regresjon;
  • Multipliser denne fordelen med alle nye funksjoner og alle funksjoner som allerede er implementert;
  • De tre foregående punktene gjelder for hver ny versjon;
  • ...

Prøv å se for deg applikasjonen din om et år eller to, med nye utviklere på laget som ikke kan koden skrevet i tidligere år, eller til og med hvordan de skal teste den. 

Våre første automatiserte tester

For å utføre den første automatisert testing i Laravel, trenger du ikke skrive noen kode. Ja, du leste riktig. Alt er allerede konfigurert og forberedt i forhåndsinstallasjonendefiLaravel, inkludert det aller første grunnleggende eksemplet.

Du kan prøve å installere et Laravel-prosjekt og kjøre de første testene umiddelbart:

laravel new project
cd project
php artisan test

Dette bør være resultatet i konsollen din:

Hvis vi tar en titt på predefinatt til 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 trenger ikke å kunne noen syntaks for å forstå hva som skjer her: last inn hjemmesiden og sjekk om statuskoden HTTP è "200 OK".

Også kjent som metodenavnet test_the_application_returns_a_successful_response() blir lesbar tekst når du ser testresultatene, ganske enkelt ved å erstatte understrekingssymbolet med et mellomrom.

tests/Unit/ExampleTest.php :

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

Virker litt meningsløst, å sjekke om dette er sant? 

Vi skal snakke spesifikt om enhetstester litt senere. For nå må du forstå hva som vanligvis skjer i hver test.

  • Hver testfil i mappen /tests er en PHP-klasse som utvider TestCase av PHPUnit
  • Innenfor hver klasse kan du lage flere metoder, vanligvis én metode for en situasjon å teste
  • Innenfor hver metode er det tre handlinger: forberede situasjonen, deretter iverksette tiltak og deretter bekrefte (bekrefte) om resultatet er som forventet

Strukturelt sett er det alt du trenger å vite, alt annet avhenger av de nøyaktige tingene du vil teste.

For å generere en tom testklasse, kjør ganske enkelt denne kommandoen:

php artisan make:test HomepageTest

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

La oss nå se hva som skjer hvis en testkode feiler i Laravel

La oss nå se hva som skjer hvis testpåstandene ikke gir det forventede resultatet.

La oss endre 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 nå, hvis vi kjø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

Det er to mislykkede tester, merket som FAIL, med forklaringer nedenfor og piler som peker på den nøyaktige linjen med tester som mislyktes. Feil vises på denne måten.

Eksempel: Testing av registreringsskjemakode i Laravel

Anta at vi har et skjema og vi må teste ulike saker: vi sjekker om det feiler med ugyldige data, vi sjekker om det lykkes med riktig inndata osv.

Det offisielle startsettet av Laravel Breeze inkluderer i testing av funksjonaliteten i den. La oss se på noen 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 en klasse, siden de begge er knyttet til påmeldingsskjemaet: en sjekker om skjemaet er lastet inn riktig og en annen sjekker om innleveringen fungerer bra.

La oss bli kjent med ytterligere to metoder for å verifisere resultatet, ytterligere to påstander: $this->assertAuthenticated()$response->assertRedirect(). Du kan sjekke alle påstandene som er tilgjengelige i den offisielle dokumentasjonen til PHPUnit e Laravel-svar . Merk at noen generelle påstander forekommer om emnet $this, mens andre sjekker det spesifikke $responsefra ruteanropet.

En annen viktig ting er use RefreshDatabase;uttalelse, med slaget, satt inn over klassen. Det er nødvendig når testhandlinger kan påvirke databasen, som i dette eksemplet legger logging til en ny oppføring i usersdatabasetabell. For dette bør du lage en egen testdatabase som vil bli oppdatert med php artisan migrate:freshhver gang testene kjøres.

Du har to alternativer: fysisk opprette en egen database eller bruk en SQLite-database i minnet. Begge er konfigurert i filen phpunit.xmlgitt som standarddefinita med Laravel. Spesifikt trenger du denne delen:

<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 DB_CONNECTIONDB_DATABASEhvilke er kommentert? Hvis du har SQLite på serveren din, er den enkleste handlingen å ganske enkelt fjerne kommentarene til disse linjene, og testene vil kjøre mot den minnedatabasen.

I denne testen sier vi at brukeren er vellykket autentisert og omdirigert til riktig hjemmeside, men vi kan også teste de faktiske dataene i databasen.

I tillegg til denne koden:

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

Vi kan også bruke databasetestpåstandene og gjør noe sånt som dette:

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

Eksempel på påloggingssiden

La oss nå se et annet eksempel på en påloggingsside 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 påloggingsskjemaet. Logikken ligner på registrering, ikke sant? Men tre metoder i stedet for to, så dette er et eksempel på å teste både gode og dårlige scenarier. Så den vanlige logikken er at du bør teste begge tilfeller: når ting går bra og når de mislykkes.

Nyhetsbrev for innovasjon
Ikke gå glipp av de viktigste nyhetene om innovasjon. Registrer deg for å motta dem på e-post.

Det du ser i denne testen er også bruken av Databasefabrikker : Laravel lager falsk bruker ( igjen, på din oppdaterte testdatabase ) og prøver deretter å logge på med riktig eller feil påloggingsinformasjon.

Nok en gang genererer Laravel fabrikken predefinita med falske data for Usermodell, utenfor esken.

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

Du skjønner, hvor mange ting er laget av Laravel selv, så ville det være lett for oss å begynne å teste?

Så hvis vi utfører php artisan testetter å ha installert Laravel Breeze, bør vi se noe slikt:

 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

Funksjonstester sammenlignet med enhetstester og andre

Du har sett undermappene tests/Feature e tests/Unit ?. 

Hva er forskjellen mellom dem? 

Globalt, utenfor Laravel/PHP-økosystemet, finnes det flere typer automatisert testing. Du kan finne begreper som:

  • Enhetstester
  • Funksjonstesting
  • Integrasjonstester
  • Funksjonstester
  • End-to-end testing
  • Akseptprøver
  • Røykprøver
  • etc.

Det høres komplisert ut, og de faktiske forskjellene mellom disse typene tester er noen ganger uskarpe. Det er derfor Laravel har forenklet alle disse forvirrende begrepene og gruppert dem i to: enhet/funksjon.

Enkelt sagt, funksjonstester prøver å utføre den faktiske funksjonaliteten til applikasjonene dine: få URL-en, ring API-en, etterlign den nøyaktige oppførselen som å fylle ut skjemaet. Funksjonstester utfører vanligvis samme eller lignende operasjoner som enhver prosjektbruker ville gjort, manuelt, i det virkelige liv.

Enhetstester har to betydninger. Generelt kan du oppleve at enhver automatisert test kalles "enhetstesting", og hele prosessen kan kalles "enhetstesting". Men i sammenheng med funksjonalitet versus enhet, handler denne prosessen om å teste en spesifikk ikke-offentlig kodeenhet, isolert. For eksempel har du en Laravel-klasse med en metode som beregner noe, som den totale ordreprisen med parametere. Derfor vil enhetstesten angi om korrekte resultater returneres fra den metoden (kodeenhet), med forskjellige parametere.

For å generere en enhetstest må du legge til et flagg:

php artisan make:test OrderPriceTest --unit

Den genererte koden er den samme som pre-enhetstestendefiLaravel system:

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

Som du kan se, eksisterer den ikke RefreshDatabase, og dette er en av defivanligste enhetstestdefinisjoner: den berører ikke databasen, den fungerer som en "svart boks", isolert fra den kjørende applikasjonen.

For å prøve å etterligne eksemplet jeg nevnte tidligere, la oss forestille oss 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
    }
}

Deretter kan enhetstesten se omtrent slik ut:

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-prosjekter er de aller fleste testene funksjonstester, ikke enhetstester. Først må du teste om applikasjonen din fungerer, slik virkelige mennesker ville brukt den.

Deretter, hvis du har spesielle beregninger eller logikk kan du definire som en enhet, med parametere, kan du lage enhetstester spesielt for det.

Noen ganger krever skriving av tester å modifisere selve koden og refaktorere den for å gjøre den mer "testbar": separering av enhetene i spesielle klasser eller metoder.

Når/hvordan utfører man tester?

Hva er egentlig bruken av dette php artisan test, når bør du kjøre den?

Det er forskjellige tilnærminger, avhengig av arbeidsflyten din, men generelt må du sørge for at alle tester er "grønne" (dvs. feilfrie) før du sender de endelige kodeendringene til depotet.

Deretter jobber du lokalt med oppgaven din, og når du tror du er ferdig, kjører du noen tester for å sikre at du ikke har ødelagt noe. Husk at koden din kan forårsake feil, ikke bare i logikken din, men også utilsiktet bryte annen oppførsel i andres kode skrevet for lenge siden.

Tar vi det et steg videre, er det mulig å automatisere mange tingene. Med forskjellige CI/CD-verktøy kan du spesifisere tester som skal kjøres når noen trykker endringer til en spesifikk Git-gren eller før du slår sammen kode i produksjonsgrenen. Den enkleste arbeidsflyten ville være å bruke Github Actions, jeg har en egen video som beviser det.

Hva bør du teste?

Det er forskjellige meninger om hvor stor den såkalte "testdekningen" bør være: prøv alle mulige operasjoner og etui på hver side, eller begrens arbeidet til de viktigste delene.

Faktisk er det her jeg er enig med folk som anklager automatisert testing for å ta mer tid enn å gi faktiske fordeler. Dette kan skje hvis du skriver tester for hver eneste detalj. Når det er sagt, kan det kreves av prosjektet ditt: Hovedspørsmålet er "hva er prisen på potensiell feil".

Med andre ord, du må prioritere testarbeidet ditt ved å stille spørsmålet "Hva ville skje hvis denne koden mislyktes?" Hvis betalingssystemet ditt har feil, vil det direkte påvirke virksomheten. Så hvis funksjonaliteten til rollene/tillatelsene dine er ødelagt, er dette et stort sikkerhetsproblem.

Jeg liker hvordan Matt Stauffer sa det på en konferanse: "Du må først teste de tingene som, hvis de mislykkes, ville få deg sparket fra jobben din." Selvfølgelig er det en overdrivelse, men du skjønner ideen: prøv de viktige tingene først. Og så andre funksjoner, hvis du har tid.

PEST: nytt alternativ til PHPUnit

Alle eksemplene ovenfor er basert på Laravels forhåndstestingsverktøydefikveld: PHPUnit . Men i løpet av årene har andre verktøy dukket opp i økosystemet, og en av de siste populære er SKADEDYR . Laget av offisiell Laravel-ansatt Nuno Maduro , har som mål å forenkle syntaksen, noe som gjør det enda raskere å skrive kode for tester.

Under panseret løper den su PHPUnit, som et ekstra lag, prøver bare å minimere noen forhåndsgjentatte delerdefinite til PHPUnit-koden.

La oss se på et eksempel. Husk pre-funksjonstestklassendefisatt i Laravel? Jeg vil minne deg på:

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

Vet du hvordan den samme testen vil se ut med PEST?

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

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

  • Lage klasser og metoder for alt;
  • Test case utvidelse;
  • Ved å sette handlinger på separate linjer: i PEST kan du lenke dem sammen.

For å generere en PEST-test i Laravel, må du spesifisere et ekstra flagg:

php artisan make:test HomepageTest --pest

Når dette skrives, er PEST ganske populær blant Laravel-utviklere, men det er din personlige preferanse om du vil bruke dette tilleggsverktøyet og lære dets syntaks, så vel som et PHPUnit-notat.

BlogInnovazione.it

Nyhetsbrev for innovasjon
Ikke gå glipp av de viktigste nyhetene om innovasjon. Registrer deg for å motta dem på e-post.

Siste artikler

Utgivere og OpenAI signerer avtaler for å regulere flyten av informasjon som behandles av kunstig intelligens

Sist mandag kunngjorde Financial Times en avtale med OpenAI. FT lisensierer sin journalistikk i verdensklasse...

30 april 2024

Nettbetalinger: Her er hvordan strømmetjenester får deg til å betale for alltid

Millioner av mennesker betaler for strømmetjenester og betaler månedlige abonnementsavgifter. Det er vanlig oppfatning at du...

29 april 2024

Veeam har den mest omfattende støtten for løsepengevare, fra beskyttelse til respons og gjenoppretting

Coveware by Veeam vil fortsette å tilby responstjenester for cyberutpressing. Coveware vil tilby kriminaltekniske og utbedringsmuligheter...

23 april 2024

Grønn og digital revolusjon: Hvordan prediktivt vedlikehold transformerer olje- og gassindustrien

Prediktivt vedlikehold revolusjonerer olje- og gasssektoren, med en innovativ og proaktiv tilnærming til anleggsledelse...

22 april 2024