Klasy pomocnicze
HttpAssert
Klasa Tester\HttpAssert udostępnia narzędzia do testowania serwerów HTTP. Pozwala łatwo wykonywać żądania
HTTP i weryfikować kody statusu, nagłówki oraz treść ciała odpowiedzi za pomocą interfejsu płynnego.
# Podstawowe żądanie HTTP i weryfikacja odpowiedzi
$response = Tester\HttpAssert::fetch('https://example.com/api/users');
$response
->expectCode(200)
->expectHeader('Content-Type', contains: 'json')
->expectBody(contains: 'users');
Metoda fetch() domyślnie tworzy żądanie GET, ale wszystkie parametry da się dostosować:
HttpAssert::fetch(
'https://api.example.com/users',
method: 'POST',
headers: [
'Authorization' => 'Bearer token123', # tablica asocjacyjna
'Accept: application/json', # albo format tekstowy
],
cookies: ['session' => 'abc123'],
follow: false, # nie podążaj za przekierowaniami
body: '{"name": "John"}'
)
->expectCode(201);
Kody statusu weryfikuje się metodami expectCode() i denyCode(). Możesz przekazać albo konkretną
liczbę, albo funkcję walidującą:
$response
->expectCode(200) # dokładny kod
->expectCode(fn($code) => $code < 400) # własna walidacja
->denyCode(404) # nie może być 404
->denyCode(fn($code) => $code >= 500); # nie może być błędem serwera
Do weryfikacji nagłówków służą metody expectHeader() i denyHeader(). Możesz sprawdzić, czy
nagłówek istnieje, zweryfikować jego dokładną wartość albo dopasować część jego treści:
$response
->expectHeader('Content-Type') # nagłówek musi istnieć
->expectHeader('Content-Type', 'application/json') # dokładna wartość
->expectHeader('Content-Type', contains: 'json') # zawiera tekst
->expectHeader('Server', matches: 'nginx %a%') # pasuje do wzorca
->denyHeader('X-Powered-By') # nagłówek nie może istnieć
->denyHeader('X-Debug', contains: 'sensitive') # nie może zawierać tekstu
->denyHeader('X-Debug', matches: '~debug~i'); # nie może pasować do wzorca
Weryfikacja ciała odpowiedzi działa podobnie, metodami expectBody() i denyBody():
$response
->expectBody('OK') # dokładna wartość
->expectBody(contains: '"status": "success"') # zawiera fragment JSON
->expectBody(matches: '%A% hello %A%') # pasuje do wzorca
->expectBody(fn($body) => json_decode($body) !== null) # własna walidacja
->denyBody('Error occurred') # nie może mieć dokładnej wartości
->denyBody(contains: 'error') # nie może zawierać tekstu
->denyBody(matches: '~exception|fatal~i'); # nie może pasować do wzorca
Parametr follow steruje tym, jak HttpAssert obsługuje przekierowania HTTP:
# Testowanie przekierowania bez podążania za nim (domyślne)
HttpAssert::fetch('https://example.com/redirect', follow: false)
->expectCode(301)
->expectHeader('Location', 'https://example.com/new-url');
# Podążanie za wszystkimi przekierowaniami do ostatecznej odpowiedzi
HttpAssert::fetch('https://example.com/redirect', follow: true)
->expectCode(200)
->expectBody(contains: 'final content');
DomQuery
Tester\DomQuery to klasa rozszerzająca SimpleXMLElement o łatwe wyszukiwanie w HTML albo XML za
pomocą selektorów CSS.
# tworzymy DomQuery z ciągu HTML
$dom = Tester\DomQuery::fromHtml('
<article class="post">
<h1>Title</h1>
<div class="content">Text</div>
</article>
');
# testujemy istnienie elementu selektorami CSS
Assert::true($dom->has('article.post'));
Assert::true($dom->has('h1'));
# znajdujemy elementy jako tablicę obiektów DomQuery
$headings = $dom->find('h1');
Assert::same('Title', (string) $headings[0]);
# testujemy, czy element pasuje do selektora (od wersji 2.5.3)
$content = $dom->find('.content')[0];
Assert::true($content->matches('div'));
Assert::false($content->matches('p'));
# znajdujemy najbliższego przodka pasującego do selektora (od 2.5.5)
$article = $content->closest('.post');
Assert::true($article->matches('article'));
Dla dokumentów XML użyj metody fromXml():
$dom = Tester\DomQuery::fromXml('<catalog><item>First</item></catalog>');
Assert::true($dom->has('item'));
FileMock
Tester\FileMock emuluje pliki w pamięci i ułatwia testowanie kodu używającego funkcji takich jak
fopen(), file_get_contents(), parse_ini_file() i podobnych. Przykład użycia:
# Testowana klasa
class Logger
{
public function __construct(
private string $logFile,
) {
}
public function log(string $message): void
{
file_put_contents($this->logFile, $message . "\n", FILE_APPEND);
}
}
# Nowy pusty plik
$file = Tester\FileMock::create('');
$logger = new Logger($file);
$logger->log('Login');
$logger->log('Logout');
# Testujemy utworzoną treść
Assert::same("Login\nLogout\n", file_get_contents($file));
Opcjonalny drugi parametr $extension ustawia rozszerzenie pliku w wygenerowanym URL, co przydaje się, gdy
testowany kod decyduje na jego podstawie:
$file = Tester\FileMock::create('{"key": "value"}', 'json');
Assert::with()
To nie jest asercja, tylko pomocnik do testowania prywatnych metod i właściwości obiektów.
class Entity
{
private $enabled;
// ...
}
$ent = new Entity;
Assert::with($ent, function () {
Assert::true($this->enabled); // dostępne prywatne $ent->enabled
});
Helpers::purge()
Metoda purge() tworzy podany katalog, a jeśli już istnieje, usuwa całą jego zawartość. Przydaje się do
utworzenia katalogu tymczasowego. Na przykład w tests/bootstrap.php:
@mkdir(__DIR__ . '/tmp'); # @ - katalog może już istnieć
define('TempDir', __DIR__ . '/tmp/' . getmypid());
Tester\Helpers::purge(TempDir);
Environment::lock()
Testy działają równolegle. Czasem jednak potrzebujemy, żeby wykonanie testów się nie nakładało. Typowo testy bazodanowe
wymagają przygotowania zawartości bazy i zapewnienia, że żaden inny test nie będzie w trakcie ich wykonywania ingerował w
bazę. W tych testach używamy Tester\Environment::lock($name, $dir):
Tester\Environment::lock('database', __DIR__ . '/tmp');
Pierwszym parametrem jest nazwa blokady, drugim ścieżka do katalogu przechowywania blokady. Test, który zdobędzie blokadę jako pierwszy, kontynuuje, pozostałe testy muszą czekać na jego zakończenie.
Environment::bypassFinals()
Klasy albo metody oznaczone jako final trudno testować. Wywołanie
Tester\Environment::bypassFinals() na początku testu sprawia, że przy wczytywaniu kodu słowa kluczowe
final są pomijane.
require __DIR__ . '/bootstrap.php';
Tester\Environment::bypassFinals();
class MyClass extends NormallyFinalClass # <-- NormallyFinalClass nie jest już final
{
// ...
}
Environment::setup()
- poprawia czytelność dumpów błędów (wraz z kolorowaniem); w przeciwnym razie wypisywany jest domyślny stos wywołań PHP
- włącza sprawdzanie, czy w teście wywołano asercje; w przeciwnym razie testy bez asercji (np. zapomniane) też przechodzą
- automatycznie uruchamia zbieranie informacji o wykonanym kodzie (gdy używane jest
--coverage) (opisane dalej) - wypisuje na końcu skryptu status OK albo FAILURE
Environment::setupFunctions()
Tworzy globalne funkcje test(), testException(), testNoError(), setUp() i
tearDown(), w które możesz ustrukturyzować swoje testy.
test('test description', function () {
Assert::same(123, foo());
Assert::false(bar());
// ...
});
Environment::VariableRunner
Pozwala ustalić, czy test został uruchomiony bezpośrednio, czy przez Testera.
if (getenv(Tester\Environment::VariableRunner)) {
# uruchomiony przez Testera
} else {
# uruchomiony inaczej
}
Environment::VariableThread
Tester uruchamia testy równolegle w podanej liczbie wątków. Jeśli interesuje nas numer wątku, znajdziemy go w zmiennej środowiskowej:
echo "Running in thread number " . getenv(Tester\Environment::VariableThread);