Tesztek írása
A Nette Testerhez való tesztek írása abban egyedi, hogy minden teszt egy PHP szkript, amelyet önállóan lehet futtatni. Ez nagy potenciált rejt magában. Már amikor a tesztet írod, egyszerűen futtathatod, és megállapíthatod, hogy helyesen működik-e. Ha nem, könnyen lépésenként végigmehetsz rajta az IDE-ben, és keresheted a hibát.
A tesztet akár meg is nyithatod a böngészőben. De mindenekelőtt – azzal, hogy futtatod, végrehajtod a tesztet. Azonnal megtudod, hogy átment-e vagy meghiúsult.
A bevezető fejezetben mutattuk egy igazán triviális tesztet a tömbökkel való munkára. Most már létrehozunk egy saját osztályt, amelyet tesztelni fogunk, bár ez is egyszerű lesz.
Kezdjük egy tipikus könyvtárstruktúrával egy könyvtárhoz vagy projekthez. Fontos elkülöníteni a teszteket a kód többi részétől, például a deployment miatt, mert a teszteket nem akarjuk feltölteni az éles szerverre. A struktúra például ilyen lehet:
├── src/           # a kód, amelyet tesztelni fogunk
│   ├── Rectangle.php
│   └── ...
├── tests/         # tesztek
│   ├── bootstrap.php
│   ├── RectangleTest.php
│   └── ...
├── vendor/
└── composer.json
És most létrehozzuk az egyes fájlokat. Kezdjük a tesztelt osztállyal, amelyet az src/Rectangle.php fájlba
helyezünk:
<?php
class Rectangle
{
	private float $width;
	private float $height;
	public function __construct(float $width, float $height)
	{
		if ($width < 0 || $height < 0) {
			throw new InvalidArgumentException('A méret nem lehet negatív.');
		}
		$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 felelnie a *Test.php vagy *.phpt
maszknak, válasszuk például a RectangleTest.php változatot:
<?php
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
// általános téglalap
$rect = new Rectangle(10, 20);
Assert::same(200.0, $rect->getArea());  # ellenőrizzük a várt eredményeket
Assert::false($rect->isSquare());
Ahogy látod, az ún. assert metódusok, mint az
Assert::same(), arra szolgálnak, hogy megerősítsék, hogy a tényleges érték megfelel a várt értéknek.
Már csak az utolsó lépés van hátra, ez a bootstrap.php fájl. Ez tartalmazza az összes teszthez közös
kódot, például az osztályok autoloadingját, a környezet konfigurálását, ideiglenes könyvtár létrehozását,
segédfüggvényeket és hasonlókat. Minden teszt betölti a bootstrapot, és tovább csak a teszteléssel foglalkozik.
A bootstrap például így nézhet ki:
<?php
require __DIR__ . '/vendor/autoload.php';   # betölti a Composer autoloadert
Tester\Environment::setup();                # Nette Tester inicializálása
// és további konfiguráció (ez csak egy példa, esetünkben nincs rá szükség)
date_default_timezone_set('Europe/Prague');
define('TmpDir', '/tmp/app-tests');
A megadott bootstrap feltételezi, hogy a Composer autoloader képes lesz betölteni a Rectangle.php
osztályt is. Ezt például az autoload szakasz
beállításával lehet elérni a composer.json-ban stb.
A tesztet most futtathatjuk a parancssorból, mint bármely más önálló PHP szkriptet. Az első futtatás felfedi az esetleges szintaktikai hibákat, és ha sehol nincs elírás, kiíródik:
$ php RectangleTest.php
OK
Ha a tesztben az állítást hamisra változtatnánk: Assert::same(123, $rect->getArea());, ez történne:
$ php RectangleTest.php Failed: 200.0 should be 123 in RectangleTest.php(5) Assert::same(123, $rect->getArea()); FAILURE
Tesztek írásakor jó lefedni az összes szélsőséges helyzetet. Például, ha a bemenet nulla, negatív szám, más esetekben például üres string, null stb. Valójában ez arra kényszerít, hogy elgondolkodj, és eldöntsd, hogyan kell a kódnak viselkednie ilyen helyzetekben. A tesztek ezután rögzítik a viselkedést.
Esetünkben a negatív értéknek kivételt kell dobnia, amit az Assert::exception() segítségével ellenőrzünk:
// a szélesség nem lehet negatív
Assert::exception(
	fn() => new Rectangle(-1, 20),
	InvalidArgumentException::class,
	'A méret nem lehet negatív.',
);
És hasonló tesztet adunk hozzá a magassághoz. Végül teszteljük, hogy az isSquare() true-t ad-e
vissza, ha mindkét méret azonos. Próbálja meg gyakorlásként megírni ezeket a teszteket.
Áttekinthetőbb tesztek
A tesztfájl mérete növekedhet, és gyorsan áttekinthetetlenné válhat. Ezért praktikus az egyes tesztelt területeket különálló függvényekbe csoportosítani.
Először egy egyszerűbb, de elegánsabb változatot mutatunk be, a globális test() függvény segítségével.
A Tester nem hozza létre automatikusan, hogy ne legyen ütközés, ha a kódban azonos nevű függvény lenne. Csak a
setupFunctions() metódus hozza létre, amelyet a bootstrap.php fájlban hívjon meg:
Tester\Environment::setup();
Tester\Environment::setupFunctions();
Ezzel a függvénnyel szépen feloszthatjuk a tesztfájlt elnevezett egységekre. Futtatáskor a leírások sorban kiíródnak.
<?php
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
test('általános téglalap', function () {
	$rect = new Rectangle(10, 20);
	Assert::same(200.0, $rect->getArea());
	Assert::false($rect->isSquare());
});
test('általános négyzet', function () {
	$rect = new Rectangle(5, 5);
	Assert::same(25.0, $rect->getArea());
	Assert::true($rect->isSquare());
});
test('a méretek nem lehetnek negatívak', function () {
	Assert::exception(
		fn() => new Rectangle(-1, 20),
        InvalidArgumentException::class,
	);
	Assert::exception(
		fn() => new Rectangle(10, -1),
        InvalidArgumentException::class,
	);
});
Ha minden teszt előtt vagy után kódot kell futtatnia, adja át azt a setUp() ill. tearDown()
függvénynek:
setUp(function () {
	// inicializációs kód, amely minden test() előtt lefut
});
A második változat objektumorientált. Létrehozunk egy ún. TestCase-t, ami egy osztály, ahol az egyes egységeket metódusok képviselik, amelyek nevei test– kezdetűek.
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);
	}
}
// Tesztmetódusok futtatása
(new RectangleTest)->run();
A kivételek tesztelésére ezúttal a @throws annotációt használtuk. Többet a TestCase fejezetben tudhat meg.
Segédfüggvények
A Nette Tester több osztályt és függvényt tartalmaz, amelyek megkönnyíthetik például a HTML dokumentum tartalmának tesztelését, a fájlokkal dolgozó függvények tesztelését és így tovább.
Leírásukat a Segédosztályok oldalon találja.
Annotációk és tesztek kihagyása
A tesztek futtatását befolyásolhatják a fájl elején lévő phpDoc kommentár formájában megadott annotációk. Például így nézhetnek ki:
/**
 * @phpExtension pdo, pdo_pgsql
 * @phpVersion >= 7.2
 */
A megadott annotációk azt mondják, hogy a tesztet csak PHP 7.2 vagy újabb verzióval kell futtatni, és ha a pdo és
pdo_pgsql PHP kiterjesztések jelen vannak. Ezeket az annotációkat a parancssori tesztfuttató veszi figyelembe, amely abban az esetben, ha a
feltételek nem teljesülnek, kihagyja a tesztet, és a kimenetben s – skipped betűvel jelöli.
Azonban a teszt manuális futtatásakor nincs hatásuk.
A tesztet saját feltétel teljesülése alapján is ki lehet hagyni az Environment::skip() segítségével.
Például ez kihagyja a teszteket Windows rendszeren:
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
	Tester\Environment::skip('Requires UNIX.');
}
Könyvtárstruktúra
Javasoljuk, hogy már kicsit nagyobb könyvtáraknál vagy projekteknél ossza fel a teszteket tartalmazó könyvtárat még alkönyvtárakra a tesztelt osztály névtere szerint:
└── tests/
	├── NamespaceOne/
	│   ├── MyClass.getUsers.phpt
	│   ├── MyClass.setUsers.phpt
	│   └── ...
	│
	├── NamespaceTwo/
	│   ├── MyClass.creating.phpt
	│   ├── MyClass.dropping.phpt
	│   └── ...
	│
	├── bootstrap.php
	└── ...
Így futtathatja a teszteket egyetlen névtérből, azaz alkönyvtárból:
tester tests/NamespaceOne
Speciális helyzetek
Az a teszt, amely egyetlen assert metódust sem hív meg, gyanús, és hibásnak minősül:
Error: This test forgets to execute an assertion.
Ha valóban azt szeretné, hogy az assert hívások nélküli teszt érvényesnek minősüljön, hívja meg például az
Assert::true(true)-t.
Szintén félrevezető lehet az exit() és die() használata a teszt hibaüzenettel történő
leállítására. Például az exit('Hiba a kapcsolatban') a tesztet 0 visszatérési értékkel fejezi be, ami
sikert jelez. Használja az Assert::fail('Hiba a kapcsolatban')-t.