Nette Documentation Preview

syntax
Testy pisarskie
***************

.[perex]
Pisanie testów dla Nette Tester jest o tyle wyjątkowe, że każdy test jest skryptem PHP, który może być uruchamiany niezależnie. Kryje się w tym spory potencjał.
Nawet gdy napiszesz test, możesz go po prostu uruchomić i sprawdzić, czy działa poprawnie. Jeśli tak nie jest, możesz łatwo przejść przez to w IDE i poszukać błędu.

Możesz nawet otworzyć test w przeglądarce. Ale co najważniejsze - uruchamiając go, wykonujesz test. Od razu dowiesz się, czy przeszedł, czy nie.

W rozdziale wprowadzającym pokazaliśmy naprawdę trywialny test pracy [z |guide#What-Makes-Tester-Unique] tablicą. Teraz stworzymy własną klasę do testowania, choć ona również będzie prosta.

Zaczniemy od typowej struktury katalogów dla biblioteki lub projektu. Ważne jest, aby oddzielić testy od reszty kodu, na przykład do deploymentu, ponieważ nie chcemy wrzucać testów na żywy serwer. Struktura może być na przykład:

```
├── src/           # kód, který budeme testovat
│   ├── Rectangle.php
│   └── ...
├── tests/         # testy
│   ├── bootstrap.php
│   ├── RectangleTest.php
│   └── ...
├── vendor/
└── composer.json
```

A teraz tworzymy poszczególne pliki. Zaczynamy od testowanej klasy, którą umieszczamy w pliku `src/Rectangle.php`

```php .{file:src/Rectangle.php}
<?php
class Rectangle
{
	private float $width;
	private float $height;

	public function __construct(float $width, float $height)
	{
		if ($width < 0 || $height < 0) {
			throw new InvalidArgumentException('The dimension must not be negative.');
		}
		$this->width = $width;
		$this->height = $height;
	}

	public function getArea(): float
	{
		return $this->width * $this->height;
	}

	public function isSquare(): bool
	{
		return $this->width === $this->height;
	}
}
```

i stworzyć dla niego test. Nazwa pliku testowego powinna odpowiadać masce `*Test.php` lub `*.phpt`, na przykład wybierz `RectangleTest.php`:


```php .{file:tests/RectangleTest.php}
<?php
use Tester\Assert;

require __DIR__ . '/bootstrap.php';

// rodzajowy podłużny
$rect = new Rectangle(10, 20);
Assert::same(200.0, $rect->getArea()); # walidacja oczekiwanych wyników
Assert::false($rect->isSquare());
```

Jak widać, [metody asercji |assertions] takie jak `Assert::same()` są używane do potwierdzenia, że rzeczywista wartość pasuje do wartości oczekiwanej.

Pozostaje ostatni krok, którym jest plik `bootstrap.php`. Zawiera on kod wspólny dla wszystkich testów, taki jak autoloading klas, konfiguracja środowiska, tworzenie katalogu tymczasowego, funkcje pomocnicze i tak dalej. Wszystkie testy ładują bootstrap i kontynuują tylko testowanie. Bootstrap może wyglądać jak poniżej:

```php .{file:tests/bootstrap.php}
<?php
require __DIR__ . '/vendor/autoload.php'; # załaduj autoloader Composera

Tester\Environment::setup();                # zainicjuj Tester Nette

// i inne konfiguracje (to tylko przykład, nie są one potrzebne w naszym przypadku)
date_default_timezone_set('Europe/Prague');
define('TmpDir', '/tmp/app-tests');
```

.[note]
Powyższy bootstrap zakłada, że autoloader Composera będzie w stanie załadować również klasę `Rectangle.php`. Można to zrobić na przykład [poprzez ustawienie sekcji autoload |best-practices:composer#Autoloading] w `composer.json` itp.

Test może być teraz uruchamiany z linii poleceń jak każdy inny samodzielny skrypt PHP. Uruchomienie go po raz pierwszy ujawni wszelkie błędy składniowe, a jeśli nigdzie nie ma literówki, zostanie wydrukowany:

/--pre .[terminal]
$ php RectangleTest.php

<span style="color:#FFF; background-color:#090">OK</span>
\--

Gdybyśmy zmienili stwierdzenie w teście na false `Assert::same(123, $rect->getArea());` to co by się stało:

/--pre .[terminal]
$ php RectangleTest.php

<span style="color: #FFF">Failed: </span><span style="color: #FF0">200.0</span><span style="color: #FFF"> should be </span><span style="color: #FF0">123</span>

<span style="color: #CCC">in </span><span style="color: #FFF">RectangleTest.php(5)</span><span style="color: #808080"> Assert::same(123, $rect->getArea());</span>

<span style="color: #FFF; background-color: #900">FAILURE</span>
\--


Podczas pisania testów dobrze jest objąć wszystkie skrajności. Na przykład, jeśli dane wejściowe są zerowe, liczba ujemna, w innych przypadkach pusty ciąg, null, itp. W rzeczywistości zmusza cię do myślenia i decydowania, jak kod powinien zachowywać się w takich sytuacjach. Testy następnie naprawiają zachowanie.

W naszym przypadku wartość ujemna ma rzucić wyjątek, co sprawdzamy za pomocą [Assert::exception() |Assertions#Assert-exception]:

```php .{file:tests/RectangleTest.php}
// szerokość nie może być ujemna
Assert::exception(
	fn() => new Rectangle(-1, 20),
	InvalidArgumentException::class,
	'Wymiar nie może być ujemny',
);
```

I dodać podobny test dla wzrostu. Na koniec testujemy, że `isSquare()` zwraca `true`, jeśli oba wymiary są takie same. Spróbuj napisać takie testy jako ćwiczenie.


Bardziej przejrzyste testy .[#toc-well-arranged-tests]
======================================================

Rozmiar pliku testowego może rosnąć i szybko stać się niezorganizowany. Dlatego praktycznym rozwiązaniem jest pogrupowanie różnych obszarów testowych w osobne funkcje.

Najpierw pokażemy prostszy, ale elegancki wariant, wykorzystujący funkcję globalną `test()`. Tester nie tworzy go automatycznie, aby uniknąć kolizji, jeśli miałeś funkcję o tej samej nazwie w swoim kodzie. Tworzy go dopiero metoda `setupFunctions()`, którą wywołujesz w pliku `bootstrap.php`:

```php .{file:tests/bootstrap.php}
Tester\Environment::setup();
Tester\Environment::setupFunctions();
```

Dzięki tej funkcji możemy ładnie podzielić plik testowy na nazwane jednostki. Po uruchomieniu etykiety zostaną wymienione jedna po drugiej.

```php .{file:tests/RectangleTest.php}
<?php
use Tester\Assert;

require __DIR__ . '/bootstrap.php';

test('obecný oblong', function () {
	$rect = new Rectangle(10, 20);
	Assert::same(200.0, $rect->getArea());
	Assert::false($rect->isSquare());
});

test('obecný čtverec', function () {
	$rect = new Rectangle(5, 5);
	Assert::same(25.0, $rect->getArea());
	Assert::true($rect->isSquare());
});

test('rozměry nesmí být záporné', function () {
	Assert::exception(
		fn() => new Rectangle(-1, 20),
        InvalidArgumentException::class,
	);

	Assert::exception(
		fn() => new Rectangle(10, -1),
        InvalidArgumentException::class,
	);
});
```

Jeśli potrzebujesz uruchomić kod przed lub po każdym teście, przekaż go do funkcji `setUp()` lub `tearDown()`:

```php
setUp(function () {
	// kod inicjalizacyjny, który uruchamia się przed każdym testem()
});
```

Drugą opcją jest orientacja obiektowa. Tworzymy coś, co nazywa się TestCase, czyli klasę, w której jednostki reprezentują metody, których nazwy zaczynają się od test-.

```php .{file:tests/RectangleTest.php}
class RectangleTest extends Tester\TestCase
{
	public function testGeneralOblong()
	{
		$rect = new Rectangle(10, 20);
		Assert::same(200.0, $rect->getArea());
		Assert::false($rect->isSquare());
	}

	public function testGeneralSquare()
	{
		$rect = new Rectangle(5, 5);
		Assert::same(25.0, $rect->getArea());
		Assert::true($rect->isSquare());
	}

	/** @throws InvalidArgumentException */
	public function testWidthMustNotBeNegative()
	{
		$rect = new Rectangle(-1, 20);
	}

	/** @throws InvalidArgumentException */
	public function testHeightMustNotBeNegative()
	{
		$rect = new Rectangle(10, -1);
	}
}

// Spuścizna testowa metody
(new RectangleTest)->run();
```

Tym razem do testowania wyjątków wykorzystaliśmy adnotację `@throw`. Zobacz rozdział [TestCase |TestCase], aby dowiedzieć się więcej.


Funkcje pomocnicze .[#toc-annotation-and-skipping-tests]
========================================================

Nette Tester zawiera kilka klas i funkcji, które mogą ułatwić Ci np. testowanie zawartości dokumentu HTML, testowanie funkcji pracujących z plikami itd.

Ich opis można znaleźć na stronie [Klasy pomocnicze |helpers].


Dodawanie adnotacji i pomijanie testów .[#toc-anotace-a-preskakovani-testu]
===========================================================================

Na uruchomienie testów mogą mieć wpływ adnotacje w postaci komentarzy phpDoc na początku pliku. Na przykład może to wyglądać tak:

```php .{file:tests/RectangleTest.php}
/**
 * @phpExtension pdo, pdo_pgsql
 * @phpVersion >= 7.2
 */
```

Te adnotacje mówią, że test powinien być uruchomiony tylko z PHP w wersji 7.2 lub wyższej i jeśli rozszerzenia PHP pdo i pdo_pgsql są obecne. Za tymi adnotacjami podąża [runner testów z linii poleceń |running-tests], który pominie test, jeśli warunki nie są spełnione i oznaczy wyjście za pomocą `s` - skipped. Jednak podczas ręcznego uruchamiania testu nie mają one żadnego efektu.

Opis adnotacji znajduje się na stronie Adnotacje do [testów |test-annotations].

Możesz również zlecić pominięcie testu w oparciu o spełnienie niestandardowego warunku, używając `Environment::skip()`. Na przykład pomija to testy w systemie Windows:

```php
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
	Tester\Environment::skip('Requires UNIX.');
}
```


Struktura katalogu .[#toc-adresarova-struktura]
===============================================

Zalecamy, aby w przypadku nieco większych bibliotek lub projektów, katalog testowy był dodatkowo podzielony na podkatalogi według przestrzeni nazw testowanej klasy:

```
└── tests/
	├── NamespaceOne/
	│   ├── MyClass.getUsers.phpt
	│   ├── MyClass.setUsers.phpt
	│   └── ...
	│
	├── NamespaceTwo/
	│   ├── MyClass.creating.phpt
	│   ├── MyClass.dropping.phpt
	│   └── ...
	│
	├── bootstrap.php
	└── ...
```

W ten sposób możesz uruchomić testy z pojedynczej przestrzeni nazw lub podkatalogu:

/--pre .[terminal]
tester tests/NamespaceOne
\--


Sytuacje szczególne .[#toc-specialni-situace]
=============================================

Test, który nie wywołuje żadnej z metod asercji jest podejrzany i zostanie oceniony jako niepowodzenie:

/--pre .[terminal]
<span style="color: #FFF; background-color: #900">Error: This test forgets to execute an assertion.</span>
\--

Jeśli test bez wywoływania asercji ma być naprawdę uznany za ważny, wywołaj na przykład `Assert::true(true)`.

Podstępne może być również użycie `exit()` i `die()`, aby wyjść z testu z komunikatem o błędzie. Na przykład `exit('Error in connection')` zakończy test z kodem zwrotnym 0, oznaczającym sukces. Skorzystaj z `Assert::fail('Error in connection')`.

Testy pisarskie

Pisanie testów dla Nette Tester jest o tyle wyjątkowe, że każdy test jest skryptem PHP, który może być uruchamiany niezależnie. Kryje się w tym spory potencjał. Nawet gdy napiszesz test, możesz go po prostu uruchomić i sprawdzić, czy działa poprawnie. Jeśli tak nie jest, możesz łatwo przejść przez to w IDE i poszukać błędu.

Możesz nawet otworzyć test w przeglądarce. Ale co najważniejsze – uruchamiając go, wykonujesz test. Od razu dowiesz się, czy przeszedł, czy nie.

W rozdziale wprowadzającym pokazaliśmy naprawdę trywialny test pracy z tablicą. Teraz stworzymy własną klasę do testowania, choć ona również będzie prosta.

Zaczniemy od typowej struktury katalogów dla biblioteki lub projektu. Ważne jest, aby oddzielić testy od reszty kodu, na przykład do deploymentu, ponieważ nie chcemy wrzucać testów na żywy serwer. Struktura może być na przykład:

├── src/           # kód, který budeme testovat
│   ├── Rectangle.php
│   └── ...
├── tests/         # testy
│   ├── bootstrap.php
│   ├── RectangleTest.php
│   └── ...
├── vendor/
└── composer.json

A teraz tworzymy poszczególne pliki. Zaczynamy od testowanej klasy, którą umieszczamy w pliku src/Rectangle.php

<?php
class Rectangle
{
	private float $width;
	private float $height;

	public function __construct(float $width, float $height)
	{
		if ($width < 0 || $height < 0) {
			throw new InvalidArgumentException('The dimension must not be negative.');
		}
		$this->width = $width;
		$this->height = $height;
	}

	public function getArea(): float
	{
		return $this->width * $this->height;
	}

	public function isSquare(): bool
	{
		return $this->width === $this->height;
	}
}

i stworzyć dla niego test. Nazwa pliku testowego powinna odpowiadać masce *Test.php lub *.phpt, na przykład wybierz RectangleTest.php:

<?php
use Tester\Assert;

require __DIR__ . '/bootstrap.php';

// rodzajowy podłużny
$rect = new Rectangle(10, 20);
Assert::same(200.0, $rect->getArea()); # walidacja oczekiwanych wyników
Assert::false($rect->isSquare());

Jak widać, metody asercji takie jak Assert::same() są używane do potwierdzenia, że rzeczywista wartość pasuje do wartości oczekiwanej.

Pozostaje ostatni krok, którym jest plik bootstrap.php. Zawiera on kod wspólny dla wszystkich testów, taki jak autoloading klas, konfiguracja środowiska, tworzenie katalogu tymczasowego, funkcje pomocnicze i tak dalej. Wszystkie testy ładują bootstrap i kontynuują tylko testowanie. Bootstrap może wyglądać jak poniżej:

<?php
require __DIR__ . '/vendor/autoload.php'; # załaduj autoloader Composera

Tester\Environment::setup();                # zainicjuj Tester Nette

// i inne konfiguracje (to tylko przykład, nie są one potrzebne w naszym przypadku)
date_default_timezone_set('Europe/Prague');
define('TmpDir', '/tmp/app-tests');

Powyższy bootstrap zakłada, że autoloader Composera będzie w stanie załadować również klasę Rectangle.php. Można to zrobić na przykład poprzez ustawienie sekcji autoload w composer.json itp.

Test może być teraz uruchamiany z linii poleceń jak każdy inny samodzielny skrypt PHP. Uruchomienie go po raz pierwszy ujawni wszelkie błędy składniowe, a jeśli nigdzie nie ma literówki, zostanie wydrukowany:

$ php RectangleTest.php

OK

Gdybyśmy zmienili stwierdzenie w teście na false Assert::same(123, $rect->getArea()); to co by się stało:

$ php RectangleTest.php

Failed: 200.0 should be 123

in RectangleTest.php(5) Assert::same(123, $rect->getArea());

FAILURE

Podczas pisania testów dobrze jest objąć wszystkie skrajności. Na przykład, jeśli dane wejściowe są zerowe, liczba ujemna, w innych przypadkach pusty ciąg, null, itp. W rzeczywistości zmusza cię do myślenia i decydowania, jak kod powinien zachowywać się w takich sytuacjach. Testy następnie naprawiają zachowanie.

W naszym przypadku wartość ujemna ma rzucić wyjątek, co sprawdzamy za pomocą Assert::exception():

// szerokość nie może być ujemna
Assert::exception(
	fn() => new Rectangle(-1, 20),
	InvalidArgumentException::class,
	'Wymiar nie może być ujemny',
);

I dodać podobny test dla wzrostu. Na koniec testujemy, że isSquare() zwraca true, jeśli oba wymiary są takie same. Spróbuj napisać takie testy jako ćwiczenie.

Bardziej przejrzyste testy

Rozmiar pliku testowego może rosnąć i szybko stać się niezorganizowany. Dlatego praktycznym rozwiązaniem jest pogrupowanie różnych obszarów testowych w osobne funkcje.

Najpierw pokażemy prostszy, ale elegancki wariant, wykorzystujący funkcję globalną test(). Tester nie tworzy go automatycznie, aby uniknąć kolizji, jeśli miałeś funkcję o tej samej nazwie w swoim kodzie. Tworzy go dopiero metoda setupFunctions(), którą wywołujesz w pliku bootstrap.php:

Tester\Environment::setup();
Tester\Environment::setupFunctions();

Dzięki tej funkcji możemy ładnie podzielić plik testowy na nazwane jednostki. Po uruchomieniu etykiety zostaną wymienione jedna po drugiej.

<?php
use Tester\Assert;

require __DIR__ . '/bootstrap.php';

test('obecný oblong', function () {
	$rect = new Rectangle(10, 20);
	Assert::same(200.0, $rect->getArea());
	Assert::false($rect->isSquare());
});

test('obecný čtverec', function () {
	$rect = new Rectangle(5, 5);
	Assert::same(25.0, $rect->getArea());
	Assert::true($rect->isSquare());
});

test('rozměry nesmí být záporné', function () {
	Assert::exception(
		fn() => new Rectangle(-1, 20),
        InvalidArgumentException::class,
	);

	Assert::exception(
		fn() => new Rectangle(10, -1),
        InvalidArgumentException::class,
	);
});

Jeśli potrzebujesz uruchomić kod przed lub po każdym teście, przekaż go do funkcji setUp() lub tearDown():

setUp(function () {
	// kod inicjalizacyjny, który uruchamia się przed każdym testem()
});

Drugą opcją jest orientacja obiektowa. Tworzymy coś, co nazywa się TestCase, czyli klasę, w której jednostki reprezentują metody, których nazwy zaczynają się od test-.

class RectangleTest extends Tester\TestCase
{
	public function testGeneralOblong()
	{
		$rect = new Rectangle(10, 20);
		Assert::same(200.0, $rect->getArea());
		Assert::false($rect->isSquare());
	}

	public function testGeneralSquare()
	{
		$rect = new Rectangle(5, 5);
		Assert::same(25.0, $rect->getArea());
		Assert::true($rect->isSquare());
	}

	/** @throws InvalidArgumentException */
	public function testWidthMustNotBeNegative()
	{
		$rect = new Rectangle(-1, 20);
	}

	/** @throws InvalidArgumentException */
	public function testHeightMustNotBeNegative()
	{
		$rect = new Rectangle(10, -1);
	}
}

// Spuścizna testowa metody
(new RectangleTest)->run();

Tym razem do testowania wyjątków wykorzystaliśmy adnotację @throw. Zobacz rozdział TestCase, aby dowiedzieć się więcej.

Funkcje pomocnicze

Nette Tester zawiera kilka klas i funkcji, które mogą ułatwić Ci np. testowanie zawartości dokumentu HTML, testowanie funkcji pracujących z plikami itd.

Ich opis można znaleźć na stronie Klasy pomocnicze.

Dodawanie adnotacji i pomijanie testów

Na uruchomienie testów mogą mieć wpływ adnotacje w postaci komentarzy phpDoc na początku pliku. Na przykład może to wyglądać tak:

/**
 * @phpExtension pdo, pdo_pgsql
 * @phpVersion >= 7.2
 */

Te adnotacje mówią, że test powinien być uruchomiony tylko z PHP w wersji 7.2 lub wyższej i jeśli rozszerzenia PHP pdo i pdo_pgsql są obecne. Za tymi adnotacjami podąża runner testów z linii poleceń, który pominie test, jeśli warunki nie są spełnione i oznaczy wyjście za pomocą s – skipped. Jednak podczas ręcznego uruchamiania testu nie mają one żadnego efektu.

Opis adnotacji znajduje się na stronie Adnotacje do testów.

Możesz również zlecić pominięcie testu w oparciu o spełnienie niestandardowego warunku, używając Environment::skip(). Na przykład pomija to testy w systemie Windows:

if (defined('PHP_WINDOWS_VERSION_BUILD')) {
	Tester\Environment::skip('Requires UNIX.');
}

Struktura katalogu

Zalecamy, aby w przypadku nieco większych bibliotek lub projektów, katalog testowy był dodatkowo podzielony na podkatalogi według przestrzeni nazw testowanej klasy:

└── tests/
	├── NamespaceOne/
	│   ├── MyClass.getUsers.phpt
	│   ├── MyClass.setUsers.phpt
	│   └── ...
	│
	├── NamespaceTwo/
	│   ├── MyClass.creating.phpt
	│   ├── MyClass.dropping.phpt
	│   └── ...
	│
	├── bootstrap.php
	└── ...

W ten sposób możesz uruchomić testy z pojedynczej przestrzeni nazw lub podkatalogu:

tester tests/NamespaceOne

Sytuacje szczególne

Test, który nie wywołuje żadnej z metod asercji jest podejrzany i zostanie oceniony jako niepowodzenie:

Error: This test forgets to execute an assertion.

Jeśli test bez wywoływania asercji ma być naprawdę uznany za ważny, wywołaj na przykład Assert::true(true).

Podstępne może być również użycie exit() i die(), aby wyjść z testu z komunikatem o błędzie. Na przykład exit('Error in connection') zakończy test z kodem zwrotnym 0, oznaczającym sukces. Skorzystaj z Assert::fail('Error in connection').