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.