Články

Naučte se dělat testy v Laravelu na jednoduchých příkladech pomocí PHPUnit a PEST

Pokud jde o automatizované testy nebo testy jednotek, v jakémkoli programovacím jazyce existují dva protichůdné názory:

  • Ztráta času
  • Bez toho to nejde

Tímto článkem se tedy pokusíme přesvědčit prvně jmenované, zejména tím, že předvedeme, jak snadné je začít s automatizovaným testováním v Laravelu.

Nejprve si promluvme o „proč“ a poté se podívejme na několik příkladů jak.

Proč potřebujeme automatizované testování

Automatizované testy spouštějí části kódu a hlásí případné chyby. To je nejjednodušší způsob, jak je popsat. Představte si, že v aplikaci spustíte novou funkci a osobní robotický asistent by pak šel ručně otestovat novou funkci a zároveň by testoval, zda nový kód neporušil některou ze starých funkcí.

To je hlavní výhoda: automatické opětovné testování všech funkcí. Může se to zdát jako práce navíc, ale pokud neřeknete „robotovi“, aby to udělal, měli bychom to případně udělat ručně, že? 

Nebo by mohly být nové funkce uvolněny bez testování, zda fungují, v naději, že uživatelé budou hlásit chyby.

Automatizované testy nám mohou poskytnout několik výhod:

  • Ušetřete čas ručního testování;
  • Umožňují vám ušetřit čas jak na nové implementované funkci, tak na konsolidovaných funkcích tím, že se vyhnete regresi;
  • Vynásobte tuto výhodu všemi novými funkcemi a všemi již implementovanými funkcemi;
  • Předchozí tři body platí pro každou novou verzi;
  • ...

Zkuste si představit vaši aplikaci za rok nebo dva, s novými vývojáři v týmu, kteří neznají kód napsaný v předchozích letech, ani neznají, jak jej testovat. 

Naše první automatizované testy

K provedení prvního automatizované testování v Laravelu, nemusíte psát žádný kód. Ano, čtete správně. Vše je již nakonfigurováno a připraveno v předinstalacidefinite of Laravel, včetně úplně prvního základního příkladu.

Můžete zkusit nainstalovat projekt Laravel a okamžitě spustit první testy:

laravel new project
cd project
php artisan test

Toto by měl být výsledek ve vaší konzoli:

Když se podíváme na předdefimísto Laravelu /tests, máme dva soubory:

testy/Feature/ExampleTest.php :

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

Nepotřebujete znát žádnou syntaxi, abyste pochopili, co se zde děje: načtěte domovskou stránku a zkontrolujte, zda je stavový kód HTTP è "200 OK".

Také známý jako název metody test_the_application_returns_a_successful_response() se stane čitelným textem, když si prohlížíte výsledky testu, jednoduše nahrazením symbolu podtržení mezerou.

testy/Unit/ExampleTest.php :

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

Zdá se to trochu zbytečné, kontrolovat, zda je to pravda? 

Konkrétně o jednotkových testech si povíme o něco později. Prozatím musíte pochopit, co se obecně děje v každém testu.

  • Každý testovací soubor ve složce /tests je třída PHP, která rozšiřuje TestCase of PHPUnit
  • V rámci každé třídy můžete vytvořit více metod, obvykle jednu metodu pro testování situace
  • V rámci každé metody existují tři akce: příprava situace, následná akce a poté ověření (potvrzení), zda výsledek odpovídá očekávání.

Strukturálně je to vše, co potřebujete vědět, vše ostatní závisí na konkrétních věcech, které chcete testovat.

Chcete-li vygenerovat prázdnou testovací třídu, jednoduše spusťte tento příkaz:

php artisan make:test HomepageTest

Soubor je vygenerován 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);
    }
}

Nyní se podívejme, co se stane, když testovací kód selže v Laravelu

Nyní se podívejme, co se stane, když testovací tvrzení nevrátí očekávaný výsledek.

Změňme ukázkové testy na toto:

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

A teď, když spustíme příkaz php artisan test znovu:

 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

Existují dva neúspěšné testy, označené jako NEÚSPĚŠNÉ, s vysvětlením níže a šipkami ukazujícími na přesnou řadu testů, které selhaly. Chyby jsou indikovány tímto způsobem.

Příklad: Testování kódu registračního formuláře v Laravelu

Předpokládejme, že máme formulář a potřebujeme otestovat různé případy: zkontrolujeme, zda selže s neplatnými daty, zkontrolujeme, zda uspěje se správným zadáním atd.

Oficiální startovací sada od Laravel Breeze zahrnuje i testování funkčnosti v něm. Podívejme se na některé příklady odtud:

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

Zde máme dva testy v jedné třídě, protože oba souvisejí s registračním formulářem: jeden kontroluje, zda je formulář správně načten, a druhý kontroluje, zda odeslání funguje dobře.

Pojďme se seznámit s dalšími dvěma způsoby ověření výsledku, dalšími dvěma tvrzeními: $this->assertAuthenticated()$response->assertRedirect(). Všechna tvrzení dostupná v oficiální dokumentaci můžete zkontrolovat PHPUnit e Odpověď Laravel . Všimněte si, že na toto téma se vyskytují některá obecná tvrzení $this, zatímco ostatní kontrolují konkrétní $responsez hovoru na trase.

Další důležitou věcí je use RefreshDatabase;příkaz s tahem vloženým nad třídu. Je nezbytné, když testovací akce mohou ovlivnit databázi, jako v tomto příkladu, protokolování přidá novou položku do usersdatabázová tabulka. Za tímto účelem byste měli vytvořit samostatnou testovací databázi, která bude aktualizována php artisan migrate:freshpokaždé, když jsou testy spuštěny.

Máte dvě možnosti: fyzicky vytvořit samostatnou databázi nebo použít in-memory databázi SQLite. Obojí je nakonfigurováno v souboru phpunit.xmlve výchozím nastavenídefinita s laravel. Konkrétně potřebujete tuto část:

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

Viz DB_CONNECTIONDB_DATABASEkteré jsou komentované? Pokud máte na svém serveru SQLite, nejjednodušší akcí je jednoduše odkomentovat tyto řádky a vaše testy poběží proti této databázi v paměti.

V tomto testu říkáme, že uživatel je úspěšně autentizován a přesměrován na správnou domovskou stránku, ale také můžeme otestovat aktuální data v databázi.

Kromě tohoto kódu:

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

Můžeme také použít databázová testovací tvrzení a udělejte něco takového:

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

Příklad přihlašovací stránky

Podívejme se nyní na další příklad přihlašovací stránky s 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();
    }
}

Jde o přihlašovací formulář. Logika je podobná registraci, že? Ale tři metody místo dvou, takže toto je příklad testování dobrých i špatných scénářů. Společnou logikou tedy je, že byste měli otestovat oba případy: když věci jdou dobře, i když selžou.

Inovační zpravodaj
Nenechte si ujít nejdůležitější novinky o inovacích. Přihlaste se k jejich odběru e-mailem.

Také to, co vidíte v tomto testu, je použití Databázové továrny : Laravel vytváří falešného uživatele ( znovu ve vaší aktualizované testovací databázi ) a poté se pokusí přihlásit se správnými nebo nesprávnými přihlašovacími údaji.

Laravel opět generuje tovární predefinita s nepravdivými údaji pro Usermodel, mimo krabici.

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

Vidíte, kolik věcí připravuje samotný Laravel, takže by pro nás bylo snadné začít s testováním?

Pokud tedy provedeme php artisan testpo instalaci Laravel Breeze bychom měli vidět něco takového:

 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

Funkční testy ve srovnání s jednotkovými testy a dalšími

Viděli jste podsložky tests/Feature e tests/Unit ?. 

Jaký je mezi nimi rozdíl? 

Globálně, mimo ekosystém Laravel/PHP, existuje několik typů automatizovaného testování. Můžete najít výrazy jako:

  • Jednotkové testy
  • Testování funkcí
  • Integrační testy
  • Funkční testy
  • End-to-end testování
  • Přijímací zkoušky
  • Kouřové testy
  • atd.

Zní to složitě a skutečné rozdíly mezi těmito typy testů se někdy stírají. Laravel proto všechny tyto matoucí pojmy zjednodušil a seskupil je do dvou: jednotka/vlastnost.

Jednoduše řečeno, testy funkcí se snaží provést skutečnou funkčnost vašich aplikací: získat adresu URL, zavolat API, napodobit přesné chování, jako je vyplňování formuláře. Testy funkcí obvykle provádějí stejné nebo podobné operace, jaké by prováděl každý uživatel projektu ručně v reálném životě.

Jednotkové testy mají dva významy. Obecně můžete zjistit, že jakýkoli automatizovaný test se nazývá „testování jednotek“ a celý proces lze nazvat „testování jednotek“. Ale v kontextu funkčnosti versus jednotka je tento proces o testování konkrétní neveřejné jednotky kódu, izolovaně. Například máte třídu Laravel s metodou, která něco vypočítá, jako je celková cena objednávky s parametry. Test jednotky by tedy uvedl, zda jsou z této metody (kódové jednotky) vráceny správné výsledky s různými parametry.

Chcete-li vygenerovat test jednotky, musíte přidat příznak:

php artisan make:test OrderPriceTest --unit

Vygenerovaný kód je stejný jako test před jednotkoudefiLaravel systém:

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

Jak vidíte, neexistuje RefreshDatabase, a toto je jeden z definejběžnější definice unit testů: nedotýká se databáze, funguje jako „černá skříňka“, izolovaná od běžící aplikace.

Ve snaze napodobit příklad, který jsem zmínil dříve, si představme, že máme třídu služeb OrderPrice.

app/Services/OrderPriceService.php:

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

Pak by test jednotky mohl vypadat nějak takto:

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
}

Podle mých osobních zkušeností s projekty Laravel jsou naprostá většina testů testy funkcí, nikoli testy jednotek. Nejprve musíte otestovat, zda vaše aplikace funguje, jak by ji používali skuteční lidé.

Dále, pokud máte speciální výpočty nebo logiku, můžete definire jako jednotku, s parametry, můžete vytvořit testy jednotek speciálně pro to.

Někdy psaní testů vyžaduje úpravu samotného kódu a jeho refaktorizaci, aby byl „testovatelnější“: rozdělení jednotek do speciálních tříd nebo metod.

Kdy/jak provádět testy?

Jaké je skutečné využití tohoto php artisan test, kdy to máte spustit?

Existují různé přístupy v závislosti na vašem obchodním pracovním postupu, ale obecně se musíte ujistit, že všechny testy jsou „zelené“ (tj. bez chyb) před odesláním konečných změn kódu do úložiště.

Potom na svém úkolu pracujete lokálně, a když si myslíte, že jste hotovi, spusťte nějaké testy, abyste se ujistili, že jste nic neporušili. Pamatujte, že váš kód může způsobit chyby nejen ve vaší logice, ale také neúmyslně narušit některé jiné chování v kódu někoho jiného napsaného již dávno.

Pokud to uděláme o krok dále, je možné automatizovat mnoho věci. Pomocí různých nástrojů CI/CD můžete určit testy, které se mají spustit vždy, když někdo vloží změny do konkrétní větve Git nebo před sloučením kódu do produkční větve. Nejjednodušší pracovní postup by bylo použití Github Actions, mám samostatné video což dokazuje.

Co byste měli otestovat?

Existují různé názory na to, jak velké by mělo být takzvané „testovací pokrytí“: vyzkoušejte každou možnou operaci a případ na každé stránce nebo omezte práci na nejdůležitější části.

Ve skutečnosti zde souhlasím s lidmi, kteří obviňují automatizované testování z toho, že zabírá více času, než poskytuje skutečný přínos. To se může stát, pokud píšete testy pro každý jednotlivý detail. To znamená, že to může vyžadovat váš projekt: hlavní otázkou je „jaká je cena potenciální chyby“.

Jinými slovy, musíte upřednostnit své testovací úsilí položením otázky „Co by se stalo, kdyby tento kód selhal?“ Pokud má váš platební systém chyby, bude to mít přímý dopad na podnikání. Pokud je tedy funkčnost vašich rolí/oprávnění narušena, jedná se o velký bezpečnostní problém.

Líbí se mi, jak to řekl Matt Stauffer na konferenci: „Musíte nejprve otestovat ty věci, které, pokud selžou, vás vyhodí z práce.“ To je samozřejmě nadsázka, ale máte myšlenku: nejprve vyzkoušejte to důležité. A pak další funkce, pokud máte čas.

PEST: nová alternativa k PHPUnit

Všechny výše uvedené příklady jsou založeny na předběžném testovacím nástroji Laraveldefinoc: PHPUnit . Ale v průběhu let se v ekosystému objevily další nástroje a jedním z nejnovějších populárních je ŠKŮDCE . Vytvořeno oficiálním zaměstnancem Laravel Nuno Maduro , si klade za cíl zjednodušit syntaxi, čímž je psaní kódu pro testy ještě rychlejší.

Pod kapotou to běží su PHPUnit, jako další vrstva, se jen snaží minimalizovat některé předem opakované částidefičást kódu PHPUnit.

Podívejme se na příklad. Pamatujte na třídu předběžného testu funkcídefinited v Laravelu? Připomenu vám:

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

Víte, jak by vypadal stejný test s PEST?

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

Ano, JEDEN řádek kódu a je to. Cílem PEST je tedy odstranit režii:

  • Vytváření tříd a metod pro všechno;
  • Rozšíření testovacího případu;
  • Umístěním akcí na samostatné řádky: v PEST je můžete zřetězit dohromady.

Chcete-li vygenerovat test PEST v Laravelu, musíte zadat další příznak:

php artisan make:test HomepageTest --pest

V době psaní tohoto článku je PEST mezi vývojáři Laravel docela populární, ale je to vaše osobní preference, zda použijete tento další nástroj a naučíte se jeho syntaxi, stejně jako poznámku PHPUnit.

BlogInnovazione.it

Inovační zpravodaj
Nenechte si ujít nejdůležitější novinky o inovacích. Přihlaste se k jejich odběru e-mailem.

Nedávné články

Budoucnost je tady: Jak lodní průmysl revolucionizuje globální ekonomiku

Námořní sektor je skutečnou globální ekonomickou velmocí, která se dostala na 150miliardový trh...

1. května 2024

Vydavatelé a OpenAI podepisují dohody o regulaci toku informací zpracovávaných umělou inteligencí

Minulé pondělí Financial Times oznámily dohodu s OpenAI. FT licencuje svou prvotřídní žurnalistiku…

30. dubna 2024

Online platby: Zde je návod, jak vám streamovací služby umožňují platit navždy

Miliony lidí platí za streamovací služby a platí měsíční předplatné. Je obecný názor, že jste…

29. dubna 2024

Veeam nabízí nejkomplexnější podporu pro ransomware, od ochrany po reakci a obnovu

Společnost Coveware od společnosti Veeam bude i nadále poskytovat služby reakce na incidenty v oblasti kybernetického vydírání. Coveware nabídne forenzní a sanační schopnosti…

23. dubna 2024