Auditor – Testy automatyczne i ich organizacja

Aktualnie w projekcie Auditor wykorzystuję dwa typy testów automatycznych – testy jednostkowe oraz integracyjne. W obu przypadkach za uruchamianie testów, ich układ i wykorzystywane asercje odpowiada najpopularniejszy PHPowy test framework – PHPUnit. Jego autorem jest Sebastian Bergmann. Zresztą bardzo spoko gość – w 2015 roku miałem okazję uczestniczyć w warsztatach prowadzonych przez Sebastiana na konferencji IPC Berlin. Poniżej pamiątkowe zdjęcie ekipy Future Processing wraz z Sebastianem i Stefanem (thePHP.cc):

Future Processing & thePHP.cc

Testy jednostkowe (unit test)

Najprościej mówiąc testy jednostkowe to kod weryfikujący poprawność zachowania kodu produkcyjnego. Sprawdzenie czy elementy systemu zachowują się w sposób jaki zaplanował programista. Co jest bardzo ważne – testuje się jednostkę czyli możliwie najmniejszy element systemu – obiekt, a najlepiej konkretną metodę lub funkcję.

O testach jednostkowych rozpisał się ciekawie Maciej, polecam zatem jego artykuł – Zapowiedź minicyklu o testach

Testy integracyjne (integration test)

Testy integracyjne odpowiadają za sprawdzenie kilku komponentów, które ze sobą współpracują. Nie dotyczą konkretnej jednostki, a grupy jednostek realizujących zadanie. To ten rodzaj testów, który nie musi posiadać mocków na operacje bazodanowe czy I/O (może, ale weryfikowanie poprawności odbywa się wtedy na zasadzie weryfikacji kontraktu wejściowego / wyjściowego). Po prostu, komunikacja z zewnętrznymi serwisami, bazą danych czy systemem plików są naturalną częścią takich testów.

W projekcie Auditor testy integracyjne wykorzystują osobną bazę danych. Ich „wejściem” jest akcja kontrolera dla której dostarczam dane wejściowe (symulacja metody POST). Następnie weryfikuję stan bazy danych – czy wszystkie dane zostały zapisane w założony sposób. Te testy będą pokrywały w projekcie jedynie ścieżkę Happy Path, przypadki brzegowe powinny zostać pokryte testami jednostkowymi (uwaga – potencjalnie to podejście może ulec zmianie).

Sposobów na przeprowadzanie i weryfikację testów integracyjnych jest wiele. Wszystko zależy od kontekstu środowiska w którym działa aplikacja oraz prawdę mówiąc – przyjętego w zespole programistycznym założenia (oby tylko popartego konkretnymi argumentami :)).

Struktura

tests
|-- Common
| |-- Mock
| |-- Builder
|
|-- Integration
| |-- CreateNewProjectTest.php
| |-- phpunit.xml
|
|-- Unit
| |-- AppBundle
| |-- SymfonyCommandHandlerResolverTest.php
| |-- phpunit.xml

Common – jest folderem zawierającym współdzielone pomiędzy testami klasy. To tutaj znajdować będą się buildery, mocki, stuby i fake wykorzystywane w testach.

Integration – przestrzeń do umieszczania testów integracyjnych. Bez podfolderów. Spowodowane jest to samą ilością testów oraz faktem, że dotyczą one jedynie żądań (Command czyli akcji zmieniających stan systemu) które może wykonać użytkownik aplikacji.

Unit – przestrzeń dla testów jednostkowych. Podfoldery odzwierciedlają bundle aplikacji.

Poszczególne Test Suite to kolejne klasy (zawierające przypadki testowe – Test Case). Nie są one równoznaczne z bardzo często przyjętym założeniem (momentami błednym!), jedna klasa produkcyjna = jedna klasa testowa. Jeśli potrzebuję utworzyć kilka przypadków testowych dla metody – mogę wyodrębnić je do osobnego Test Suite. Jednak warto zadać sobie wtedy pytanie: „Czy na pewno metoda którą chcę przetestować nie robi zbyt dużo? Może lepiej wyodrębnić ją jako osobną klasę?

Każdy z typów testów posiada osobą konfigurację PHPUnit, ze względu na fakt, że przy najbliższych zmianach zamierzam dodać Code Coverage dla testów jednostkowych.

Composer Scripts

Aby ułatwić sobie uruchamianie testów z konsoli, stworzyłem dwa aliasy na poziomie Composer Scripts. Wpis w composer.json wygląda następująco:

{
  "scripts": {
    "unit": "php vendor/phpunit/phpunit/phpunit --testdox --configuration tests/Unit/phpunit.xml",
    "integration": "php vendor/phpunit/phpunit/phpunit --testdox --configuration tests/Integration/phpunit.xml"
  }
}
Na co dzień programujący CTO w Emphie Solutions. Projektuje, tworzy oraz wdraża rozwiązania oparte o ekosystem JavaScript. Rozwija swoje umiejętności z zakresu Cloud / DevOps / SRE. Fascynat programowania, architektury, chmury i dobrych praktyk w szerokim ujęciu. Na temat technologii publikuje materiały w ramach projektu DevEnv, którego jest założycielem.
PODZIEL SIĘ