Pierwsze kroki z Nette Testerem

Nawet dobrzy programiści popełniają błędy. Różnica między dobrym a złym programistą polega na tym, że dobry popełni błąd tylko raz, a następnym razem wykryje go automatycznymi testami.

  • “Kto nie testuje, skazany jest na powtarzanie swoich błędów.” (przysłowie)
  • “Gdy tylko pozbędziemy się jednego błędu, pojawia się kolejny.” (prawo Murphy'ego)
  • “Kiedykolwiek kusi Cię, żeby napisać print, napisz zamiast tego test.” (Martin Fowler)

Pisałeś kiedyś w PHP taki kod?

$obj = new MyClass;
$result = $obj->process($input);

var_dump($result);

Czyli wypisywałeś wynik wywołania funkcji tylko po to, żeby wzrokowo sprawdzić, czy zwraca to, co powinna? Zapewne robisz to wiele razy dziennie. Ręka na sercu: jeśli wszystko działa poprawnie, usuwasz ten kod? Spodziewasz się, że klasa w przyszłości się nie zepsuje? Prawa Murphy'ego gwarantują coś przeciwnego :-)

W zasadzie napisałeś test. Wystarczy go tylko drobnie zmodyfikować, żeby nie wymagał wzrokowej kontroli, tylko sprawdzał się sam. A jeśli testu nie usuniesz, możesz uruchomić go kiedykolwiek w przyszłości i zweryfikować, że wszystko nadal działa jak trzeba. Z czasem utworzysz dużą liczbę takich testów, więc przydałoby się uruchamiać je automatycznie.

I z tym wszystkim pomoże Ci Nette Tester.

Co czyni Testera wyjątkowym?

Pisanie testów dla Nette Testera jest wyjątkowe tym, że każdy test to zwykły skrypt PHP, który można uruchomić samodzielnie.

Gdy więc piszesz test, możesz po prostu go uruchomić i sprawdzić, czy nie ma w nim na przykład błędu programistycznego. Czy działa poprawnie. Jeśli nie, możesz łatwo przejść go krokowo w swoim IDE i poszukać błędu. Możesz nawet otworzyć go w przeglądarce.

A co najważniejsze: uruchamiając go, wykonujesz test. Od razu dowiadujesz się, czy przeszedł, czy nie. Jak? Pokażmy to. Napiszemy trywialny test pracy z tablicą PHP i zapiszemy go do pliku ArrayTest.php:

<?php
use Tester\Assert;

require __DIR__ . '/vendor/autoload.php';  # wczytujemy autoloader Composera
Tester\Environment::setup();               # inicjalizacja Nette Testera

$stack = [];
Assert::same(0, count($stack));   # oczekujemy, że count() zwróci zero

$stack[] = 'foo';
Assert::same(1, count($stack));   # oczekujemy, że count() zwróci jeden
Assert::contains('foo', $stack);  # weryfikujemy, że $stack zawiera pozycję 'foo'

Jak widzisz, do potwierdzenia, że rzeczywista wartość odpowiada wartości oczekiwanej, służą tak zwane metody asercji, jak Assert::same().

Mamy napisany test i możemy uruchomić go z wiersza poleceń. Pierwsze uruchomienie ujawni ewentualne błędy składniowe, a jeśli nigdzie nie zrobiłeś literówki, wypisze:

$ php ArrayTest.php

OK

Spróbuj zmienić asercję w teście na fałszywą, na przykład Assert::contains('XXX', $stack);, i zobacz, co się stanie przy uruchomieniu:

$ php ArrayTest.php

Failed: ['foo'] should contain 'XXX'

in ArrayTest.php(17) Assert::contains('XXX', $stack);

FAILURE

O pisaniu testów mówimy dalej w rozdziale Pisanie testów.

Instalacja i wymagania

Minimalna wersja PHP wymagana przez Testera to 8.0 (szczegóły w tabeli Wspierane wersje PHP). Preferowanym sposobem instalacji jest Composer:

composer require --dev nette/tester

Spróbuj uruchomić Nette Testera z wiersza poleceń (bez argumentów wypisze tylko pomoc):

vendor/bin/tester

Uruchamianie testów

Wraz z aplikacją rośnie liczba testów. Uruchamianie testów po jednym nie byłoby praktyczne. Dlatego Tester ma masowy runner testów, który wywołujemy z wiersza poleceń. Jako parametr podajemy katalog, w którym znajdują się testy. Kropka oznacza bieżący katalog.

vendor/bin/tester .

Runner testów przeszukuje podany katalog i wszystkie podkatalogi w poszukiwaniu testów, czyli plików *.phpt i *Test.php. Znajdzie też nasz test ArrayTest.php, bo pasuje do maski.

Następnie zaczyna testowanie. Każdy test uruchamiany jest jako nowy proces PHP, więc działa całkowicie odizolowany od pozostałych. Uruchamia je równolegle w wielu wątkach, dzięki czemu jest niezwykle szybki. A najpierw uruchamia testy, które nie przeszły przy poprzednim uruchomieniu, więc od razu dowiadujesz się, czy udało Ci się naprawić błąd.

W trakcie wykonywania testów Tester na bieżąco wypisuje wyniki do terminala jako znaki:

  • . – test przeszedł
  • s – test został pominięty
  • F – test nie przeszedł

Wyjście może wyglądać tak:

 _____ ___  ___ _____ ___  ___
|_   _/ __)( __/_   _/ __)| _ )
  |_| \___ /___) |_| \___ |_|_\  v2.6.0

PHP 8.5.2 (cli) | php | 8 threads

........s................F.........

-- FAILED: greeting.phpt
   Failed: 'Hello John' should be
       ... 'Hello Peter'

   in greeting.phpt(19) Assert::same('Hello Peter', $o->say('John'));

FAILURES! (35 tests, 1 failures, 1 skipped, 1.7 seconds)

Uruchomiono 35 testów, jeden nie przeszedł, jeden został pominięty.

Kontynuujemy w rozdziale Uruchamianie testów.

Tryb watch

Refaktoryzujesz kod? A może nawet tworzysz zgodnie z metodyką TDD (Test Driven Development)? Wtedy spodoba Ci się tryb watch. W tym trybie Tester monitoruje kody źródłowe i przy zmianie uruchamia się automatycznie.

W trakcie tworzenia masz w rogu monitora terminal, w którym świeci się zielony pasek stanu, a gdy nagle zmieni się na czerwony, wiesz, że właśnie zrobiłeś coś nie do końca właściwie. To właściwie świetna gra, w której programujesz i starasz się utrzymać kolor.

Tryb watch uruchamia się parametrem --watch.

Raporty pokrycia kodu

Tester potrafi generować raporty z przeglądem tego, jak dużą część kodu źródłowego pokrywają testy. Raport może być albo w czytelnym dla człowieka formacie HTML, albo w Clover XML do dalszego przetwarzania maszynowego.

Zobacz przykładowy raport HTML z pokryciem kodu.

Wspierane wersje PHP

Wersja Kompatybilna z PHP
Tester 2.6 PHP 8.0 – 8.5
Tester 2.5 PHP 8.0 – 8.5
Tester 2.4 PHP 7.2 – 8.2
Tester 2.3 PHP 7.1 – 8.0
Tester 2.1 – 2.2 PHP 7.1 – 7.3
Tester 2.0 PHP 5.6 – 7.3
Tester 1.7 PHP 5.3 – 7.3 + HHVM 3.3+
Tester 1.6 PHP 5.3 – 7.0 + HHVM
Tester 1.3 – 1.5 PHP 5.3 – 5.6 + HHVM
Tester 0.9 – 1.2 PHP 5.3 – 5.6

Dotyczy najnowszej wersji patch.

Tester do wersji 1.7 wspierał też HHVM 3.3.0 albo nowszy (przez tester -p hhvm). Wsparcie zostało wycofane od wersji Testera 2.0.