Asercja

Asercje są używane do potwierdzenia, że rzeczywista wartość pasuje do wartości oczekiwanej. Są to metody klasy Tester\Assert.

Wybierz najbardziej odpowiednie twierdzenia. Jest to lepsze Assert::same($a, $b) niż Assert::true($a === $b), ponieważ wyświetli sensowny komunikat o błędzie, gdy się nie powiedzie. W tym drugim przypadku tylko false should be true, który nie mówi nam nic o zawartości zmiennych $a i $b.

Również większość asercji może mieć opcjonalną etykietę w parametrze $description, która zostanie wyświetlona w komunikacie o błędzie, jeśli oczekiwanie nie powiedzie się.

W przykładach założono, że alias został utworzony:

use Tester\Assert;

Assert::same($expected, $actual, string $description=null)

$expected musi być identyczny z $actual. Identyczny jak operator PHP ===.

Assert::notSame($expected, $actual, string $description=null)

Przeciwieństwo Assert::same(), czyli to samo co operator PHP !==.

Assert::equal($expected, $actual, string $description=null, bool $matchOrder=false, bool $matchIdentity=false)

$expected musi być taka sama jak $actual. W przeciwieństwie do Assert::same(), tożsamość obiektu, kolejność par kluczy ⇒ wartości w polach i marginalnie różne liczby dziesiętne są ignorowane, co można zmienić poprzez ustawienie $matchIdentity i $matchOrder.

Poniższe przypadki są identyczne z perspektywy equal(), ale nie same():

Assert::equal(0.3, 0.1 + 0.2);
Assert::equal($obj, clone $obj);
Assert::equal(
	['first' => 11, 'second' => 22],
	['second' => 22, 'first' => 11],
);

Jednakże, uważaj, pole [1, 2] a [2, 1] nie są takie same, ponieważ tylko kolejność wartości jest inna, a nie pary klucz ⇒ wartość. Pole [1, 2] można również zapisać jako [0 => 1, 1 => 2] i dlatego będą uważane za takie same [1 => 2, 0 => 1].

Ponadto w $expected można wykorzystać tzw. oczekiwania.

Assert::notEqual($expected, $actual, string $description=null)

Przeciwieństwo Assert::equal().

Assert::contains($needle, string|array $actual, string $description=null)

Jeśli $actual jest ciągiem, musi zawierać podłańcuch $needle. Jeśli jest to tablica, musi zawierać element $needle (dopasowany ściśle).

Assert::notContains($needle, string|array $actual, string $description=null)

Przeciwieństwo Assert::contains().

Assert::hasKey(string|int $needle, array $actual, string $description=null)

$actual musi być tablicą i musi zawierać klucz $needle.

Assert::notHasKey(string|int $needle, array $actual, string $description=null)

$actual musi być tablicą i nie może zawierać klucza $needle.

Assert::true($value, string $description=null)

$value musi być true, czyli $value === true.

Assert::truthy($value, string $description=null)

$value musi być prawdziwa, czyli spełnia warunek if ($value) ....

Assert::false($value, string $description=null)

$value musi być false, czyli $value === false.

Assert::falsey($value, string $description=null)

$value musi być fałszywy, czyli spełnia warunek if (!$value) ....

Assert::null($value, string $description=null)

$value musi być null, czyli $value === null.

Assert::notNull($value, string $description=null)

$value nie może być null, czyli $value !== null.

Assert::nan($value, string $description=null)

$value musi być “Not a Number”. Do testowania wartości NAN używaj wyłącznie Assert::nan() Wartość NAN jest bardzo specyficzna i asercje Assert::same() lub Assert::equal() mogą działać nieoczekiwanie.

Assert::count($count, Countable|array $value, string $description=null)

Liczba elementów w $value musi być $count. Zatem taka sama jak count($value) === $count.

Assert::type(string|object $type, $value, string $description=null)

$value musi być danego typu. Jako $type możemy użyć ciągu znaków:

  • array
  • list – tablica indeksowana rosnącym szeregiem kluczy numerycznych od zera
  • bool
  • callable
  • float
  • int
  • null
  • object
  • resource
  • scalar
  • string
  • nazwa klasy lub samego obiektu, to musi być $value instanceof $type

Assert::exception(callable $callable, string $class, string $message=null, $code=null)

Po wywołaniu $callable musi zostać rzucony wyjątek klasowy $class Jeśli podamy $message, komunikat wyjątku musi pasować do wzorca, a jeśli podamy $code, kody muszą być ściśle dopasowane.

Poniższy test kończy się niepowodzeniem, ponieważ komunikat wyjątku nie pasuje:

Assert::exception(
	fn() => throw new App\InvalidValueException('Zero value'),
	App\InvalidValueException::class,
	'Value is to low',
);

Assert::exception() zwraca rzucony wyjątek, więc można przetestować zagnieżdżony wyjątek.

$e = Assert::exception(
	fn() => throw new MyException('Something is wrong', 0, new RuntimeException),
	MyException::class,
	'Something is wrong',
);

Assert::type(RuntimeException::class, $e->getPrevious());

Assert::error(string $callable, int|string|array $type, string $message=null)

Sprawdza, czy funkcja $callable wygenerowała oczekiwane błędy (tj. Ostrzeżenia, powiadomienia itp.). Jako $type podajemy jedną ze stałych E_..., czyli np. E_WARNING. A jeśli określimy $message, to komunikat o błędzie musi pasować do wzorca. Na przykład:

Assert::error(
	fn() => $i++,
	E_NOTICE,
	'Undefined variable: i',
);

Jeśli callback generuje wiele błędów, musimy oczekiwać ich wszystkich w dokładnej kolejności. W tym przypadku przekazujemy pole $type:

Assert::error(function () {
	$a++;
	$b++;
}, [
	[E_NOTICE, 'Undefined variable: a'],
	[E_NOTICE, 'Undefined variable: b'],
]);

Jeśli określisz nazwę klasy jako $type, zachowuje się ona tak samo jak Assert::exception().

Assert::noError(callable $callable)

Sprawdza, czy funkcja $callable nie wygenerowała żadnych ostrzeżeń, błędów lub wyjątków. Jest to przydatne do testowania fragmentów kodu, w których nie ma innej asercji.

Assert::match(string $pattern, $actual, string $description=null)

$actual musi pasować do wzorca $pattern. Możemy użyć dwóch wariantów wzorców: wyrażeń regularnych lub symboli wieloznacznych.

Jeśli przekażemy wyrażenie regularne jako $pattern, musimy użyć ~ nebo # do jego delimitacji, inne delimitery nie są obsługiwane. Na przykład test, w którym $var musi zawierać tylko cyfry szesnastkowe:

Assert::match('#^[0-9a-f]$#i', $var);

Druga opcja jest podobna do dopasowywania ciągów regularnych, ale możemy używać różnych znaków wieloznacznych w $pattern:

  • %a% jeden lub więcej znaków, z wyjątkiem znaków końca linii
  • %a?% żaden lub więcej znaków, z wyjątkiem znaków końca linii
  • %A% jeden lub więcej znaków, w tym znaki końca linii
  • %A?% żaden lub więcej znaków, w tym znaki końca linii
  • %s% jeden lub więcej białych znaków, z wyłączeniem znaków końca linii
  • %s?% żaden lub więcej białych znaków, z wyjątkiem znaków końca linii
  • %S% jeden lub więcej znaków, z wyłączeniem białych znaków
  • %S?% żaden lub więcej znaków, z wyjątkiem białych znaków
  • %c% dowolny znak, z wyjątkiem znaków przerwy w linii
  • %d% jedna lub więcej cyfr
  • %d?% brak lub więcej cyfr
  • %i% podpisana wartość całkowita
  • %f% liczba z kropką dziesiętną
  • %h% jedna lub więcej cyfr szesnastkowych
  • %w% jeden lub więcej znaków alfanumerycznych
  • %% znak %

Przykłady:

# Opět test na hexadecimální číslo
Assert::match('%h%', $var);

# Zobecnění cesty k souboru a čísla řádky
Assert::match('Error in file %a% on line %i%', $errorMessage);

Assert::matchFile(string $file, $actual, string $description=null)

Asercja jest identyczna jak Assert::match(), ale wzór jest odczytywany z $file. Jest to przydatne do testowania bardzo długich łańcuchów. Plik testowy pozostaje przezroczysty.

Assert::fail(string $message, $actual=null, $expected=null)

To twierdzenie zawsze się nie sprawdza. Czasami po prostu się przydaje. Opcjonalnie możemy dołączyć wartość oczekiwaną i rzeczywistą.

Oczekiwania

Gdy chcemy porównywać bardziej złożone struktury z elementami niestałymi, powyższe twierdzenia mogą okazać się niewystarczające. Na przykład testujemy metodę, która tworzy nowego użytkownika i zwraca jego atrybuty jako tablicę. Nie znamy wartości hashowej hasła, ale wiemy, że musi to być ciąg szesnastkowy. A o następnym elemencie wiemy tylko tyle, że musi to być obiekt DateTime.

W takich sytuacjach możemy użyć Tester\Expect wewnątrz parametru $expected metod Assert::equal() i Assert::notEqual(), za pomocą których możemy w prosty sposób opisać strukturę.

use Tester\Expect;

Assert::equal([
	'id' => Expect::type('int'),                   # očekáváme celé číslo
	'username' => 'milo',
	'password' => Expect::match('%h%'),            # očekáváme řetězec vyhovující vzoru
	'created_at' => Expect::type(DateTime::class), # očekáváme instanci třídy
], User::create(123, 'milo', 'RandomPaSsWoRd'));

Za pomocą Expect możemy wykonać prawie takie same asercje jak za pomocą Assert. Tym samym dostępne są dla nas metody Expect::same(), Expect::match(), Expect::count(), itd. Co więcej, możemy je konkatenować:

Expect::type(MyIterator::class)->andCount(5);  # očekáváme MyIterator a počet prvků 5

Możemy też napisać własne assertion handlers.

Expect::that(function ($value) {
	# vrátíme false, pokud očekávání selže
});

Badanie błędnych twierdzeń

Kiedy asercja się nie powiedzie, Tester wymienia, co to za błąd. Przy porównywaniu bardziej złożonych struktur Tester tworzy atrapy porównywanych wartości i przechowuje je w katalogu output. Na przykład, jeśli test manekinów Arrays.recursive.phpt nie powiedzie się, manekiny zostaną zapisane w następujący sposób:

app/
└── tests/
	├── output/
	│   ├── Arrays.recursive.actual    # aktuální hodnota
	│   └── Arrays.recursive.expected  # očekávaná hodnota
	│
	└── Arrays.recursive.phpt          # selhávající test

Nazwę katalogu możemy zmienić poprzez stronę Tester\Dumper::$dumpDir.