Artikoloj

Lernu kiel fari testojn en Laravel per simplaj ekzemploj, uzante PHPUnit kaj PEST

Kiam temas pri aŭtomatigitaj testoj aŭ unuopaj testoj, en iu ajn programlingvo, ekzistas du kontraŭaj opinioj:

  • Tempoperdo
  • Vi ne povas fari sen ĝi

Do, per ĉi tiu artikolo ni provos konvinki la unuan, precipe montrante kiom facile estas komenci kun aŭtomata testado en Laravel.

Unue ni parolu pri la "kial", kaj poste ni vidu kelkajn ekzemplojn de la kiel.

Kial ni bezonas aŭtomatan testadon

Aŭtomatigitaj testoj prizorgas partojn de la kodo kaj raportas ajnajn erarojn. Tio estas la plej simpla maniero priskribi ilin. Imagu lanĉi novan funkcion en aplikaĵo, kaj tiam persona robota asistanto irus kaj mane testis la novan funkcion, dum ankaŭ provas ĉu la nova kodo ne rompis iujn el la malnovaj funkcioj.

Jen la ĉefa avantaĝo: retesti ĉiujn funkciojn aŭtomate. Ĉi tio povus ŝajni kiel ekstra laboro, sed se vi ne diras al la "roboto" fari ĝin, ni devus alternative fari ĝin permane, ĉu ne? 

Aŭ novaj funkcioj povus esti liberigitaj sen provi ĉu ili funkcias, esperante ke uzantoj raportos cimojn.

Aŭtomatigitaj provoj povas doni al ni plurajn avantaĝojn:

  • Ŝparu manan testan tempon;
  • Ili permesas ŝpari tempon kaj pri la nova funkcio efektivigita kaj pri la solidigitaj funkcioj evitante regreson;
  • Multipliku ĉi tiun profiton per ĉiuj novaj funkcioj kaj ĉiuj funkcioj jam efektivigitaj;
  • La antaŭaj tri punktoj validas por ĉiu nova versio;
  • ...

Provu imagi vian aplikaĵon post unu aŭ du jaroj, kun novaj programistoj en la teamo, kiuj ne konas la kodon skribitan en antaŭaj jaroj, aŭ eĉ kiel testi ĝin. 

Niaj unuaj aŭtomatigitaj provoj

Por plenumi la unuan aŭtomatigita testado en Laravel, vi ne bezonas skribi ajnan kodon. Jes, vi legis ĝuste. Ĉio jam estas agordita kaj preta en la antaŭinstalodefinoto de Laravel, inkluzive de la plej unua baza ekzemplo.

Vi povas provi instali Laravel-projekton kaj fari la unuajn provojn tuj:

laravel new project
cd project
php artisan test

Ĉi tio devus esti la rezulto en via konzolo:

Se ni rigardu la predefinokto de Laravel /tests, ni havas du dosierojn:

tests/Feature/ExampleTest.php :

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

Vi ne bezonas scii ajnan sintakson por kompreni kio okazas ĉi tie: ŝarĝu la ĉefpaĝon kaj kontrolu ĉu la statuskodo HTTP è "200 OK".

Ankaŭ konata kiel la metodonomo test_the_application_returns_a_successful_response() fariĝas legebla teksto kiam vi rigardas la testrezultojn, simple anstataŭigante la substrekan simbolon per spaco.

tests/Unuo/ExampleTest.php :

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

Ŝajnas iom sencela, kontroli ĉu tio estas vera? 

Ni parolos specife pri unuotestoj iom poste. Nuntempe, vi devas kompreni, kio ĝenerale okazas en ĉiu testo.

  • Ĉiu prova dosiero en la dosierujo /tests estas PHP-klaso kiu etendas la TestCase de PHPUnit
  • Ene de ĉiu klaso, vi povas krei plurajn metodojn, kutime unu metodon por provi situacion
  • Ene de ĉiu metodo estas tri agoj: preparado de la situacio, tiam agado kaj poste kontroli (aserti) ĉu la rezulto estas kiel atendita.

Strukture, tio estas ĉio, kion vi bezonas scii, ĉio alia dependas de la precizaj aferoj, kiujn vi volas testi.

Por generi malplenan testklason, simple rulu ĉi tiun komandon:

php artisan make:test HomepageTest

La dosiero estas generita 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);
    }
}

Nun ni vidu, kio okazas se prova kodo malsukcesas en Laravel

Ni nun vidu, kio okazas se la testaj asertoj ne redonas la atendatan rezulton.

Ni ŝanĝu la ekzemplotestojn al ĉi tio:

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

Kaj nun, se ni rulas la komandon php artisan test denove:

 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

Estas du malsukcesaj testoj, markitaj kiel FAIL, kun klarigoj malsupre kaj sagoj montrantaj al la preciza linio de testoj malsukcesintaj. Eraroj estas indikitaj tiel.

Ekzemplo: Testado de aliĝformularo kodo en Laravel

Supozu, ke ni havas formon kaj ni devas testi diversajn kazojn: ni kontrolas ĉu ĝi malsukcesas kun nevalidaj datumoj, ni kontrolas ĉu ĝi sukcesas per la ĝusta enigo ktp.

La oficiala startkompleto de Laravel Breeze inkluzivas i provante la funkciojn ene de ĝi. Ni rigardu kelkajn ekzemplojn de tie:

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

Ĉi tie ni havas du testojn en unu klaso, ĉar ili ambaŭ rilatas al la aliĝilo: unu kontrolas ĉu la formularo estas ĝuste ŝargita kaj alia kontrolas ĉu la sendado funkcias bone.

Ni konatiĝu kun du pliaj metodoj por kontroli la rezulton, du pliajn asertojn: $this->assertAuthenticated()$response->assertRedirect(). Vi povas kontroli ĉiujn disponeblajn asertojn en la oficiala dokumentado de PHPUnit e LaravelResponse . Rimarku, ke iuj ĝeneralaj asertoj okazas pri la temo $this, dum aliaj kontrolas la specifan $responsede la itinervoko.

Alia grava afero estas la use RefreshDatabase;deklaro, kun la streko, enigita super la klaso. Estas necese kiam testaj agoj povas influi la datumbazon, kiel en ĉi tiu ekzemplo, protokolo aldonas novan eniron en la usersdatumbaza tablo. Por tio, vi devus krei apartan testan datumbazon kun kiu estos ĝisdatigita php artisan migrate:freshĉiufoje kiam la testoj estas rulitaj.

Vi havas du eblojn: fizike krei apartan datumbazon aŭ uzi en-memoran SQLite-datumbazon. Ambaŭ estas agorditaj en la dosiero phpunit.xmlprovizita defaŭltedefinita kun Laravel. Specife, vi bezonas ĉi tiun parton:

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

Vidu la DB_CONNECTIONDB_DATABASEkiuj estas komentataj? Se vi havas SQLite sur via servilo, la plej simpla ago estas simple malkomenti tiujn liniojn kaj viaj testoj funkcios kontraŭ tiu en-memora datumbazo.

En ĉi tiu testo ni diras, ke la uzanto estas sukcese aŭtentikigita kaj alidirektita al la ĝusta hejmpaĝo, sed ni ankaŭ povas testi la realajn datumojn en la datumbazo.

Krom ĉi tiu kodo:

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

Ni ankaŭ povas uzi la datumbazaj testaj asertoj kaj faru ion tian:

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

Ekzemplo de la paĝo Ensalutu

Ni nun vidu alian ekzemplon de Ensaluta paĝo kun 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();
    }
}

Temas pri la ensaluta formularo. La logiko similas al registriĝo, ĉu ne? Sed tri metodoj anstataŭ du, do ĉi tio estas ekzemplo de testado de ambaŭ bonaj kaj malbonaj scenaroj. Do, la komuna logiko estas, ke vi devus testi ambaŭ kazojn: kiam aferoj iras bone kaj kiam ili malsukcesas.

Informilo pri novigo
Ne maltrafu la plej gravajn novaĵojn pri novigado. Registriĝi por ricevi ilin retpoŝte.

Ankaŭ tio, kion vi vidas en ĉi tiu testo, estas la uzado de Datumbazaj Fabrikoj : Laravel kreas falsan uzanton ( denove, sur via ĝisdatigita testa datumbazo ) kaj poste provas ensaluti, kun ĝustaj aŭ malĝustaj akreditaĵoj.

Denove, Laravel generas la fabrikon predefinita kun falsaj datumoj por la Usermodelo, ekster la skatolo.

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

Vi vidas, kiom da aferoj estas preparitaj de Laravel mem, do ĉu estus facile por ni komenci testi?

Do se ni ekzekutas php artisan testpost instalo de Laravel Breeze, ni devus vidi ion tian:

 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

Funkciaj testoj kompare kun unuotestoj kaj aliaj

Vi vidis la subdosierujojn tests/Feature e tests/Unit ?. 

Kio estas la diferenco inter ili? 

Tutmonde, ekster la Laravel/PHP-ekosistemo, ekzistas pluraj specoj de aŭtomataj testadoj. Vi povas trovi terminojn kiel:

  • Unuaj provoj
  • Trajtotestado
  • Testoj de integriĝo
  • Funkciaj provoj
  • Fin-al-fina testado
  • Akceptotestoj
  • Fumaj provoj
  • ktp.

Ĝi sonas komplika, kaj la realaj diferencoj inter ĉi tiuj specoj de testoj foje estas malklaraj. Tial Laravel simpligis ĉiujn ĉi tiujn konfuzajn terminojn kaj grupigis ilin en du: unuo/trajto.

Simple dirite, ĉeftestoj provas efektivigi la realan funkciecon de viaj aplikoj: akiri la URL, voki la API, imiti la ĝustan konduton kiel plenigi la formularon. Trajtaj testoj kutime plenumas la samajn aŭ similajn operaciojn kiel iu ajn projekt-uzanto farus, permane, en la reala vivo.

Unuaj testoj havas du signifojn. Ĝenerale, vi eble trovos, ke iu ajn aŭtomatigita testo nomiĝas "unua testado" kaj la tuta procezo povas esti nomata "unua provo". Sed en la kunteksto de funkcieco kontraŭ unuo, ĉi tiu procezo temas pri testado de specifa nepublika unuo de kodo, izole. Ekzemple, vi havas Laravel-klason kun metodo, kiu kalkulas ion, kiel la totala ordoprezo kun parametroj. Tial, la unutesto deklarus ĉu ĝustaj rezultoj estas resenditaj de tiu metodo (kodunuo), kun malsamaj parametroj.

Por generi unuteston, vi devas aldoni flagon:

php artisan make:test OrderPriceTest --unit

La generita kodo estas la sama kiel la antaŭunua testodefiLaravel-sistemo:

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

Kiel vi povas vidi, ĝi ne ekzistas RefreshDatabase, kaj ĉi tiu estas unu el defiplej oftaj unutestaj difinoj: ĝi ne tuŝas la datumbazon, ĝi funkcias kiel "nigra skatolo", izolita de la ruliĝanta aplikaĵo.

Provante imiti la ekzemplon, kiun mi menciis pli frue, ni imagu, ke ni havas servoklason OrderPrice.

app/Services/OrderPriceService.php:

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

Tiam, la unutesto povus aspekti kiel ĉi tio:

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
}

Laŭ mia persona sperto kun Laravel-projektoj, la vasta plimulto de testoj estas Trajtaj testoj, ne Unuaj testoj. Unue, vi devas provi ĉu via aplikaĵo funkcias, kiel realaj homoj uzus ĝin.

Poste, se vi havas specialajn kalkulojn aŭ logikon vi povas definire kiel unuo, kun parametroj, vi povas krei unuotestojn specife por tio.

Foje, skribi testojn postulas modifi la kodon mem kaj refaktorigi ĝin por igi ĝin pli "testebla": apartigi la unuojn en specialajn klasojn aŭ metodojn.

Kiam/kiel fari provojn?

Kio estas la reala uzo de ĉi tio php artisan test, kiam vi devus funkcii ĝin?

Estas malsamaj aliroj, depende de via komerca laborfluo, sed ĝenerale vi devas certigi, ke ĉiuj testoj estas "verdaj" (t.e. seneraraj) antaŭ puŝi la finajn kodŝanĝojn al la deponejo.

Poste, vi laboras loke pri via tasko, kaj kiam vi pensas, ke vi finis, faru kelkajn provojn por certigi, ke vi ne rompis ion ajn. Memoru, ke via kodo povas kaŭzi cimojn ne nur en via propra logiko, sed ankaŭ neintence rompi iun alian konduton en la kodo de aliulo skribita antaŭ longe.

Se ni faras ĝin paŝon pli, eblas aŭtomatigi multaj aferojn. Per diversaj CI/KD-iloj, vi povas specifi testojn por ruliĝi kiam ajn iu puŝas ŝanĝojn al specifa Git-filio aŭ antaŭ kunfandi kodon en la produktadbranĉon. La plej simpla laborfluo estus uzi Github-Agojn, mi havas aparta filmeto kiu pruvas ĝin.

Kion vi devus testi?

Estas malsamaj opinioj pri kiom granda devus esti la tiel nomata "testkovrado": provu ĉiun ebla operacio kaj kazo sur ĉiu paĝo, aŭ limigu la laboron al la plej gravaj partoj.

Fakte, ĉi tie mi konsentas kun homoj, kiuj akuzas aŭtomatan testadon preni pli da tempo ol havigi realan profiton. Ĉi tio povas okazi se vi skribas testojn por ĉiu detalo. Dirite, ĝi povas esti postulata de via projekto: la ĉefa demando estas "kio estas la prezo de ebla eraro".

Alivorte, vi devas prioritati viajn testajn klopodojn demandante la demandon "Kio okazus se ĉi tiu kodo malsukcesus?" Se via pagsistemo havas cimojn, ĝi rekte influos la komercon. Do se la funkcieco de viaj roloj/permesoj estas rompita, ĉi tio estas grandega sekureca problemo.

Mi ŝatas kiel Matt Stauffer diris tion en konferenco: "Vi unue devas testi tiujn aferojn, kiuj, se ili malsukcesos, maldungus vin de via laboro." Kompreneble tio estas troigo, sed vi ricevas la ideon: provu unue la gravajn aferojn. Kaj poste aliaj funkcioj, se vi havas tempon.

PEST: nova alternativo al PHPUnit

Ĉiuj ĉi-supraj ekzemploj baziĝas sur Laravel antaŭtesta ilodefinokto: PHPUnit . Sed tra la jaroj aliaj iloj aperis en la ekosistemo kaj unu el la plej novaj popularaj estas PLAGO . Kreite de oficiala Laravel-dungito Nuno Maduro , celas simpligi la sintakson, igante skribkodon por testoj eĉ pli rapide.

Sub la kapuĉo, ĝi kuras su PHPUnit, kiel aldona tavolo, nur provas minimumigi kelkajn antaŭ-ripetajn partojndefifino de la PHPUnit-kodo.

Ni rigardu ekzemplon. Memoru la antaŭfunkcian testoklasondefiniita en Laravel? Mi memorigos al vi:

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

Ĉu vi scias, kiel aspektus la sama testo kun PEST?

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

Jes, UNU linio de kodo kaj jen ĝi. Do, la celo de PEST estas forigi la supran koston de:

  • Krei klasojn kaj metodojn por ĉio;
  • Testkazo etendo;
  • Metante agojn sur apartajn liniojn: en PEST vi povas ĉeni ilin kune.

Por generi PEST-teston en Laravel, vi devas specifi plian flagon:

php artisan make:test HomepageTest --pest

De ĉi tiu skribo, PEST estas sufiĉe populara inter Laravel-programistoj, sed estas via persona prefero ĉu uzi ĉi tiun aldonan ilon kaj lerni ĝian sintakson, same kiel PHPUnit-noton.

BlogInnovazione.it

Informilo pri novigo
Ne maltrafu la plej gravajn novaĵojn pri novigado. Registriĝi por ricevi ilin retpoŝte.

Lastaj artikoloj

Eldonistoj kaj OpenAI subskribas interkonsentojn por reguligi la fluon de informoj prilaboritaj de Artefarita Inteligenteco

Pasintlunde, la Financial Times anoncis interkonsenton kun OpenAI. FT licencas sian mondklasan ĵurnalismon...

30 aprilo 2024

Interretaj Pagoj: Jen Kiel Fluaj Servoj Faras Vin Pagi Eterne

Milionoj da homoj pagas por streaming-servoj, pagante monatajn abonkotizojn. Estas komuna opinio, ke vi...

29 aprilo 2024

Veeam havas la plej ampleksan subtenon por ransomware, de protekto ĝis respondo kaj reakiro

Coveware de Veeam daŭre liveros servojn de respondaj incidentoj pri ciberĉantaĝo. Coveware ofertos krimmedicinajn kaj solvajn kapablojn...

23 aprilo 2024

Verda kaj Cifereca Revolucio: Kiel Prognoza Prizorgado Transformas la Petrolo kaj Gasa Industrio

Prognoza prizorgado revolucias la petrolon kaj gasan sektoron, kun noviga kaj iniciatema aliro al plantadministrado...

22 aprilo 2024