• Czas czytania ~10 min
  • 27.03.2023

Częstym wyzwaniem w testowaniu jest nie tylko JAK coś przetestować, ale CO można przetestować. Dlatego zrobiłem listę wszystkich rzeczy, które lubię testować w moich aplikacjach.

Wszystkie przykłady testów koncentrują się na koncepcjach testowania i mogą być stosowane do wszystkich struktur testowych. Moje przykłady są napisane za pomocą PEST.

Szukasz czegoś konkretnego? Wybierz to:

Nuta: Wszystkie przykłady można również znaleźć w dedykowanym repozytorium.

Testowanie statusu

odpowiedzi strony Testowanie odpowiedzi strony jest jednym z najprostszych testów do napisania; mimo to jest niezwykle przydatne.

Upewnia się, że strona odpowiada poprawnym kodem stanu HTTP, głównie odpowiedzią 200.

it('gives back a successful response for home page', function () {
    $this->get('/')->assertOk();
});

To prosty test, ale wiedza o tym, że strona główna nie zgłasza błędu, ma kluczowe znaczenie. Tak więc, jeśli chcesz napisać swój pierwszy test, to jest ten, od którego należy zacząć.

Odniesienie: Dowiedz się więcej o testowaniu odpowiedzi na strony w oficjalnych dokumentach.

Testowanie tekstu

odpowiedzi strony Ten test jest podobny do testu odpowiedzi na pierwszą stronę. Testujemy również odpowiedź, ale tym razem interesuje nas treść odpowiedzi.

it('lists products', function () {
    
    $firstProduct = Product::factory()->create();
    $secondProduct = Product::factory()->create();

    
    $this->get('/')
        ->assertOk()
        ->assertSeeTextInOrder([
            $firstProduct->title,
            $secondProduct->title,
        ]);
});

Tutaj upewniamy się, że widzimy tytuły naszych produktów na stronie głównej. Jest to przydatne, jeśli ładujesz produkty z bazy danych i upewniasz się, że są wyświetlane.

Tutaj również możesz być bardziej szczegółowy, na przykład gdy chcesz tylko pokazać released products.

it('lists released products', function () {
    
    $releasedProduct = Product::factory()
        ->released()
        ->create();
        
    $draftProduct = Product::factory()
        ->create();

    
    $this->get('/')
        ->assertOk()
        ->assertSeeText($releasedProduct->title)
        ->assertDontSeeText($draftProduct->title);
});

Pokazuje również, jak przetestować coś, co nie jest wyświetlane, co również może być pomocne. Ten test nie byłby tak pomocny, gdybyśmy mieli tylko statyczny tekst na stronie głównej.

Odniesienie: Dowiedz się więcej o testowaniu odpowiedzi na strony w oficjalnych dokumentach.

Testowanie widoku

odpowiedzi strony Oprócz testowania stanu odpowiedzi i zawartości można również przetestować zwrócony widok.

it('returns correct view', function() {
    
    $this->get('/')
        ->assertOk()
        ->assertViewIs('home');
});

Możesz pójść jeszcze dalej i przetestować dane przekazywane do widoku.

it('returns correct view', function() {
    
    $this->get('/')
        ->assertOk()
        ->assertViewIs('home')
        ->assertViewHas('products');
});

Odniesienie: Dowiedz się więcej o testowaniu odpowiedzi na strony w oficjalnych dokumentach.

Testowanie JSON odpowiedzi strony Często chcesz zwrócić dane JSON

z interfejsu API. Tutaj możesz użyć pomocników JSON Laravela, takich jak metoda assertJson .

it('returns all products as JSON', function () {
    
    $product = Product::factory()->create();
    $anotherProduct = Product::factory()->create();

    
    $this->post('api/products')
        ->assertOk()
        ->assertJson([
            [
                'title' => $product->title,
                'description' => $product->description,
            ],
            [
                'title' => $anotherProduct->title,
                'description' => $anotherProduct->description,
            ],
        ]);
});

Odniesienie: Dowiedz się więcej o testowaniu odpowiedzi na strony w oficjalnych dokumentach.

Testowanie z bazą danych Ponieważ przechowujemy dane w bazie danych

, chcemy mieć pewność, że dane są poprawnie przechowywane. W tym miejscu Laravel może ci pomóc z kilkoma przydatnymi pomocnikami asertywnymi.

it('stores a product', function () {
    
    $this->actingAs(User::factory()->create())
        ->post('product', [
        'title' => 'Product name',
        'description' => 'Product description',
    ])->assertSuccessful();

    
    $this->assertDatabaseCount(Product::class, 1);
    $this->assertDatabaseHas(Product::class, [
        'title' => 'Product name',
        'description' => 'Product description',
    ]);
});

Przykład zapewnia, że produkt jest tworzony i przechowywany w bazie danych dla naszej trasy pocztowej.

Odniesienie: Znajdź wszystkie potwierdzenia bazy danych w oficjalnej dokumentacji.

Testowanie Walidacja Walidacja

jest kluczową częścią wielu aplikacji. Chcesz mieć pewność, że można przesyłać tylko prawidłowe dane. Domyślnie Laravel wysyła błędy walidacji z powrotem do użytkownika, co możemy sprawdzić za pomocą assertInvalid metody.

it('requires the title', function () {
    
    $this->actingAs(User::factory()->create())
        ->post('product', [
            'description' => 'Product description',
        ])->assertInvalid(['title' => 'required']);
});

it('requires the description', function () {
    
    $this->actingAs(User::factory()->create())
        ->post('product', [
            'title' => 'Product name',
        ])->assertInvalid(['description' => 'required']);
});

W przypadku wielu reguł sprawdzania poprawności korzystanie z zestawów danych może być bardzo pomocne. To może bardzo oczyścić twoje testy.

it('requires title and description tested with a dataset', function($data, $error) {
    
    $this->actingAs(User::factory()->create())
        ->post('product', $data)->assertInvalid($error);
})->with([
    'title required' => [['description' => 'text'], ['title' => 'required']],
    'description required' => [['title' => 'Title'], ['description' => 'required']],
]);

Odniesienie: Dowiedz się więcej o potwierdzeniach sprawdzania poprawności w oficjalnych dokumentach.

Testowanie modeli / relacji

Po pierwsze, lubię testować każdą relację modelu. Mówiąc dokładniej, chcemy przetestować funkcjonalność związku; not to właśnie robi Laravel. Chcemy mieć pewność, że relacje są zdefiniowane.

it('has products', function () {
    
    $user = User::factory()
        ->has(Product::factory())
        ->create();

    
    $products = $user->products;

    
    expect($products)
        ->toBeInstanceOf(Collection::class)
        ->first()->toBeInstanceOf(Product::class);
});

Programiści mają różne opinie na temat tego, jaka logika powinna być obsługiwana w modelu i to jest w porządku. Ale jeśli go dodasz, pamiętaj również, aby go przetestować.

it('only returns released courses for query scope', function () {
    
    Course::factory()->released()->create();
    Course::factory()->create();

    
    expect(Course::released()->get())
        ->toHaveCount(1)
        ->first()->id->toEqual(1);
});

Innym przykładem może być model akcesora taki jak:

protected function firstName(): Attribute
    {
        return Attribute::make(
            get: fn (string $value) => ucfirst($value),
        );
    }

A test wyglądałby tak:

it('capitalizes the first character of the first name', function () {
    
    $user = User::factory()->create(['first_name' => 'christoph'])

    
    expect($user->first_name)
        ->toBe('Christoph');
});

Testowanie Wysyłanie wiadomości

Laravel zapewnia wielu pomocników testujących, szczególnie podczas korzystania z fasad.

class PublishPodcastController extends Controller
{
    public function __invoke(Podcast $podcast)
    {
        

        Mail::to($podcast->author)->send(new PodcastPublishedMail());
    }
}

Na końcu tego kontrolera wysyłamy wiadomość e-mail. W naszym teście możemy trafić do kontrolera przez punkt końcowy i upewnić się, że ten e-mail would have been został wysłany.

it('sends email to podcast author', function() {
    
    Mail::fake();
    $podcast = Podcast::factory()->create();

    
    $this->post(route('publish-podcast', $podcast));

   
    Mail::assertSent(PodcastPublishedMail::class);
});

Zawsze uruchamiaj metodę Mail::fake() na początku testów podczas testowania wiadomości e-mail. Dzięki temu żadna rzeczywista wiadomość e-mail nie jest wysyłana do użytkownika.

Większość metod pomocniczych, takich jak assertSent również akceptuje wywołanie zwrotne jako drugi argument. W naszym przypadku otrzymuje obiekt pocztowy. Zawiera wszystkie dane e-mail, takie jak wiadomość e-mail, na którą należy ją wysłać.

Pozwala to na jeszcze więcej twierdzeń, takich jak "adres" "do" wiadomości e-mail.

Mail::assertSent(PodcastPublishedMail::class, function(PodcastPublishedMail $mail) use ($podcast) {
    return $mail->hasTo($podcast->author->email);
});

Odniesienie: Dowiedz się więcej o testowaniu materiałów do wysyłki w oficjalnych dokumentach.

Testowanie zawartości

poczty Warto również przetestować zawartość wiadomości e-mail. Jest to szczególnie przydatne, gdy masz wiele wiadomości e-mail w aplikacji. Chcesz się upewnić, że zawartość jest poprawna.

it('contains the product title', function () {
    
    $product = Product::factory()->make();

   
    $mail = new PaymentSuccessfulMail($product);

    
    expect($mail)
        ->assertHasSubject('Your payment was successful')
        ->assertSeeInHtml($product->title);
});

Odniesienie: Dowiedz się więcej o wszystkich innych twierdzeniach dotyczących testowania zawartości poczty w oficjalnych dokumentach.

Testowanie zadań i kolejek

Lubię testować zadania i kolejki osobno, zaczynając od zewnątrz. Oznacza to, że testuję, że zadanie jest wypychane do kolejki.

it('dispatches an import products job', function () {
    
    Queue::fake();

    
    $this->post('import');

    
    Queue::assertPushed(ImportProductsJob::class);
});

Gwarantuje to, że moje zadanie zostanie wypchnięte do kolejki dla określonego wyzwalacza, takiego jak trafienie w punkt końcowy. Ponownie, dba Queue::fake() o to, aby nie pchać pracy. W tym momencie nie chcemy uruchamiać zadania.

Ale nadal musimy przetestować pracę, prawda? Oczywiście. Zawiera kluczową logikę tej funkcji:

it('imports products', function() {
   
    
    (new ImportProductsJob)->handle();

    
    $this->assertDatabaseCount(Product::class, 50);
    
    
})

Ten nowy test koncentruje się na pracy i na tym, co powinien zrobić. Uruchamiamy zadanie bezpośrednio, wywołując handle na nim, co ma każda praca.

Testowanie powiadomień

Powiadomienia doskonale nadają się do informowania użytkowników o ważnych zdarzeniach. Pamiętaj, aby je również przetestować:

it('sends notification about new product', function () {
    
    Notification::fake();
    $user = User::factory()->create();
    $product = Product::factory()->create();

    
    $this->artisan(InformAboutNewProductNotification::class, [
        'productId' => $product->id,
        'userId' => $user->id,
    ]);

    
    Notification::assertSentTo(
        [$user], NewProductNotification::class
    );
});

W powyższym przykładzie testujemy powiadomienie wysyłane do użytkownika po utworzeniu nowego produktu. Używamy tej artisan metody do wyzwalania powiadomienia. Jest to świetny sposób na przetestowanie powiadomień wyzwalanych przez polecenie.

Ponownie, istnieje fake metoda fasady powiadomienia, która zapewnia, że żadne powiadomienie nie jest wysyłane.

Odniesienie: Dowiedz się więcej o testowaniu powiadomień w oficjalnych dokumentach.

Testowanie akcji

Akcje to tylko proste klasy, które mają jedno określone zadanie. Są świetnym sposobem na uporządkowanie kodu i oddzielenie logiki od kontrolerów, aby utrzymać je w czystości. Ale jak je przetestować?

Zacznijmy od początku. Po pierwsze, chcemy sprawdzić, czy nasze działanie jest wywoływane po osiągnięciu określonego punktu końcowego.

it('calls add-product-to-user action', function () {
    
    $this->mock(AddProductToUserAction::class)
        ->shouldReceive('handle')
        ->atLeast()->once();

    
    $product = Product::factory()->create();
    $user = User::factory()->create();

    
    $this->post("purchase/$user->id/$product->i");
});

Możemy to zrobić, kpiąc z naszej klasy działania i oczekując, że metoda uchwytu zostanie wywołana. Ale, znowu, nie jesteśmy tutaj zainteresowani tym, co robi nasze działanie; Chcemy mieć pewność, że zostanie wywołany, gdy uderzymy w nasz kontroler zakupów.

Aby to zadziałało, musimy upewnić się, że pojemnik rozwiązuje nasze działanie.

class PurchaseController extends Controller
{
    public function __invoke(User $user, Product $product): void
    {
        app(AddProductToUserAction::class->handle($user, $product);

        
    }
}

Wtedy też możemy przetestować samą akcję. Podobnie jak w przypadku pracy, wywołujemy handle metodę wyzwalania akcji.

it('adds product to user', function () {
    
    $product = Product::factory()->create();
    $user = User::factory()->create();

    
    (new AddProductToUserAction())->handle($user, $product);

    
    expect($user->products)
        ->toHaveCount(1)
        ->first()->id->toEqual($product->id);
});

Testowanie wyjątków Czasami dobrze jest, gdy wyjątek

jest zgłaszany, ponieważ celowo chcemy zatrzymać wykonywanie naszego kodu. To też możemy przetestować.

it('stops if at least one account not found', function () {
    
    $this->artisan(MergeAccountsCommand::class, [
        'userId' => 1,
        'userToBeMergedId' => 2,
    ]);
})->throws(ModelNotFoundException::class);

Możemy połączyć metodę throws z naszym testem w PEST. Zapewni to, że wyjątek zostanie zgłoszony.

Jednostki testowe (testy jednostkowe)

Testy jednostkowe doskonale nadają się do testowania małych fragmentów kodu, takich jak pojedyncza metoda. Nie występują żadne inne zależności. Dzięki temu są bardzo szybkie i łatwe do napisania.

Nasz przykład dotyczy obiektu danych. Zawiera metodę, która tworzy nowe wystąpienie z ładunku elementu webhook.

class UserData
{
    public function __construct(
        public string $email,
        public string $name,
        public string $country,
    )
    {}

    public static function fromWebhookPayload(array $webhookCallData): UserData
    {
        return new self(
            $webhookCallData['client_email'],
            $webhookCallData['client_name'],
            $webhookCallData['client_country'],
        );
    }
}

W odpowiednim teście sprawdzamy tylko to, co zwraca ta metoda.

it('creates UserData object from paddle webhook call', function () {
    
    $payload = [
      'client_email' => '[email protected]',
      'client_name' => 'Christoph Rumpel',
      'client_country' => 'AT',
    ];

    
    $userData = UserData::fromWebhookPayload($payload);

    
    expect($userData)
        ->email->toBe('[email protected]')
        ->name->toBe('Christoph Rumpel')
        ->country->toBe('AT');
});

Fałszowanie wywołań HTTP Czasami musisz wykonywać wywołania

HTTP w aplikacji. Może to być pobieranie danych z zewnętrznego interfejsu API lub wysyłanie danych do innej usługi. Często chcesz sfałszować te połączenia w swoich testach, aby nie musieć polegać na usłudze zewnętrznej.

it('import product', function () {
    
    Http::fake();

    
    
});

Metoda fake na fasadzie HTTP zapewni, że nie zostanie wykonane żadne prawdziwe połączenie, a odpowiedź będzie zawsze kodem 200 stanu.

Ale możesz też być bardziej konkretny. Na przykład testujemy akcję, która pobiera dane z zewnętrznego interfejsu API i zapisuje je w bazie danych.

it('imports product', function() {
    
    Http::fake([
        'https://christoph-rumpel.com/import' => Http::response([
            'title' => 'My new product',
            'description' => 'This is a description',
        ]),
    ]);
    $user = User::factory()->create();

    
    (new ImportProductAction)->handle($user);

    
    $this->assertDatabaseHas(Product::class, [
        'title' => 'My new product',
        'description' => 'This is a description',
    ]);
});

Testowanie połączeń

HTTP Oprócz fałszowania wywołań HTTP można również sprawdzić, czy wykonano określone połączenie. Jest to przydatne, gdy chcesz mieć pewność, że kod wykonuje prawidłowe wywołania.

it('make the right call', function () {
    
    Http::fake();
    $user = User::factory()->create();

    
    (new ImportProductAction)->handle($user);

    
    Http::assertSent(function ($request) {
        return $request->url() === 'https://christoph-rumpel.com/import'
            && $request['accessToken'] === '123456';
    });
});

Odniesienie: Dowiedz się więcej o fałszowaniu wywołań HTTP w oficjalnych dokumentach.

Pozorowanie zależności

Podczas pracy z kodem z zależnościami pomocne może być ich wyśmiewanie. Pozwoli to skoncentrować się na logice kodu, a nie na zależnościach. Oznacza to również mocking can be useful for any kind of tests.

Zrobiliśmy to już podczas testowania naszych klas akcji, ale działa to z każdą zależnością. W poniższym przykładzie mamy kontroler z dwiema zależnościami: dostawcą płatności i mailerem.

class PaymentController extends Controller
{
    public function __invoke(PaymentProvider $paymentProvider, Mailer $mailer)
    {
        $paymentProvider->handle();

        $mailer->to(auth()->user())->send(new PaymentSuccessfulMail);
    }
}

W naszym teście chcemy skupić się na sprawdzeniu, czy wysyłana jest prawidłowa wiadomość e-mail. Dlatego możemy kpić z dostawcy płatności i oczekiwać, handle że metoda zostanie wywołana. W rezultacie nasz test nie zakończy się niepowodzeniem, nawet jeśli rzeczywisty dostawca płatności nigdy nie zostanie wezwany.

it('sends payment successful mail', function () {
    
    Mail::fake();

    
    $this->mock(PaymentProvider::class)
        ->shouldReceive('handle')
        ->once();

    
    $this->post('payment');

    
    Mail::assertSent(PaymentSuccessfulMail::class);
});

Wniosek

: Stworzenie w głowie listy wszystkiego, co lubisz testować w swoich aplikacjach, jest niezbędne. Ale oczywiście, ucząc się nowych koncepcji kodowania, musisz także nauczyć się nowych technik testowania. Twoja lista będzie się z czasem powiększać.

Jeśli uważasz, że brakuje mi czegoś ważnego, daj mi znać na Twitterze. Zawsze chętnie uczę się nowych rzeczy.

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

O

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

O autorze CrazyBoy49z
WORK EXPERIENCE
Kontakt
Ukraine, Lutsk
+380979856297