Wprowadzenie do Laravel Dusk
Czym jest Laravel Dusk
Laravel Dusk jest narzędziem do automatyzowania przeglądarki i testowania stron budowanych w Laravelu. Narzędzie to uruchamia i steruje ChromeDriver, dzięki czemu mamy możliwość automatycznego testowania naszych aplikacji w przeglądarce.
Test-driven-development w Laravel Dusk
Test driven development to technika tworzenia oprogramowania, która opiera się na cyklu trzech następujących po sobie czynności: test czerwony -> test zielony -> refactoring. Więcej informacji na temat TDD można znaleźć w sieci, ja natomiast polecam książkę „TDD. Techniki programowania sterowanego testami” Dariusza Woźniaka, kod tej książki jest pisany w C#, ale zawarte w niej informacje są uniwersalne dla wszystkich języków programowania.
Dla potrzeb naszego artykułu musimy o TDD wiedzieć tyle, że najpierw piszemy test który zakończy się niepowodzeniem, później piszemy kod który powoduje że test kończy się powodzeniem, następnie robimy refactoring i przechodzimy do pisania kolejnego testu, tym samym zapętlając cykl.
Instalacja Laravel Dusk
Tworzymy standardowy projekt w laravelu: tworzymy nowy projekt, konfigurujemy dostęp do bazy danych, uruchamiamy migracje. Następnie przechodzimy do katalogu projektu i wykonujemy poniższe czynności.
Na początek instalujemy Laravel Dusk przy pomocy composera:
composer require --dev laravel/dusk
Następnie wykonujemy polecenie, które integruje Laravel Dusk z naszym projektem:
php artisan dusk:install
Od tego momentu możemy wykonywać testy poleceniem:
php artisan dusk
Jeśli na swoim komputerze napotkasz problemy z wersją ChromeDriver, użyj poniższego polecenia aby użyć innej wersji:
php artisan dusk:chrome-driver 74
(74 to numer wersji ChromeDriver)
Dodatkowo można przyznać odpowiednie uprawnienia, wykonując polecenie:
chmod -R 0755 vendor/laravel/dusk/bin/
Pierwszy Test
Załóżmy że w naszej aplikacji będzie strona profilowa pod adresem /profile.
Tworzymy test poleceniem:
php artisan dusk:make ProfilePageTest
Zostanie utworzony plik tests/Browser/ProfilePageTest.php a w nim klasa testów ProfilePageTest. Dodajmy do tej klasy pierwszą metodę testującą:
public function testIfProfilePageHasHeader() { $this->browse(function (Browser $browser) { $browser->visit('/profile') ->assertSeeIn('h3.page-title', 'My Profile'); }); }
Powyższy test uruchamia przeglądarkę (przeglądarka jest “headless” więc jej nie zobaczymy, gdyż działa w tle). W przeglądarce odwiedzany jest adres /profile a następnie sprawdzane jest czy na stronie istnieje element h3 z klasą css page-title, zawierający tekst “My Profile”.
Tutaj mała uwaga: nasza aplikacja musi być uruchomiona, żeby testy zadziałały, czyli najpierw uruchamiamy php artisan serve (lub serwujemy ją z serwera) i dopiero wtedy w drugiej konsoli odpalamy:
php artisan dusk
Test zwróci błąd, ponieważ strona /profile nie istnieje. Zróbmy prostą “zaślepkę” tej strony w pliku routes/web.php
Route::get('/profile', function(\Illuminate\Http\Request $request) { return '<h3 class="page-title">My Profile</h3>'; });
Po dodaniu powyższej reguły routingu test się wykona, ponieważ strona istnieje i zawiera element oczekiwany w teście.
Drugi test: sesja użytkownika
Strona profilowa zapewne będzie dostępna tylko dla zalogowanych użytkowników. Napiszmy kolejny test, który sprawdza, czy na stronie istnieje element h4 zawierający nazwę zalogowanego użytkownika.
public function testIfProfilePageShowsUserName() { $this->browse(function (Browser $browser) { $user = User::find(self::TEST_USER_ID); $browser->loginAs($user) ->visit('/profile') ->assertSeeIn('h4', $user->name); }); }
Powyższy kod zwraca z bazy danych encję użytkownika, id użytkownika jest brane w tym przypadku ze stałej TEST_USER_ID, możesz ją dodać w pliku tests/Browser/ProfilePageTest.php lub tests/DuskTestCase.php, decyzję zostawiam Tobie. W moim kodzie stała jest zdefiniowana następująco: const TEST_USER_ID = 596, w bazie mam dodanego użytkownika o id = 596.
Uruchamiamy testy, powyższy test oczywiście kończy się niepowodzeniem, ponieważ nasza strona nie zawiera elementu h4 z nazwą użytkownika, zmieńmy więc nasz kod routingu do następującej postaci:
Route::get('/profile', function(\Illuminate\Http\Request $request) { $user = Auth::user(); return '<h3 class="page-title">My Profile</h3>' . (empty($user) ? '' : '<h4>' . $user->name . '</h4>'); });
Po ponownym uruchomieniu testów, otrzymamy prawidłowy wynik, ponieważ powyższy kod pobiera zalogowanego użytkownika i wstawia jego nazwę do elementu h4.
W tym momencie należy przejść do refactoringu, na początek proponowałbym przenieść kod z routingu do kontrolera i widoku. Następnie można napisać kolejny test dla strony /profile lub stworzyć klasę testów dla kolejnej strony.
Usprawnienia testów: strony
Laravel Dusk zawiera funkcjonalność Pages, która pozwala nam stworzyć klasę definiującą zadania, które chcemy wykonać na danej stronie podczas jej testowania. Klasa Page pozwala również zdefiniować skróty do różnych selektorów. Więcej na ten temat znajdziecie w sekcji Pages w dokumentacji Laravel Dusk, ja pokażę tylko jak przerobić nasz test, żeby używał klasy zamiast stringa z urlem ‘/profile’.
Na początek tworzymy klasę stronypoleceniem:
php artisan dusk:page Profile
Klasa Profile zostanie utworzona w pliku tests/Browser/Pages/Profile.php, jedyną rzeczą którą musimy w niej zmienić to wartość zwracana z metody url():
public function url() { return '/profile'; }
Następnie w naszych testach zamieniamy wszystkie wystąpienia ->visit(‘/profile’) na ->visit(new Profile) i dodajemy instrukcję use Tests\Browser\Pages\Profile;
Wykonujemy testy, żeby potwierdzić że nic nie popsuliśmy. Działa!

Co się zmieniło? Na tym etapie nic, testy sprawdzają dokładnie to samo co wcześniej i zwracają ten sam wynik, ale dzięki użyciu klasy Tests\Browser\Pages\Profile mamy możliwość tworzenia kolejnych testów w bardziej zorganizowany sposób. Dodatkowo sama klasa zawiera asercję sprawdzającą czy po nawigacji do strony /profile strona ta faktycznie się otwiera.
Dalsze możliwości
Opisane powyżej działania to tylko podstawa żeby zacząć przygodę z testowaniem przy pomocy Laravel Dusk, możliwości tego narzędzia są jednak dużo większe. Dusk udostępnia bogatą paletę metod do interakcji z interfejsem użytkownika a nawet testowanie komponentów VueJs. O testowaniu interfejsu użytkownika opowiem więcej w osobnym artykule. Zachęcam do zapoznania się z wszystkimi możliwościami interakcji na stronie https://laravel.com/docs/6.x/dusk#interacting-with-elements
Przykład z życia
Dostałem do przepisania działającą aplikację zawierającą masę legacy code. Postanowiłem napisać kod od zera, ale z zachowaniem istniejącej struktury adresów url, endpointów API, tabel w bazie danych.
Przepisywanie kodu metoda po metodzie jest kiepskim pomysłem, ponieważ chcemy stworzyć aplikację o tych samych funkcjonalnościach ale z dużo lepszym kodem odpowiadającym współczesnym standardom kodowania, z wykorzystaniem wzorców projektowych oraz dostępnych w Laravelu rozwiązań. Przyjąłem więc plan zakładający użycie Laravel Dusk do sprawdzenia czy wszystkie funkcjonalności w nowej aplikacji odpowiadają funkcjonalnościom ze starej. Praca nad projektem zaczęła się więc od stworzenia zestawu testów sprawdzających czy wszystkie strony zawierają wymagane elementy, w kolejnych etapach powstaną testy sprawdzające czy odpowiednie dane pojawiają się na stronach i czy interakcje z elementami na stronie działają zgodnie z założeniami. Dopiero po stworzeniu tego zestawu testów przystąpimy do tworzenia kodu.
Inne podejście, przez wiele źródeł traktowane jako lepsze, zakłada pisanie pojedynczego testu i zaraz po nim kodu, ja jednak zdecydowałem się napisać najpierw pełny zestaw testów, aby móc rozdzielić pracę nad poszczególnymi modułami na różnych programistów i samemu, jako team leader sprawdzać postęp prac nad kodem.
Oczywiście testy pisane w Laravel Dusk należy traktować jako funkcjonalne i integracyjne, ich pisanie nie zwalnia to programistów z obowiązku pisania testów jednostkowych.
Skomentuj