Í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.