Articole

Aflați cum să faceți teste în Laravel cu exemple simple, folosind PHPUnit și PEST

Când vine vorba de teste automate sau de teste unitare, în orice limbaj de programare, există două opinii opuse:

  • Pierdere de timp
  • Nu te poți descurca fără ea

Așadar, cu acest articol vom încerca să-i convingem pe primul, mai ales demonstrând cât de ușor este să începi cu testarea automată în Laravel.

Mai întâi să vorbim despre „de ce”, apoi să vedem câteva exemple despre cum.

De ce avem nevoie de testare automată

Testele automate rulează părți ale codului și raportează orice erori. Acesta este cel mai simplu mod de a le descrie. Imaginați-vă că lansați o nouă funcție într-o aplicație, iar apoi un asistent robot personal ar merge și va testa manual noua caracteristică, testând în același timp dacă noul cod nu a încălcat niciuna dintre funcțiile vechi.

Acesta este principalul avantaj: retestarea automată a tuturor funcțiilor. Acest lucru poate părea o muncă suplimentară, dar dacă nu îi spuneți „robotului” să o facă, ar trebui să o facem, alternativ, manual, nu? 

Sau ar putea fi lansate noi funcții fără a testa dacă funcționează, în speranța că utilizatorii vor raporta erori.

Testele automate ne pot oferi mai multe avantaje:

  • Economisiți timp de testare manuală;
  • Acestea vă permit să economisiți timp atât la noua funcție implementată, cât și la funcțiile consolidate prin evitarea regresiei;
  • Înmulțiți acest beneficiu cu toate funcțiile noi și cu toate caracteristicile deja implementate;
  • Cele trei puncte anterioare se aplică fiecărei versiuni noi;
  • ...

Încercați să vă imaginați aplicația peste un an sau doi, cu noi dezvoltatori în echipă care nu știu codul scris în anii anteriori sau chiar cum să-l testeze. 

Primele noastre teste automate

Pentru a efectua primul testare automată în Laravel, nu trebuie să scrieți niciun cod. Da, ai citit bine. Totul este deja configurat și pregătit în preinstalaredefisfârșitul lui Laravel, inclusiv primul exemplu de bază.

Puteți încerca să instalați un proiect Laravel și să rulați imediat primele teste:

laravel new project
cd project
php artisan test

Acesta ar trebui să fie rezultatul în consola dvs.:

Dacă ne uităm la predefinoaptea lui Laravel /tests, avem două fișiere:

teste/Feature/ExampleTest.php :

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

Nu trebuie să cunoașteți nicio sintaxă pentru a înțelege ce se întâmplă aici: încărcați pagina de pornire și verificați dacă codul de stare HTTP este "200 OK".

Cunoscut și ca numele metodei test_the_application_returns_a_successful_response() devine text lizibil atunci când vizualizați rezultatele testului, pur și simplu prin înlocuirea simbolului de subliniere cu un spațiu.

teste/Unitate/ExampleTest.php :

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

Pare puțin inutil să verifici dacă este adevărat? 

Vom vorbi în special despre testele unitare puțin mai târziu. Pentru moment, trebuie să înțelegeți ce se întâmplă în general în fiecare test.

  • Fiecare fișier de testare din folder /tests este o clasă PHP care extinde TestCase de PHPUnit
  • În cadrul fiecărei clase, puteți crea mai multe metode, de obicei o metodă pentru o situație de testat
  • În cadrul fiecărei metode există trei acțiuni: pregătirea situației, apoi acțiunea și apoi verificarea (afirmarea) dacă rezultatul este cel așteptat

Din punct de vedere structural, asta este tot ce trebuie să știți, totul depinde de lucrurile exacte pe care doriți să le testați.

Pentru a genera o clasă de test goală, pur și simplu rulați această comandă:

php artisan make:test HomepageTest

Fișierul este generat 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);
    }
}

Acum să vedem ce se întâmplă dacă un cod de test eșuează în Laravel

Să vedem acum ce se întâmplă dacă afirmațiile testului nu returnează rezultatul așteptat.

Să schimbăm exemplele de teste cu aceasta:

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

Și acum, dacă rulăm comanda php artisan test din nou:

 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

Există două teste eșuate, marcate ca FAIL, cu explicații mai jos și săgeți care indică linia exactă de teste care au eșuat. Erorile sunt indicate astfel.

Exemplu: Testarea codului formularului de înregistrare în Laravel

Să presupunem că avem un formular și trebuie să testăm diverse cazuri: verificăm dacă eșuează cu date invalide, verificăm dacă reușește cu introducerea corectă etc.

Trusa de pornire oficială de Laravel Breeze include i testarea funcționalității din cadrul acestuia. Să ne uităm la câteva exemple de acolo:

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

Aici avem două teste într-o clasă, deoarece ambele sunt legate de formularul de înregistrare: unul verifică dacă formularul este încărcat corect și altul verifică dacă trimiterea funcționează bine.

Să ne familiarizăm cu încă două metode de verificare a rezultatului, încă două afirmații: $this->assertAuthenticated()$response->assertRedirect(). Puteți verifica toate afirmațiile disponibile în documentația oficială a PHPUnit e Răspunsul Laravel . Rețineți că unele afirmații generale apar asupra subiectului $this, în timp ce alții verifică specificul $responsedin apelul de rută.

Un alt lucru important este use RefreshDatabase;declarație, cu trăsura, inserată deasupra clasei. Este necesar atunci când acțiunile de testare pot afecta baza de date, ca în acest exemplu, înregistrarea adaugă o nouă intrare în userstabelul bazei de date. Pentru aceasta, ar trebui să creați o bază de date de testare separată, cu care va fi actualizată php artisan migrate:freshde fiecare dată când se execută testele.

Aveți două opțiuni: creați fizic o bază de date separată sau utilizați o bază de date SQLite în memorie. Ambele sunt configurate în fișier phpunit.xmlfurnizate implicitdefinita cu Laravel. Mai exact, aveți nevoie de această piesă:

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

Vezi DB_CONNECTIONDB_DATABASEcare sunt comentate? Dacă aveți SQLite pe serverul dvs., cea mai simplă acțiune este pur și simplu să decomentați acele linii, iar testele dvs. vor rula pe baza de date din memorie.

În acest test spunem că utilizatorul este autentificat cu succes și redirecționat către pagina de pornire corectă, dar putem testa și datele reale din baza de date.

Pe lângă acest cod:

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

Putem folosi, de asemenea aserțiunile de testare a bazei de date si fa ceva de genul asta:

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

Exemplu de pagină de conectare

Să vedem acum un alt exemplu de pagină de conectare cu 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();
    }
}

Este vorba despre formularul de autentificare. Logica este similară cu înregistrarea, nu? Dar trei metode în loc de două, deci acesta este un exemplu de testare atât a scenariilor bune, cât și a celor rele. Deci, logica comună este că ar trebui să testați ambele cazuri: când lucrurile merg bine și când eșuează.

Buletin informativ de inovare
Nu rata cele mai importante știri despre inovație. Înscrieți-vă pentru a le primi pe e-mail.

De asemenea, ceea ce vedeți în acest test este utilizarea Fabrici de baze de date : Laravel creează un utilizator fals ( din nou, în baza de date de teste actualizată ) și apoi încearcă să se autentifice, cu acreditări corecte sau incorecte.

Încă o dată, Laravel generează pre-fabricadefinita cu date false pentru the Usermodel, în afara cutiei.

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

Vedeți, câte lucruri sunt pregătite chiar de Laravel, așa că ne-ar fi ușor să începem testarea?

Deci dacă executăm php artisan testdupă instalarea Laravel Breeze, ar trebui să vedem ceva de genul acesta:

 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

Teste funcționale în comparație cu teste unitare și altele

Ați văzut subfolderele tests/Feature e tests/Unit ?. 

Care este diferența dintre ele? 

La nivel global, în afara ecosistemului Laravel/PHP, există mai multe tipuri de testare automată. Puteți găsi termeni precum:

  • Teste unitare
  • Testarea caracteristicilor
  • Teste de integrare
  • Teste funcționale
  • Testare de la capăt la capăt
  • Teste de acceptare
  • Teste de fum
  • etc.

Sună complicat, iar diferențele reale dintre aceste tipuri de teste sunt uneori neclare. De aceea, Laravel a simplificat toți acești termeni confuzi și i-a grupat în doi: unitate/funcție.

Mai simplu spus, testele de caracteristici încearcă să execute funcționalitatea reală a aplicațiilor dvs.: obțineți adresa URL, apelați API-ul, imitați comportamentul exact, cum ar fi completarea formularului. Testele caracteristicilor efectuează de obicei aceleași operațiuni sau similare pe care le-ar face orice utilizator de proiect, manual, în viața reală.

Testele unitare au două semnificații. În general, puteți descoperi că orice test automat se numește „testare unitară” și întregul proces poate fi numit „testare unitară”. Dar în contextul funcționalității versus unitate, acest proces este despre testarea unei anumite unități de cod non-publice, în mod izolat. De exemplu, aveți o clasă Laravel cu o metodă care calculează ceva, cum ar fi prețul total al comenzii cu parametri. Prin urmare, testul unitar ar indica dacă sunt returnate rezultate corecte din acea metodă (unitatea de cod), cu parametri diferiți.

Pentru a genera un test unitar, trebuie să adăugați un indicator:

php artisan make:test OrderPriceTest --unit

Codul generat este același cu testul pre-unitățiidefiSistemul Laravel:

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

După cum puteți vedea, nu există RefreshDatabase, iar acesta este unul dintre deficele mai comune definiții de test unitar: nu atinge baza de date, funcționează ca o „cutie neagră”, izolată de aplicația care rulează.

Încercând să imit exemplul pe care l-am menționat mai devreme, să ne imaginăm că avem o clasă de servicii OrderPrice.

app/Services/OrderPriceService.php:

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

Apoi, testul unitar ar putea arăta cam așa:

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
}

Din experiența mea personală cu proiectele Laravel, marea majoritate a testelor sunt teste caracteristice, nu teste unitare. În primul rând, trebuie să testați dacă aplicația dvs. funcționează, așa cum o vor folosi oamenii reali.

În continuare, dacă ai calcule speciale sau logică, poți definire ca unitate, cu parametri, puteți crea teste unitare special pentru asta.

Uneori, scrierea testelor necesită modificarea codului în sine și refactorizarea lui pentru a-l face mai „testabil”: separarea unităților în clase sau metode speciale.

Când/cum se efectuează teste?

Care este utilizarea reală a acestui lucru php artisan test, când ar trebui să-l rulezi?

Există abordări diferite, în funcție de fluxul de lucru al companiei, dar, în general, trebuie să vă asigurați că toate testele sunt „verzi” (adică fără erori) înainte de a împinge modificările finale ale codului în depozit.

Apoi, lucrezi la nivel local la sarcina ta și, când crezi că ai terminat, rulează câteva teste pentru a te asigura că nu ai spart nimic. Amintiți-vă, codul dvs. poate provoca erori nu numai în logica dvs., ci și în mod neintenționat să distrugă un alt comportament în codul altcuiva scris cu mult timp în urmă.

Dacă facem un pas mai departe, este posibil să automatizăm MOLTE lucruri. Cu diverse instrumente CI/CD, puteți specifica teste pentru a rula ori de câte ori cineva împinge modificări la o anumită ramură Git sau înainte de a fuziona codul în ramura de producție. Cel mai simplu flux de lucru ar fi să folosești Github Actions, am un videoclip separat ceea ce o dovedește.

Ce ar trebui să testezi?

Există opinii diferite cu privire la cât de mare ar trebui să fie așa-numita „acoperire de testare”: încercați fiecare operațiune posibilă și caz pe fiecare pagină sau limitați munca la cele mai importante părți.

De fapt, aici sunt de acord cu oamenii care acuză testarea automată că iau mai mult timp decât oferă beneficii reale. Acest lucru se poate întâmpla dacă scrieți teste pentru fiecare detaliu. Acestea fiind spuse, proiectul dvs. poate solicita: întrebarea principală este „care este prețul unei potențiale erori”.

Cu alte cuvinte, trebuie să prioritizați eforturile de testare punând întrebarea „Ce s-ar întâmpla dacă acest cod nu ar fi eșuat?” Dacă sistemul dvs. de plată are erori, va avea un impact direct asupra afacerii. Deci, dacă funcționalitatea rolurilor/permisiunilor dvs. este întreruptă, aceasta este o problemă uriașă de securitate.

Îmi place cum a spus Matt Stauffer la o conferință: „Mai întâi trebuie să testați acele lucruri care, dacă eșuează, v-ar scoate de la locul de muncă.” Desigur, este o exagerare, dar ai înțeles ideea: încearcă mai întâi lucrurile importante. Și apoi alte funcții, dacă ai timp.

PEST: nouă alternativă la PHPUnit

Toate exemplele de mai sus se bazează pe instrumentul de pre-testare Laraveldefinoapte: PHPUnit . Dar de-a lungul anilor au apărut și alte instrumente în ecosistem și una dintre cele mai recente populare este DAUNATOR . Creat de angajatul oficial Laravel Nuno Maduro , își propune să simplifice sintaxa, făcând scrierea codului pentru teste și mai rapidă.

Sub capotă, merge su PHPUnit, ca un strat suplimentar, încercând doar să minimizeze unele părți pre-repetatedefisfârșitul codului PHPUnit.

Să ne uităm la un exemplu. Amintiți-vă de clasa de testare prefuncționalădefigăzduit în Laravel? iti voi aminti:

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

Știți cum ar arăta același test cu PEST?

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

Da, O linie de cod și atât. Deci, obiectivul PEST este de a elimina cheltuielile generale ale:

  • Crearea de clase și metode pentru orice;
  • Extensie caz de testare;
  • Punând acțiuni pe linii separate: în PEST le puteți lega împreună.

Pentru a genera un test PEST în Laravel, trebuie să specificați un semnal suplimentar:

php artisan make:test HomepageTest --pest

În momentul scrierii acestui articol, PEST este destul de popular în rândul dezvoltatorilor Laravel, dar este preferința ta personală dacă să folosești acest instrument suplimentar și să înveți sintaxa acestuia, precum și o notă PHPUnit.

BlogInnovazione.it

Buletin informativ de inovare
Nu rata cele mai importante știri despre inovație. Înscrieți-vă pentru a le primi pe e-mail.

Articole recente

Editorii și OpenAI semnează acorduri pentru a reglementa fluxul de informații procesate de Inteligența Artificială

Luni trecută, Financial Times a anunțat un acord cu OpenAI. FT își licențiază jurnalismul de clasă mondială...

Aprilie 30 2024

Plăți online: Iată cum serviciile de streaming vă fac să plătiți pentru totdeauna

Milioane de oameni plătesc pentru serviciile de streaming, plătind taxe lunare de abonament. Este o părere comună că tu...

Aprilie 29 2024

Veeam oferă cel mai complet suport pentru ransomware, de la protecție la răspuns și recuperare

Coveware de la Veeam va continua să ofere servicii de răspuns la incidente de extorcare cibernetică. Coveware va oferi capacități criminalistice și de remediere...

Aprilie 23 2024

Revoluția verde și digitală: cum întreținerea predictivă transformă industria petrolului și gazelor

Întreținerea predictivă revoluționează sectorul petrolului și gazelor, cu o abordare inovatoare și proactivă a managementului uzinelor...

Aprilie 22 2024