Nette Documentation Preview

syntax
Teste de scriere
****************

.[perex]
Scrierea testelor pentru Nette Tester este unică prin faptul că fiecare test este un script PHP care poate fi rulat de sine stătător.. Acest lucru are un mare potențial.
Pe măsură ce scrieți testul, îl puteți executa pur și simplu pentru a vedea dacă funcționează corect. Dacă nu, puteți trece cu ușurință prin în IDE și căutați un bug.

Puteți chiar să deschideți testul într-un browser. Dar, mai presus de toate - executându-l, veți efectua testul. Veți afla imediat dacă a trecut sau a eșuat.

În capitolul introductiv, am [arătat |guide#What Makes Tester Unique?] un test foarte banal de utilizare a unui array PHP. Acum vom crea propria noastră clasă, pe care o vom testa, deși va fi, de asemenea, simplă.

Să începem cu o dispunere tipică a directoarelor pentru o bibliotecă sau un proiect. Este important să separăm testele de restul codului, de exemplu din cauza implementării, deoarece nu dorim să încărcăm testele pe server. Structura poate fi după cum urmează:

```
├── src/           # code that we will test
│   ├── Rectangle.php
│   └── ...
├── tests/         # tests
│   ├── bootstrap.php
│   ├── RectangleTest.php
│   └── ...
├── vendor/
└── composer.json
```

Și acum vom crea fișiere individuale. Vom începe cu clasa testată, pe care o vom plasa în fișierul `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 vom crea un test pentru ea. Numele fișierului de test trebuie să se potrivească cu masca `*Test.php` sau `*.phpt`, noi vom alege varianta `RectangleTest.php`:


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

require __DIR__ . '/bootstrap.php';

// general alungit
$rect = new Rectangle(10, 20);
Assert::same(200.0, $rect->getArea());  # we will verify the expected results
Assert::false($rect->isSquare());
```

După cum puteți vedea, [metodele de aserțiune |Assertions], cum ar fi `Assert::same()`, sunt utilizate pentru a afirma că o valoare reală corespunde unei valori așteptate.

Ultimul pas este crearea fișierului `bootstrap.php`. Acesta conține un cod comun pentru toate testele. De exemplu, clasele de încărcare automată, configurarea mediului, crearea de directoare temporare, ajutoare și altele similare. Fiecare test încarcă bootstrap-ul și acordă atenție doar testării. Bootstrap-ul poate arăta astfel:

```php .{file:tests/bootstrap.php}
<?php
require __DIR__ . '/vendor/autoload.php';  # load Composer autoloader

Tester\Environment::setup();               # initialization of Nette Tester

// și alte configurații (doar un exemplu, în cazul nostru nu sunt necesare)
date_default_timezone_set('Europe/Prague');
define('TmpDir', '/tmp/app-tests');
```

.[note]
Acest bootstrap presupune că autoloaderul Composer va fi capabil să încarce și clasa `Rectangle.php`. Acest lucru poate fi realizat, de exemplu, prin setarea [secțiunii autoload |best-practices:composer#autoloading] în `composer.json`, etc.

Acum putem rula testul din linia de comandă ca orice alt script PHP independent. Prima rulare va dezvălui orice erori de sintaxă, iar dacă nu ați făcut o greșeală de scriere, veți vedea:

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

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

Dacă schimbăm în test declarația în fals `Assert::same(123, $rect->getArea());`, se va întâmpla acest lucru:

/--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>
\--


Atunci când se scriu teste, este bine să se surprindă toate situațiile extreme. De exemplu, dacă intrarea este zero, un număr negativ, în alte cazuri un șir gol, null etc. De fapt, acest lucru vă obligă să vă gândiți și să decideți cum ar trebui să se comporte codul în astfel de situații. Testele fixează apoi comportamentul.

În cazul nostru, o valoare negativă ar trebui să arunce o excepție, pe care o verificăm cu [Assert::exception() |Assertions#Assert::exception]:

```php .{file:tests/RectangleTest.php}
// lățimea nu trebuie să fie un număr negativ
Assert::exception(
	fn() => new Rectangle(-1, 20),
	InvalidArgumentException::class,
	'The dimension must not be negative.',
);
```

Și adăugăm un test similar pentru înălțime. În cele din urmă, testăm că `isSquare()` returnează `true` dacă ambele dimensiuni sunt identice. Încercați să scrieți astfel de teste ca exercițiu.


Teste bine aranjate .[#toc-well-arranged-tests]
===============================================

Dimensiunea fișierului de test poate crește și poate deveni rapid aglomerat. Prin urmare, este practic să se grupeze domeniile testate individual în funcții separate.

Mai întâi, vom prezenta o variantă mai simplă, dar elegantă, utilizând funcția globală `test()`. Testerul nu o creează automat, pentru a evita o coliziune în cazul în care ați avut o funcție cu același nume în codul dumneavoastră. Ea este creată doar de metoda `setupFunctions()`, pe care o apelați în fișierul `bootstrap.php`:

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

Folosind această funcție, putem împărți frumos fișierul de testare în unități denumite. La execuție, etichetele vor fi afișate una după alta.

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

require __DIR__ . '/bootstrap.php';

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

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

test('dimensions must not be negative', function () {
	Assert::exception(
		fn() => new Rectangle(-1, 20),
        InvalidArgumentException::class,
	);

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

Dacă aveți nevoie să executați codul înainte sau după fiecare test, treceți-l la `setUp()` sau `tearDown()`:

```php
setUp(function () {
	// codul de inițializare care trebuie executat înainte de fiecare test()
});
```

A doua variantă este obiect. Vom crea așa-numitul TestCase, care este o clasă în care unitățile individuale sunt reprezentate de metode ale căror nume încep cu 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);
	}
}

// Executarea metodelor de testare
(new RectangleTest)->run();
```

De data aceasta am folosit o adnotare `@throw` pentru a testa excepțiile. Pentru mai multe informații, consultați capitolul [TestCase |TestCase].


Funcții ajutătoare .[#toc-helpers-functions]
============================================

Nette Tester include mai multe clase și funcții care vă pot ușura testarea, de exemplu, ajutători pentru a testa conținutul unui document HTML, pentru a testa funcțiile de lucru cu fișiere și așa mai departe.

Puteți găsi o descriere a acestora pe pagina [Helpers |Helpers].


Adnotare și săritură de teste .[#toc-annotation-and-skipping-tests]
===================================================================

Executarea testelor poate fi afectată de adnotările din comentariul phpDoc de la începutul fișierului. De exemplu, ar putea arăta astfel:

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

Adnotările spun că testul ar trebui să fie executat numai cu versiunea PHP 7.2 sau mai mare și dacă extensiile PHP pdo și pdo_pgsql sunt prezente. Aceste adnotări sunt controlate de [linia de comandă test runner |running-tests], care, în cazul în care condițiile nu sunt îndeplinite, sare testul și îl marchează cu litera `s` - skipped. Cu toate acestea, ele nu au niciun efect atunci când testul este rulat manual.

Pentru o descriere a adnotărilor, consultați secțiunea [Adnotări de test |Test Annotations].

Testul poate fi, de asemenea, sărit pe baza unei condiții proprii cu `Environment::skip()`. De exemplu, vom sări peste acest test pe Windows:

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


Structura directoarelor .[#toc-directory-structure]
===================================================

Pentru biblioteci sau proiecte puțin mai mari, se recomandă împărțirea directorului de testare în subdirectoare în funcție de spațiul de nume al clasei testate:

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

Veți putea executa testele dintr-un singur subdirectorat namespace ie:

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


Edge Cases .[#toc-edge-cases]
=============================

Un test care nu apelează nicio metodă de aserțiune este suspect și va fi evaluat ca fiind eronat:

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

Dacă testul fără apelarea aserțiunilor trebuie într-adevăr să fie considerat valid, apelați, de exemplu, `Assert::true(true)`.

De asemenea, poate fi înșelător să se utilizeze `exit()` și `die()` pentru a încheia testul cu un mesaj de eroare. De exemplu, `exit('Error in connection')` încheie testul cu un cod de ieșire 0, care semnalează succesul. Utilizați `Assert::fail('Error in connection')`.

Teste de scriere

Scrierea testelor pentru Nette Tester este unică prin faptul că fiecare test este un script PHP care poate fi rulat de sine stătător.. Acest lucru are un mare potențial. Pe măsură ce scrieți testul, îl puteți executa pur și simplu pentru a vedea dacă funcționează corect. Dacă nu, puteți trece cu ușurință prin în IDE și căutați un bug.

Puteți chiar să deschideți testul într-un browser. Dar, mai presus de toate – executându-l, veți efectua testul. Veți afla imediat dacă a trecut sau a eșuat.

În capitolul introductiv, am arătat un test foarte banal de utilizare a unui array PHP. Acum vom crea propria noastră clasă, pe care o vom testa, deși va fi, de asemenea, simplă.

Să începem cu o dispunere tipică a directoarelor pentru o bibliotecă sau un proiect. Este important să separăm testele de restul codului, de exemplu din cauza implementării, deoarece nu dorim să încărcăm testele pe server. Structura poate fi după cum urmează:

├── src/           # code that we will test
│   ├── Rectangle.php
│   └── ...
├── tests/         # tests
│   ├── bootstrap.php
│   ├── RectangleTest.php
│   └── ...
├── vendor/
└── composer.json

Și acum vom crea fișiere individuale. Vom începe cu clasa testată, pe care o vom plasa în fișierul 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 vom crea un test pentru ea. Numele fișierului de test trebuie să se potrivească cu masca *Test.php sau *.phpt, noi vom alege varianta RectangleTest.php:

<?php
use Tester\Assert;

require __DIR__ . '/bootstrap.php';

// general alungit
$rect = new Rectangle(10, 20);
Assert::same(200.0, $rect->getArea());  # we will verify the expected results
Assert::false($rect->isSquare());

După cum puteți vedea, metodele de aserțiune, cum ar fi Assert::same(), sunt utilizate pentru a afirma că o valoare reală corespunde unei valori așteptate.

Ultimul pas este crearea fișierului bootstrap.php. Acesta conține un cod comun pentru toate testele. De exemplu, clasele de încărcare automată, configurarea mediului, crearea de directoare temporare, ajutoare și altele similare. Fiecare test încarcă bootstrap-ul și acordă atenție doar testării. Bootstrap-ul poate arăta astfel:

<?php
require __DIR__ . '/vendor/autoload.php';  # load Composer autoloader

Tester\Environment::setup();               # initialization of Nette Tester

// și alte configurații (doar un exemplu, în cazul nostru nu sunt necesare)
date_default_timezone_set('Europe/Prague');
define('TmpDir', '/tmp/app-tests');

Acest bootstrap presupune că autoloaderul Composer va fi capabil să încarce și clasa Rectangle.php. Acest lucru poate fi realizat, de exemplu, prin setarea secțiunii autoload în composer.json, etc.

Acum putem rula testul din linia de comandă ca orice alt script PHP independent. Prima rulare va dezvălui orice erori de sintaxă, iar dacă nu ați făcut o greșeală de scriere, veți vedea:

$ php RectangleTest.php

OK

Dacă schimbăm în test declarația în fals Assert::same(123, $rect->getArea());, se va întâmpla acest lucru:

$ php RectangleTest.php

Failed: 200.0 should be 123

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

FAILURE

Atunci când se scriu teste, este bine să se surprindă toate situațiile extreme. De exemplu, dacă intrarea este zero, un număr negativ, în alte cazuri un șir gol, null etc. De fapt, acest lucru vă obligă să vă gândiți și să decideți cum ar trebui să se comporte codul în astfel de situații. Testele fixează apoi comportamentul.

În cazul nostru, o valoare negativă ar trebui să arunce o excepție, pe care o verificăm cu Assert::exception():

// lățimea nu trebuie să fie un număr negativ
Assert::exception(
	fn() => new Rectangle(-1, 20),
	InvalidArgumentException::class,
	'The dimension must not be negative.',
);

Și adăugăm un test similar pentru înălțime. În cele din urmă, testăm că isSquare() returnează true dacă ambele dimensiuni sunt identice. Încercați să scrieți astfel de teste ca exercițiu.

Teste bine aranjate

Dimensiunea fișierului de test poate crește și poate deveni rapid aglomerat. Prin urmare, este practic să se grupeze domeniile testate individual în funcții separate.

Mai întâi, vom prezenta o variantă mai simplă, dar elegantă, utilizând funcția globală test(). Testerul nu o creează automat, pentru a evita o coliziune în cazul în care ați avut o funcție cu același nume în codul dumneavoastră. Ea este creată doar de metoda setupFunctions(), pe care o apelați în fișierul bootstrap.php:

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

Folosind această funcție, putem împărți frumos fișierul de testare în unități denumite. La execuție, etichetele vor fi afișate una după alta.

<?php
use Tester\Assert;

require __DIR__ . '/bootstrap.php';

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

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

test('dimensions must not be negative', function () {
	Assert::exception(
		fn() => new Rectangle(-1, 20),
        InvalidArgumentException::class,
	);

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

Dacă aveți nevoie să executați codul înainte sau după fiecare test, treceți-l la setUp() sau tearDown():

setUp(function () {
	// codul de inițializare care trebuie executat înainte de fiecare test()
});

A doua variantă este obiect. Vom crea așa-numitul TestCase, care este o clasă în care unitățile individuale sunt reprezentate de metode ale căror nume încep cu 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);
	}
}

// Executarea metodelor de testare
(new RectangleTest)->run();

De data aceasta am folosit o adnotare @throw pentru a testa excepțiile. Pentru mai multe informații, consultați capitolul TestCase.

Funcții ajutătoare

Nette Tester include mai multe clase și funcții care vă pot ușura testarea, de exemplu, ajutători pentru a testa conținutul unui document HTML, pentru a testa funcțiile de lucru cu fișiere și așa mai departe.

Puteți găsi o descriere a acestora pe pagina Helpers.

Adnotare și săritură de teste

Executarea testelor poate fi afectată de adnotările din comentariul phpDoc de la începutul fișierului. De exemplu, ar putea arăta astfel:

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

Adnotările spun că testul ar trebui să fie executat numai cu versiunea PHP 7.2 sau mai mare și dacă extensiile PHP pdo și pdo_pgsql sunt prezente. Aceste adnotări sunt controlate de linia de comandă test runner, care, în cazul în care condițiile nu sunt îndeplinite, sare testul și îl marchează cu litera s – skipped. Cu toate acestea, ele nu au niciun efect atunci când testul este rulat manual.

Pentru o descriere a adnotărilor, consultați secțiunea Adnotări de test.

Testul poate fi, de asemenea, sărit pe baza unei condiții proprii cu Environment::skip(). De exemplu, vom sări peste acest test pe Windows:

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

Structura directoarelor

Pentru biblioteci sau proiecte puțin mai mari, se recomandă împărțirea directorului de testare în subdirectoare în funcție de spațiul de nume al clasei testate:

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

Veți putea executa testele dintr-un singur subdirectorat namespace ie:

tester tests/NamespaceOne

Edge Cases

Un test care nu apelează nicio metodă de aserțiune este suspect și va fi evaluat ca fiind eronat:

Error: This test forgets to execute an assertion.

Dacă testul fără apelarea aserțiunilor trebuie într-adevăr să fie considerat valid, apelați, de exemplu, Assert::true(true).

De asemenea, poate fi înșelător să se utilizeze exit() și die() pentru a încheia testul cu un mesaj de eroare. De exemplu, exit('Error in connection') încheie testul cu un cod de ieșire 0, care semnalează succesul. Utilizați Assert::fail('Error in connection').