Articles

Apreneu a fer proves a Laravel amb exemples senzills, utilitzant PHPUnit i PEST

Quan es tracta de proves automatitzades o proves unitàries, en qualsevol llenguatge de programació, hi ha dues opinions oposades:

  • Pèrdua de temps
  • No pots prescindir-ne

Així doncs, amb aquest article intentarem convèncer el primer, sobretot demostrant com de fàcil és començar amb les proves automatitzades a Laravel.

Primer parlem del "per què" i després veurem alguns exemples del com.

Per què necessitem proves automatitzades

Les proves automatitzades executen parts del codi i informen d'errors. Aquesta és la manera més senzilla de descriure'ls. Imagineu-vos desplegar una funció nova en una aplicació i, a continuació, un assistent de robot personal aniria a provar manualment la nova funció, alhora que provava si el codi nou no trencava cap de les funcions antigues.

Aquest és el principal avantatge: tornar a provar totes les funcions automàticament. Això pot semblar un treball addicional, però si no li dieu al "robot" que ho faci, hauríem de fer-ho alternativament manualment, oi? 

O es podrien llançar noves funcions sense provar si funcionen, amb l'esperança que els usuaris informin d'errors.

Les proves automatitzades ens poden donar diversos avantatges:

  • Estalvieu temps de prova manual;
  • Permeten estalviar temps tant en la nova funció implementada com en les funcions consolidades evitant la regressió;
  • Multipliqui aquest benefici per totes les funcions noves i totes les funcions ja implementades;
  • Els tres punts anteriors s'apliquen a cada versió nova;
  • ...

Intenta imaginar la teva aplicació d'aquí a un any o dos, amb nous desenvolupadors a l'equip que no coneixen el codi escrit en anys anteriors, ni tan sols com provar-lo. 

Les nostres primeres proves automatitzades

Per realitzar el primer proves automatitzades a Laravel, no cal escriure cap codi. Sí, ho has llegit bé. Ja està tot configurat i preparat a la preinstal·laciódefinit de Laravel, inclòs el primer exemple bàsic.

Podeu provar d'instal·lar un projecte Laravel i executar les primeres proves immediatament:

laravel new project
cd project
php artisan test

Aquest hauria de ser el resultat a la vostra consola:

Si fem una ullada al predefinit de Laravel /tests, tenim dos fitxers:

proves/Feature/ExampleTest.php:

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

No cal saber cap sintaxi per entendre què passa aquí: carregueu la pàgina d'inici i comproveu si el codi d'estat HTTP és "200 OK".

També conegut com el nom del mètode test_the_application_returns_a_successful_response() es converteix en text llegible quan visualitzeu els resultats de la prova, simplement substituint el símbol de subratllat per un espai.

proves/Unitat/ExampleTest.php:

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

Sembla una mica inútil, comprovant si això és cert? 

Parlarem específicament de les proves unitàries una mica més endavant. De moment, cal entendre què passa generalment a cada prova.

  • Cada fitxer de prova a la carpeta /tests és una classe PHP que amplia el TestCase de PHPUnit
  • Dins de cada classe, podeu crear diversos mètodes, normalment un mètode per provar una situació
  • Dins de cada mètode hi ha tres accions: preparació de la situació, després acció i després verificar (afirmar) si el resultat és l'esperat.

Estructuralment, això és tot el que necessiteu saber, tota la resta depèn de les coses exactes que vulgueu provar.

Per generar una classe de prova buida, només cal que executeu aquesta ordre:

php artisan make:test HomepageTest

Es genera el fitxer 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);
    }
}

Ara vegem què passa si falla un codi de prova a Laravel

Vegem ara què passa si les afirmacions de la prova no retornen el resultat esperat.

Canviem les proves d'exemple a aquesta:

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 ara, si executem l'ordre php artisan test de 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

Hi ha dues proves fallides, marcades com a FAIL, amb explicacions a continuació i fletxes que apunten a la línia exacta de proves que han fallat. Els errors s'indiquen d'aquesta manera.

Exemple: prova del codi del formulari de registre a Laravel

Suposem que tenim un formulari i hem de provar diversos casos: comprovem si falla amb dades no vàlides, comprovem si funciona amb l'entrada correcta, etc.

El kit d'inici oficial de Laravel Breeze inclou i provant la seva funcionalitat. Vegem-ne alguns exemples:

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

Aquí tenim dues proves en una classe, ja que totes dues estan relacionades amb el formulari d'inscripció: una comprova si el formulari s'ha carregat correctament i una altra comprova si l'enviament funciona bé.

Ens familiaritzem amb dos mètodes més per verificar el resultat, dues afirmacions més: $this->assertAuthenticated()$response->assertRedirect(). Podeu consultar totes les afirmacions disponibles a la documentació oficial de PHPUnit e Resposta de Laravel . Tingueu en compte que es produeixen algunes afirmacions generals sobre el tema $this, mentre que altres comproven l'específic $responsede la trucada de ruta.

Una altra cosa important és la use RefreshDatabase;enunciat, amb el traç, inserit a sobre de la classe. És necessari quan les accions de prova poden afectar la base de dades, com en aquest exemple, el registre afegeix una nova entrada al fitxer userstaula de base de dades. Per a això, hauríeu de crear una base de dades de prova independent amb la qual s'actualitzarà php artisan migrate:freshcada vegada que es fan les proves.

Teniu dues opcions: crear físicament una base de dades independent o utilitzar una base de dades SQLite a la memòria. Tots dos estan configurats al fitxer phpunit.xmlproporcionat per defectedefinita amb laravel. Concretament, necessiteu aquesta part:

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

Veure el DB_CONNECTIONDB_DATABASEquins es comenten? Si teniu SQLite al vostre servidor, l'acció més senzilla és simplement deixar de comentar aquestes línies i les vostres proves s'executaran amb aquesta base de dades a la memòria.

En aquesta prova diem que l'usuari s'ha autenticat correctament i es redirigeix ​​a la pàgina d'inici correcta, però també podem provar les dades reals a la base de dades.

A més d'aquest codi:

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

També podem utilitzar les afirmacions de prova de la base de dades i fes alguna cosa com això:

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

Exemple de la pàgina d'inici de sessió

Vegem ara un altre exemple de pàgina d'inici de sessió amb 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();
    }
}

Es tracta del formulari d'inici de sessió. La lògica és semblant al registre, oi? Però tres mètodes en lloc de dos, així que aquest és un exemple de provar escenaris bons i dolents. Per tant, la lògica comuna és que hauríeu de provar ambdós casos: quan les coses van bé i quan fallen.

Butlletí d'innovació
No et perdis les notícies més importants sobre innovació. Registra't per rebre'ls per correu electrònic.

A més, el que veieu en aquesta prova és l'ús de Fàbriques de bases de dades : Laravel crea un usuari fals ( de nou, a la vostra base de dades de proves actualitzada ) i després intenta iniciar sessió, amb les credencials correctes o incorrectes.

Un cop més, Laravel genera la fàbrica predefinita amb dades falses per al Usermodel, fora de la caixa.

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

Ja veus, quantes coses prepara el mateix Laravel, així que ens seria fàcil començar a provar?

Així que si executem php artisan testdesprés d'instal·lar Laravel Breeze, hauríem de veure alguna cosa com això:

 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

Proves funcionals comparades amb proves unitàries i altres

Heu vist les subcarpetes tests/Feature e tests/Unit ?. 

Quina diferència hi ha entre ells? 

A nivell mundial, fora de l'ecosistema Laravel/PHP, hi ha diversos tipus de proves automatitzades. Podeu trobar termes com:

  • Proves unitàries
  • Prova de funcions
  • Proves d'integració
  • Proves funcionals
  • Proves d'extrem a extrem
  • Proves d'acceptació
  • Proves de fum
  • etc.

Sembla complicat, i les diferències reals entre aquests tipus de proves de vegades es veuen borroses. És per això que Laravel ha simplificat tots aquests termes confusos i els ha agrupat en dos: unitat/funció.

En poques paraules, les proves de funcions intenten executar la funcionalitat real de les vostres aplicacions: obteniu l'URL, truqueu a l'API, imiteu el comportament exacte com ara omplir el formulari. Les proves de característiques solen realitzar les mateixes operacions o similars que qualsevol usuari del projecte faria, manualment, a la vida real.

Les proves unitàries tenen dos significats. En general, podeu trobar que qualsevol prova automatitzada s'anomena "prova d'unitat" i tot el procés es pot anomenar "prova d'unitat". Però en el context de la funcionalitat versus la unitat, aquest procés tracta de provar una unitat de codi no pública específica, de manera aïllada. Per exemple, teniu una classe Laravel amb un mètode que calcula alguna cosa, com ara el preu total de la comanda amb paràmetres. Per tant, la prova d'unitat indicaria si es retornen resultats correctes d'aquest mètode (unitat de codi), amb diferents paràmetres.

Per generar una prova d'unitat, cal afegir una marca:

php artisan make:test OrderPriceTest --unit

El codi generat és el mateix que la prova prèvia a la unitatdefiSistema Laravel:

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

Com podeu veure, no existeix RefreshDatabase, i aquest n'és un defiles definicions més habituals de la prova d'unitat: no toca la base de dades, funciona com una “caixa negra”, aïllada de l'aplicació en execució.

Intentant imitar l'exemple que he esmentat anteriorment, imaginem que tenim una classe de servei OrderPrice.

app/Services/OrderPriceService.php:

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

Aleshores, la prova d'unitat podria semblar a això:

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
}

Segons la meva experiència personal amb projectes de Laravel, la gran majoria de proves són proves de característiques, no proves d'unitat. Primer, heu de provar si la vostra aplicació funciona, de la manera com l'utilitzarien persones reals.

A continuació, si teniu càlculs o lògica especials, podeu fer-ho definire com a unitat, amb paràmetres, podeu crear proves unitàries específicament per a això.

De vegades, escriure proves requereix modificar el propi codi i refactoritzar-lo per fer-lo més "comprovable": separant les unitats en classes o mètodes especials.

Quan/com fer les proves?

Quin és l'ús real d'això php artisan test, quan l'has d'executar?

Hi ha diferents enfocaments, depenent del vostre flux de treball empresarial, però, en general, heu d'assegurar-vos que totes les proves siguin "verdes" (és a dir, sense errors) abans d'enviar els canvis finals al codi al repositori.

A continuació, treballeu localment en la vostra tasca i, quan cregueu que heu acabat, feu algunes proves per assegurar-vos que no heu trencat res. Recordeu que el vostre codi pot causar errors no només a la vostra lògica, sinó que també trencar sense voler algun altre comportament en el codi d'una altra persona escrit fa molt de temps.

Si fem un pas més enllà, és possible automatitzar molt coses. Amb diverses eines de CI/CD, podeu especificar proves per executar-se sempre que algú faci canvis a una branca de Git específica o abans de combinar el codi a la branca de producció. El flux de treball més senzill seria utilitzar Github Actions, tinc un vídeo a part que ho demostra.

Què hauries de provar?

Hi ha diferents opinions sobre l'extensió que hauria de ser l'anomenada "cobertura de prova": proveu totes les operacions i casos possibles a cada pàgina, o limiteu el treball a les parts més importants.

De fet, aquí és on estic d'acord amb les persones que acusen que les proves automatitzades triguen més temps que oferir un benefici real. Això pot passar si escriviu proves per a cada detall. Dit això, el vostre projecte pot ser requerit: la pregunta principal és "quin és el preu de l'error potencial".

En altres paraules, heu de prioritzar els vostres esforços de prova fent la pregunta "Què passaria si aquest codi fallés?" Si el vostre sistema de pagament té errors, afectarà directament el negoci. Per tant, si la funcionalitat dels vostres rols/permisos està trencada, aquest és un gran problema de seguretat.

M'agrada com ho va dir Matt Stauffer en una conferència: "Primer has de provar aquelles coses que, si fallen, t'acomiadarien de la teva feina". Per descomptat, això és una exageració, però enteneu la idea: proveu primer les coses importants. I després altres característiques, si teniu temps.

PEST: nova alternativa a PHPUnit

Tots els exemples anteriors es basen en l'eina de prova prèvia de Laraveldefinit: PHPUnit . Però amb els anys han aparegut altres eines a l'ecosistema i una de les últimes populars és PLAGA . Creat per un empleat oficial de Laravel Nuno Maduro , pretén simplificar la sintaxi, fent que l'escriptura de codi per a proves sigui encara més ràpida.

Sota el capó, corre su PHPUnit, com a capa addicional, només intenta minimitzar algunes parts repetides prèviamentdefinit del codi PHPUnit.

Vegem-ne un exemple. Recordeu la classe de proves prèviesdefinit a Laravel? Et recordaré:

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

Saps com seria la mateixa prova amb PEST?

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

Sí, UNA línia de codi i ja està. Per tant, l'objectiu de PEST és eliminar les despeses generals de:

  • Creant classes i mètodes per a tot;
  • Extensió del cas de prova;
  • Posant accions en línies separades: a PEST podeu encadenar-les.

Per generar una prova PEST a Laravel, heu d'especificar una marca addicional:

php artisan make:test HomepageTest --pest

En el moment d'escriure aquest article, PEST és força popular entre els desenvolupadors de Laravel, però és la vostra preferència personal si voleu utilitzar aquesta eina addicional i aprendre la seva sintaxi, així com una nota de PHPUnit.

BlogInnovazione.it

Butlletí d'innovació
No et perdis les notícies més importants sobre innovació. Registra't per rebre'ls per correu electrònic.

Articles recents

Els avantatges de les pàgines per pintar per a nens: un món de màgia per a totes les edats

El desenvolupament de la motricitat fina a través del color prepara els nens per a habilitats més complexes com escriure. Per acolorir...

2 maig 2024

El futur és aquí: com la indústria naviliera està revolucionant l'economia global

El sector naval és una veritable potència econòmica mundial, que ha navegat cap a un mercat de 150 milions...

1 maig 2024

Els editors i OpenAI signen acords per regular el flux d'informació processada per la Intel·ligència Artificial

Dilluns passat, el Financial Times va anunciar un acord amb OpenAI. FT autoritza el seu periodisme de classe mundial...

30 2024 abril

Pagaments en línia: aquí teniu com els serveis de streaming us fan pagar per sempre

Milions de persones paguen per serveis de streaming, pagant quotes de subscripció mensuals. És l'opinió comuna que tu...

29 2024 abril