Nette Documentation Preview

syntax
Írásbeli tesztek
****************

.[perex]
A tesztek írása a Nette Tester számára egyedülálló abban a tekintetben, hogy minden teszt egy PHP szkript, amely önállóan futtatható... Ebben nagy lehetőségek rejlenek.
Ahogy megírja a tesztet, egyszerűen lefuttathatja, hogy lássa, megfelelően működik-e. Ha nem, akkor az IDE-ben könnyen végigmehet rajta, és megkeresheti a hibát.

Akár meg is nyithatja a tesztet egy böngészőben. De mindenekelőtt - a futtatással elvégzi a tesztet. Azonnal megtudod, hogy átment-e vagy nem ment át.

A bevezető fejezetben [mutattunk |guide#What Makes Tester Unique?] egy igazán triviális tesztet a PHP tömb használatával. Most létrehozzuk a saját osztályunkat, amelyet tesztelni fogunk, bár ez is egyszerű lesz.

Kezdjük egy tipikus könyvtár vagy projekt könyvtár elrendezésével. Fontos, hogy a teszteket elkülönítsük a kód többi részétől, például a telepítés miatt, mert nem akarjuk a teszteket feltölteni a szerverre. A struktúra lehet a következő:

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

Most pedig létrehozzuk az egyes fájlokat. A tesztelt osztállyal kezdünk, amelyet a következő fájlban helyezünk el `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;
	}
}
```

És létrehozunk hozzá egy tesztet. A tesztfájl nevének meg kell egyeznie a `*Test.php` vagy a `*.phpt` maszkkal, mi a `RectangleTest.php` változatot választjuk:


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

require __DIR__ . '/bootstrap.php';

// általános hosszúkás
$rect = new Rectangle(10, 20);
Assert::same(200.0, $rect->getArea()); # ellenőrizzük a várt eredményt.
Assert::false($rect->isSquare());
```

Mint látható, az olyan [állítási módszerek |Assertions], mint a `Assert::same()`, arra szolgálnak, hogy egy tényleges érték egyezzen egy elvárt értékkel.

Az utolsó lépés a `bootstrap.php` fájl létrehozása. Ez tartalmazza az összes teszt közös kódját. Például az osztályok automatikus betöltése, a környezet konfigurálása, ideiglenes könyvtár létrehozása, segédprogramok és hasonlók. Minden teszt betölti a bootstrapet és csak a tesztelésre figyel. A bootstrap így nézhet ki:

```php .{file:tests/bootstrap.php}
<?php
require __DIR__ . '/vendor/autoload.php'; # Composer autoloader betöltése

Tester\Environment::setup(); # Nette Tester inicializálása

// és egyéb konfigurációk (csak egy példa, a mi esetünkben nincs rájuk szükség)
date_default_timezone_set('Europe/Prague');
define('TmpDir', '/tmp/app-tests');
```

.[note]
Ez a bootstrap feltételezi, hogy a Composer autoloader képes lesz betölteni a `Rectangle.php` osztályt is. Ezt például a `composer.json`, stb. [autoload szakaszának beállításával |best-practices:composer#autoloading] lehet elérni.

Most már futtathatjuk a tesztet a parancssorból, mint bármely más önálló PHP-szkriptet. Az első futtatás során kiderül, hogy vannak-e szintaktikai hibák, és ha nem elírás történt, akkor látni fogjuk:

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

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

Ha a tesztben megváltoztatjuk az utasítást false `Assert::same(123, $rect->getArea());`, ez fog történni:

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


A tesztek írásakor jó, ha minden szélsőséges helyzetet elkapunk. Például, ha a bemenet nulla, negatív szám, más esetekben üres karakterlánc, null stb. Valójában arra kényszerít, hogy gondolkodjunk és eldöntsük, hogyan viselkedjen a kód ilyen helyzetekben. A tesztek aztán rögzítik a viselkedést.

Esetünkben egy negatív értéknek kivételt kell dobnia, amit az [Assert::exception() |Assertions#Assert::exception] segítségével ellenőrizünk:

```php .{file:tests/RectangleTest.php}
// a szélesség nem lehet negatív szám
Assert::exception(
	fn() => new Rectangle(-1, 20),
	InvalidArgumentException::class,
	'A méret nem lehet negatív.',
);
```

És hozzáadunk egy hasonló tesztet a magasságra. Végül teszteljük, hogy a `isSquare()` a `true` értéket adja vissza, ha mindkét dimenzió megegyezik. Próbáljunk meg gyakorlatként ilyen teszteket írni.


Jól elrendezett tesztek .[#toc-well-arranged-tests]
===================================================

A tesztfájl mérete megnőhet, és gyorsan zsúfolttá válhat. Ezért praktikus az egyes tesztelt területeket külön függvényekbe csoportosítani.

Először egy egyszerűbb, de elegáns változatot mutatunk be, a `test()` globális függvényt használva. A tesztelő nem hozza létre automatikusan, hogy elkerüljük az ütközést, ha a kódunkban volt egy azonos nevű függvény. Csak a `setupFunctions()` metódus hozza létre, amelyet a `bootstrap.php` fájlban hívunk meg:

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

Ezzel a függvénnyel szépen fel tudjuk osztani a tesztfájlt névre szóló egységekre. Végrehajtáskor a címkék egymás után jelennek meg.

```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,
	);
});
```

Ha az egyes tesztek előtt vagy után kell futtatni a kódot, adja át a `setUp()` vagy a `tearDown()` címre:

```php
setUp(function () {
	// inicializáló kód, amely minden egyes teszt() előtt lefut.
});
```

A második változat az objektum. Létrehozzuk az úgynevezett TestCase-t, amely egy olyan osztály, amelyben az egyes egységeket olyan metódusok reprezentálják, amelyek neve test- kezdetű.

```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);
	}
}

// Tesztmódszerek futtatása
(new RectangleTest)->run();
```

Ezúttal a `@throw` megjegyzést használtuk a kivételek tesztelésére. További információért lásd a [TestCase |TestCase] fejezetet.


Segédfunkciók .[#toc-helpers-functions]
=======================================

A Nette Tester számos olyan osztályt és függvényt tartalmaz, amelyek megkönnyíthetik a tesztelést, például a HTML-dokumentum tartalmának teszteléséhez, a fájlokkal való munka funkcióinak teszteléséhez és így tovább.

Ezek leírását a [Súgók |Helpers] oldalon találja.


Megjegyzések és tesztek kihagyása .[#toc-annotation-and-skipping-tests]
=======================================================================

A tesztek végrehajtását befolyásolhatják a fájl elején található phpDoc megjegyzésben szereplő megjegyzések. Ez például így nézhet ki:

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

A megjegyzések szerint a teszt csak a 7.2-es vagy magasabb PHP-verzióval és a pdo és pdo_pgsql PHP-kiterjesztések megléte esetén futtatható. Ezeket az annotációkat a [parancssori tesztfutó |running-tests] vezérli, amely, ha a feltételek nem teljesülnek, kihagyja a tesztet, és `s` betűvel jelöli - kihagyva. A teszt kézi futtatásakor azonban nincs hatásuk.

A megjegyzések leírását lásd a [Teszt megjegyzések |Test Annotations] című fejezetben.

A teszt saját feltétel alapján is kihagyható a `Environment::skip()` segítségével. Például Windows esetén kihagyjuk ezt a tesztet:

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


Könyvtárszerkezet .[#toc-directory-structure]
=============================================

Csak kicsit nagyobb könyvtárak vagy projektek esetén javasoljuk, hogy a tesztkönyvtárat a tesztelt osztály névterének megfelelően osszuk alkönyvtárakra:

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

A teszteket egyetlen névtérből, azaz alkönyvtárból tudja majd futtatni:

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


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

Egy olyan teszt, amely nem hív meg egyetlen állítás metódust sem, gyanús, és hibásan kerül kiértékelésre:

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

Ha az állítások meghívása nélküli tesztet valóban érvényesnek akarjuk tekinteni, hívjuk meg például a `Assert::true(true)`.

Árulkodó lehet a `exit()` és a `die()` használata is, ha a tesztet hibaüzenettel zárjuk le. A `exit('Error in connection')` például 0 kilépési kóddal fejezi be a tesztet, ami sikert jelez. Használja a `Assert::fail('Error in connection')` címet.

Írásbeli tesztek

A tesztek írása a Nette Tester számára egyedülálló abban a tekintetben, hogy minden teszt egy PHP szkript, amely önállóan futtatható… Ebben nagy lehetőségek rejlenek. Ahogy megírja a tesztet, egyszerűen lefuttathatja, hogy lássa, megfelelően működik-e. Ha nem, akkor az IDE-ben könnyen végigmehet rajta, és megkeresheti a hibát.

Akár meg is nyithatja a tesztet egy böngészőben. De mindenekelőtt – a futtatással elvégzi a tesztet. Azonnal megtudod, hogy átment-e vagy nem ment át.

A bevezető fejezetben mutattunk egy igazán triviális tesztet a PHP tömb használatával. Most létrehozzuk a saját osztályunkat, amelyet tesztelni fogunk, bár ez is egyszerű lesz.

Kezdjük egy tipikus könyvtár vagy projekt könyvtár elrendezésével. Fontos, hogy a teszteket elkülönítsük a kód többi részétől, például a telepítés miatt, mert nem akarjuk a teszteket feltölteni a szerverre. A struktúra lehet a következő:

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

Most pedig létrehozzuk az egyes fájlokat. A tesztelt osztállyal kezdünk, amelyet a következő fájlban helyezünk el 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;
	}
}

És létrehozunk hozzá egy tesztet. A tesztfájl nevének meg kell egyeznie a *Test.php vagy a *.phpt maszkkal, mi a RectangleTest.php változatot választjuk:

<?php
use Tester\Assert;

require __DIR__ . '/bootstrap.php';

// általános hosszúkás
$rect = new Rectangle(10, 20);
Assert::same(200.0, $rect->getArea()); # ellenőrizzük a várt eredményt.
Assert::false($rect->isSquare());

Mint látható, az olyan állítási módszerek, mint a Assert::same(), arra szolgálnak, hogy egy tényleges érték egyezzen egy elvárt értékkel.

Az utolsó lépés a bootstrap.php fájl létrehozása. Ez tartalmazza az összes teszt közös kódját. Például az osztályok automatikus betöltése, a környezet konfigurálása, ideiglenes könyvtár létrehozása, segédprogramok és hasonlók. Minden teszt betölti a bootstrapet és csak a tesztelésre figyel. A bootstrap így nézhet ki:

<?php
require __DIR__ . '/vendor/autoload.php'; # Composer autoloader betöltése

Tester\Environment::setup(); # Nette Tester inicializálása

// és egyéb konfigurációk (csak egy példa, a mi esetünkben nincs rájuk szükség)
date_default_timezone_set('Europe/Prague');
define('TmpDir', '/tmp/app-tests');

Ez a bootstrap feltételezi, hogy a Composer autoloader képes lesz betölteni a Rectangle.php osztályt is. Ezt például a composer.json, stb. autoload szakaszának beállításával lehet elérni.

Most már futtathatjuk a tesztet a parancssorból, mint bármely más önálló PHP-szkriptet. Az első futtatás során kiderül, hogy vannak-e szintaktikai hibák, és ha nem elírás történt, akkor látni fogjuk:

$ php RectangleTest.php

OK

Ha a tesztben megváltoztatjuk az utasítást false Assert::same(123, $rect->getArea());, ez fog történni:

$ php RectangleTest.php

Failed: 200.0 should be 123

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

FAILURE

A tesztek írásakor jó, ha minden szélsőséges helyzetet elkapunk. Például, ha a bemenet nulla, negatív szám, más esetekben üres karakterlánc, null stb. Valójában arra kényszerít, hogy gondolkodjunk és eldöntsük, hogyan viselkedjen a kód ilyen helyzetekben. A tesztek aztán rögzítik a viselkedést.

Esetünkben egy negatív értéknek kivételt kell dobnia, amit az Assert::exception() segítségével ellenőrizünk:

// a szélesség nem lehet negatív szám
Assert::exception(
	fn() => new Rectangle(-1, 20),
	InvalidArgumentException::class,
	'A méret nem lehet negatív.',
);

És hozzáadunk egy hasonló tesztet a magasságra. Végül teszteljük, hogy a isSquare() a true értéket adja vissza, ha mindkét dimenzió megegyezik. Próbáljunk meg gyakorlatként ilyen teszteket írni.

Jól elrendezett tesztek

A tesztfájl mérete megnőhet, és gyorsan zsúfolttá válhat. Ezért praktikus az egyes tesztelt területeket külön függvényekbe csoportosítani.

Először egy egyszerűbb, de elegáns változatot mutatunk be, a test() globális függvényt használva. A tesztelő nem hozza létre automatikusan, hogy elkerüljük az ütközést, ha a kódunkban volt egy azonos nevű függvény. Csak a setupFunctions() metódus hozza létre, amelyet a bootstrap.php fájlban hívunk meg:

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

Ezzel a függvénnyel szépen fel tudjuk osztani a tesztfájlt névre szóló egységekre. Végrehajtáskor a címkék egymás után jelennek meg.

<?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,
	);
});

Ha az egyes tesztek előtt vagy után kell futtatni a kódot, adja át a setUp() vagy a tearDown() címre:

setUp(function () {
	// inicializáló kód, amely minden egyes teszt() előtt lefut.
});

A második változat az objektum. Létrehozzuk az úgynevezett TestCase-t, amely egy olyan osztály, amelyben az egyes egységeket olyan metódusok reprezentálják, amelyek neve test- kezdetű.

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);
	}
}

// Tesztmódszerek futtatása
(new RectangleTest)->run();

Ezúttal a @throw megjegyzést használtuk a kivételek tesztelésére. További információért lásd a TestCase fejezetet.

Segédfunkciók

A Nette Tester számos olyan osztályt és függvényt tartalmaz, amelyek megkönnyíthetik a tesztelést, például a HTML-dokumentum tartalmának teszteléséhez, a fájlokkal való munka funkcióinak teszteléséhez és így tovább.

Ezek leírását a Súgók oldalon találja.

Megjegyzések és tesztek kihagyása

A tesztek végrehajtását befolyásolhatják a fájl elején található phpDoc megjegyzésben szereplő megjegyzések. Ez például így nézhet ki:

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

A megjegyzések szerint a teszt csak a 7.2-es vagy magasabb PHP-verzióval és a pdo és pdo_pgsql PHP-kiterjesztések megléte esetén futtatható. Ezeket az annotációkat a parancssori tesztfutó vezérli, amely, ha a feltételek nem teljesülnek, kihagyja a tesztet, és s betűvel jelöli – kihagyva. A teszt kézi futtatásakor azonban nincs hatásuk.

A megjegyzések leírását lásd a Teszt megjegyzések című fejezetben.

A teszt saját feltétel alapján is kihagyható a Environment::skip() segítségével. Például Windows esetén kihagyjuk ezt a tesztet:

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

Könyvtárszerkezet

Csak kicsit nagyobb könyvtárak vagy projektek esetén javasoljuk, hogy a tesztkönyvtárat a tesztelt osztály névterének megfelelően osszuk alkönyvtárakra:

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

A teszteket egyetlen névtérből, azaz alkönyvtárból tudja majd futtatni:

tester tests/NamespaceOne

Edge Cases

Egy olyan teszt, amely nem hív meg egyetlen állítás metódust sem, gyanús, és hibásan kerül kiértékelésre:

Error: This test forgets to execute an assertion.

Ha az állítások meghívása nélküli tesztet valóban érvényesnek akarjuk tekinteni, hívjuk meg például a Assert::true(true).

Árulkodó lehet a exit() és a die() használata is, ha a tesztet hibaüzenettel zárjuk le. A exit('Error in connection') például 0 kilépési kóddal fejezi be a tesztet, ami sikert jelez. Használja a Assert::fail('Error in connection') címet.