Budujemy przykładową aplikację w Laravel – część 3, modyfikacja modelu, tworzenie kontrolera, repository i testów

Witam w trzeciej części cyklu. Dziś spróbujemy stworzyć model naszego pojazdu, który będziemy wypożyczać. Zdefiniujemy schemat bazy, stworzymy model, wykorzystamy wzorzec Repository a także przekażemy dane do widoku i stworzymy niezbędne testy. Do dzieła!
Zaczynamy od stworzenia katalogu app/models. Będziemy w nim przechowywać nasze pliki modelów. Następnie przenosimy jedyny obecny plik modelu user.php do katalogu models i zmieniamy namespace na namespace app\models.

Kolejna czynność do zaktualizowanie kontrolera Auth/RegisterController, zmieniamy w nim App\User na App\Models\User. Aktualizujemy composer.json:

i wybieramy composer dump-autoload.

Tworzenie modelu

Katalog z modelami stworzony, czas stworzyć nową tabelę pojazdów, w tym celu wybieramy:

w którego zawartości umieszczamy informacje o naszej nowej tabeli:

  • klucz główny id
  • kolumnę z opisem, tytułem, adresem obrazu i identyfikatorem użytkownika, do którego będzie dany pojazd należał
  • stemple czasowe stworzenia i edycji rekordu (timestamps())

Czas uruchomić tak stworzoną migrację:

Za pomocą ulubionego interfejsu bazy (phpmyadmin, adminer) dodajemy testowe rekody:

Czas przenieść pliki graficzne w bardziej odpowiednie miejsce, przenosimy 1.jpg, 2.jpg i 3.jpg do katalogu public/img/vehicles/

Mamy już zdjęcia, mamy rekordy w bazie, czas stworzyć sam model i przenieść go do katalogu models:
php artisan make:model Vehicle

Uzupełniamy plik modelu danymi opisującymi strukturę obiektu:

Ponownie uruchamiamy composer dump-autoload i voila! Mamy nasz pierwszy model 🙂 Czas przejść do kontrolera HomeController:

 

Jak widzimy zmiany dotyczą:

  • wstrzyknięcia poprzez Dependency Injection modelu Vehicle do metody index.
  • Wywołanie metody get() pobierajacej wszystkie rekordy (z pośrednim użyciem with(‘user’), dzięki czemu tabela Vehicles zostanie złączona z Users i pobraną zostani właściciele pojazdów
  • Przekazanie pojazdów do widoku ‘home’Sam widok Home również zostanie zmieniony, zamiast statycznych danych wyświetlmy dynamicznie przekazane dane. W tym celu użyjemy pętli foreach iterującej po wynikach, a następnie odwołując się do poszczególnych pól obiektu wyświetlimy wszystkie niezbędne dane:

Rezultat? Fantastyczny:

Nasze dane dodane do bazy poprawnie wyświetlają się na stronie głównej. Co więcej, dodanie nowego jachtu automatycznie również umieści go na liście.

Repositories

Niby wszystko ok, nasza aplikacja spełnia wszelkie wymagania. Mimo wszystko spróbujmy nieco ją zmodyfikować. Rozważmy sytuację, gdy będziemy chcieli uzyć innego zródła danych niż MySQL – niestety by tego dokonać będziemy musieli sporo kodu mówiąc wprost przepisać. Warto byłoby dodać nową warstwę, która to oddzielałaby wartstwę danych od kontrolera, wszak kontrolera nie obchodzi skąd otrzymuje dane, kontroler potrzebuje jedynie interfejsu do zapisu i pobierania niezbędnych informacji.

W niedawnym wpisie http://blog.pawelkaminski.net/struktura-aplikacji-w-laravel-5/ proponowałem użycie osobnych klas, saverów do zapisu danych. Laravel 5 zachęca do rozbudowy tego podejścia – w tym miejscu pojawia się wzorzec projektowy repository. Jest to dodatkowa warstwa abstrakcji dla naszego modelu, pozwala na dotkliwsze odseparowanie warstwy kontrolera od modelu. W tym celu

  • tworzymy katalog app\repository
  • dodajemy go w composer.json
  • uruchamiamy composer auto-load

Celem użycia wzorca repository jest ustalenie pewnych ram, szablonu, który miałby zastosowanie dla wszystkich rodzajów źródeł danych, a któru ujednolica ich połącznie z kontrolerem. Takim szablonem będzie interfejs, czyli opis podstawowej klasy, zbiór metod, która ów podstawowa klasa musi zapewniać dla naszych obiektów. Widzimy, więc że dowolne reposytorium musi umożliwiać:

  • pobranie wszystkich danych z źródła
  • stworzenie nowego rekodu danych
  • aktualizację istniejącego rekordu
  • usunięcie rekordu

App\Repositories\RepositoryInterface.php

Idąc tym tropem dalej, możemy stworzyć podstawową klasę, która będzie implementowała nasz interfejs. Co więcej klasa ta, będzie na tyle elastyczna, iż będzie implementowała podstawową czynności, przy czym nie będzie miała na twardo zapisanego modelu, model będzie przekazywany w konstruktorze, dzięki temu będzie to klasa uniwersalna, nie przypisana na stałe do żadnego z modeli:

App\Repositories\BaseRepository.php

Oba powyższe pliki pozwalają nam na stworzenie konkretnej już klasy repozytorium ‘Vehicle’, która to będzie dziedziczyła z klasy bazowej create, update, delete i find, natomiast będzie nadpisywała metodę getAll (dlatego, iż chcemy oprócz ‘zwykłego’ pobierania wszystkich rekordów z tabeli Vehicles, połączyć to tabelę z tabelą ‘Users’):

Nasz wzorzec Repository jest już w pełni przygotowany do użycia go w kontrolerze, w którym to już nie odwołujemy się bezpośrednio do modelu, a do VehicleRepository. Obiekt ten wstrzykujemy do metody Index, a następnie uruchamiamy metodę getAll(). Tak jak poprzednio dane przekazujemy do widoku:

Testy

Kolejnym krokiem będzie próba zautomatyzowania testów:

  1. Kontrolera

Wywołujemy:
php artisan make:test Home/IndexTest.php

Co po kolei oznaczają linie kodu? Przede wszystkim:

  • tworzymy nową klasę dziedziczącą po TestCase
  • tworzymy metodę, która będzie testowała konkretną funkcjonalność, metoda musi rozpoczynać się od słowa test
  • kolejnie tworzymy mock klasy VehicleRepository, czyli emuluujemy utworzenie obiektu repozytorium Vehicle
  • za pomocą $this->app->instance informujemy Laravela, że w tym aktualnym teście, wszelkie odwołania do App\Repositories\VehicleRepository mają używać mockowanego obiektu $mock (stworzonego linijkę wyżej)
  • kolejne linijki to już same testy, za pomocą $mock->shouldReceive(‘getAll’)->once(); stwierdzamy, iż nasz obiekt VehicleRepository powinien wywołać metodę ‘getAll()’ i zrobić to dokładnie jeden raz
  • drugi rodzaj testu występuje w kolejnej linijce, wywołujemy tam żądanie GET /home i na końcu za pomocą assertHasView sprawdzamy, czy otrzymana przed chwilą odpowiedź rzeczywiście posiada zmienną ‘vehicles’

Spróbujmy uruchomić testy poprzez wywołanie:
phpunit (lub vendor/bin/phpunit)

Jeśli mamy tylko jeden test, powinniśmy ujrzeć:

Jest ok, widzimy, iż kontroler poprawnie przeszedł testy, odpowiednia metoda została wywołana, sprawdzenie czy do widoku przekazywane są dane również udało się bez przeszkód.

Oprócz tego przydałoby się również jednak sprawdzić same dane. Czy otrzymywana lista jest poprawna? Tu przechodzimy do punktu drugiego:

2. Testów repozytorium

W tym przypadku użyjmy testów jednostkowych, zgodnie z definicją z dokumentacji Laravela:

Unit tests are tests that focus on a very small, isolated portion of your code. In fact, most unit tests probably focus on a single method. Feature tests may test a larger portion of your code, including how several objects interact with each other or even a full HTTP request to a JSON endpoint.

która mówi, iż testy jednostkowe należy wykonywać na małych częsciach chodu, w praktyce są to testy pojedyńczych metod. My właśnie chcemy przetestować pojedyńczą metodę getAll z VehicleRepository. W tym celu wywołujemy:

php artisan make:test Repositories/VehicleRepositoryTest –unit

Do testów przyda nam się również fabryka Vehicle, czyli sposób na tworzenie obiektów testowych. Aby to wykonać należy stworzyć plik VehicleFactory.php w katalogu app\database\factories

Sam plik prezentuje się następująco:

Podajemy definicję klasy, a także przekazujemy obiekt fakera, czyli Laravelowego generatora losowych danych. My z niego nie skorzystamy, a wszystkie dane do testów wygenerujemy ręcznie.

Przejdźmy do samego pliku testu, tests\Unit\Repositories\VehicleRepositoryTest.php:

Przeanalizujmy nasz kod, czego tak właściwie oczekujemy od testów? Na pewno chcielibyśmy dodać do systemu jakieś pojazdy, a następnie sprawdzić, czy system zwróci je nam poprawnie. Po kolei:
1. Tworzymy metodę getAllData, która zwróci nam listę danych testowych

2. Tworzymy metodę getFilteredResponse, metoda ta zwróci nam aktualną listę obiektów w bazie pojazdów. Zauważmy, iż dane są po pobraniu filtrowane, wyrzucam takie rzeczy jak id, created_at, czy user. W celu uproszczenia robię testy bez przynależności pojazdu do konkretnego usera, natomiast pola id czy created są polami, których wartości przed testem po prostu nie znam.

3. Ostatnia metoda to testGetAllData. Zaczyna się od przedrostka test, więc jest to nasza główna metoda testująca. W niej to kolejno: pobieramy statyczne dane testowe, za pomocą factory tworzymy nowe obiekty, wywołujemy wcześniej stworzną metodę getFilteredResponse w celu pobrania listy obiektów, a następnie za pomocą assertContains sprawdzamy, czy pobrana lista zawiera 3 statyczne elementy testowe.

Nie pozostaje nam już nic innego jak uruchomienie testów i radość z pozytywnego ich przejścia:

Nasz system wzbogacił się o możliwość dynamicznego wyświetlania pojazdów. W tym celu:

  • Stworzyliśmy model pojazdu
  • Stworzyliśmy warstwę danych w postaci repozytorium i jego struktury (interfejsu)
  • Dodaliśmy sposób tworzenia obiektu Pojazdu w database\factories
  • Dodaliśmy test kontrolera i zwracanych danych
  • Dodaliśmy test samych danych – testując repozytorium

Następny etap prac to tabela wypożyczeń.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

This site uses Akismet to reduce spam. Learn how your comment data is processed.