Scrierea testelor
Scrierea testelor pentru Nette Tester este unică prin faptul că fiecare test este un script PHP care poate fi rulat independent. Acest lucru ascunde un potențial mare. Deja când scrieți testul, îl puteți rula simplu și afla dacă funcționează corect. Dacă nu, îl puteți depana ușor pas cu pas în IDE și căuta eroarea.
Puteți chiar deschide testul în browser. Dar, mai presus de toate – prin rularea sa, efectuați testul. Aflați imediat dacă a trecut sau a eșuat.
În capitolul introductiv am demonstrat un test cu adevărat trivial pentru lucrul cu array-uri. Acum vom crea propria noastră clasă pe care o vom testa, deși va fi și ea simplă.
Începem de la structura tipică de directoare pentru o bibliotecă sau un proiect. Este important să separăm testele de restul codului, de exemplu, din cauza deployment-ului, deoarece nu dorim să încărcăm testele pe serverul de producție. Structura poate fi, de exemplu, astfel:
├── src/ # codul pe care îl vom testa
│ ├── Rectangle.php
│ └── ...
├── tests/ # testele
│ ├── bootstrap.php
│ ├── RectangleTest.php
│ └── ...
├── vendor/
└── composer.json
Și acum creăm fișierele individuale. Începem de la clasa testată, pe care o plasăm î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 creăm un test pentru ea. Numele fișierului cu testul ar trebui să corespundă măștii *Test.php
sau
*.phpt
, alegem, de exemplu, varianta RectangleTest.php
:
<?php
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
// dreptunghi general
$rect = new Rectangle(10, 20);
Assert::same(200.0, $rect->getArea()); # verificăm rezultatele așteptate
Assert::false($rect->isSquare());
După cum vedeți, așa-numitele metode de aserțiune precum
Assert::same()
sunt folosite pentru a confirma că valoarea reală corespunde valorii așteptate.
Rămâne ultimul pas și anume fișierul bootstrap.php
. Acesta conține cod comun pentru toate testele, de
exemplu, autoloading-ul claselor, configurarea mediului, crearea unui director temporar, funcții ajutătoare și altele asemenea.
Toate testele încarcă bootstrap-ul și apoi se dedică doar testării. Bootstrap-ul poate arăta astfel:
<?php
require __DIR__ . '/vendor/autoload.php'; # încarcă autoloader-ul Composer
Tester\Environment::setup(); # inițializarea Nette Tester
// și altă configurare (este doar un exemplu, în cazul nostru nu sunt necesare)
date_default_timezone_set('Europe/Prague');
define('TmpDir', '/tmp/app-tests');
Bootstrap-ul menționat presupune că autoloader-ul 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.
Putem rula acum testul din linia de comandă ca orice alt script PHP independent. Prima rulare ne va dezvălui eventualele erori de sintaxă și dacă nu există nicio greșeală de tipar, se va afișa:
$ php RectangleTest.php
OK
Dacă am schimba în test afirmația într-una falsă Assert::same(123, $rect->getArea());
, se va
întâmpla asta:
$ php RectangleTest.php Failed: 200.0 should be 123 in RectangleTest.php(5) Assert::same(123, $rect->getArea()); FAILURE
La scrierea testelor este bine să acoperim toate situațiile limită. De exemplu, când intrarea va fi zero, un număr negativ, în alte cazuri, de exemplu, un șir gol, null etc. De fapt, vă obligă să vă gândiți și să decideți cum ar trebui să se comporte codul în astfel de situații. Testele apoi fixează comportamentul.
În cazul nostru, o valoare negativă ar trebui să arunce o excepție, ceea ce verificăm folosind Assert::exception():
// lățimea nu trebuie să fie 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 final, testăm că isSquare()
returnează true
,
dacă ambele dimensiuni sunt egale. Încercați să scrieți astfel de teste ca exercițiu.
Teste mai lizibile
Dimensiunea fișierului cu testul poate crește și devine rapid neclar. De aceea, este practic să grupăm zonele testate individuale în funcții separate.
Mai întâi vom arăta o variantă mai simplă, dar elegantă, și anume folosind funcția globală test()
.
Tester nu o creează automat, pentru a evita coliziunea, dacă ați avea în cod o funcție cu același nume. O creează abia
metoda setupFunctions()
, pe care o apelați în fișierul bootstrap.php
:
Tester\Environment::setup();
Tester\Environment::setupFunctions();
Cu ajutorul acestei funcții putem împărți frumos fișierul de testare în unități numite. La rulare, descrierile se vor afișa succesiv.
<?php
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
test('dreptunghi general', function () {
$rect = new Rectangle(10, 20);
Assert::same(200.0, $rect->getArea());
Assert::false($rect->isSquare());
});
test('pătrat general', function () {
$rect = new Rectangle(5, 5);
Assert::same(25.0, $rect->getArea());
Assert::true($rect->isSquare());
});
test('dimensiunile nu trebuie să fie 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ă rulați cod înainte sau după fiecare test, transmiteți-l funcției setUp()
respectiv
tearDown()
:
setUp(function () {
// cod de inițializare care se rulează înainte de fiecare test()
});
A doua variantă este orientată pe obiecte. Ne creăm așa-numitul TestCase, care este o clasă în care unitățile individuale reprezintă 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);
}
}
// Rularea metodelor de testare
(new RectangleTest)->run();
Pentru testarea excepțiilor am folosit de data aceasta adnotarea @throws
. Veți afla mai multe în capitolul TestCase.
Funcții ajutătoare
Nette Tester conține câteva clase și funcții care vă pot facilita, de exemplu, testarea conținutului documentului HTML, testarea funcțiilor care lucrează cu fișiere și așa mai departe.
Descrierea lor o găsiți pe pagina Clase ajutătoare.
Adnotări și omiterea testelor
Rularea testelor poate fi influențată de adnotări sub forma unui comentariu phpDoc la începutul fișierului. Poate arăta, de exemplu, astfel:
/**
* @phpExtension pdo, pdo_pgsql
* @phpVersion >= 7.2
*/
Adnotările specificate spun că testul trebuie rulat doar cu versiunea PHP 7.2 sau mai recentă și dacă sunt prezente
extensiile PHP pdo și pdo_pgsql. De aceste adnotări se ghidează rulatorul
de teste din linia de comandă, care, în cazul în care condițiile nu sunt îndeplinite, omite testul și în afișare îl
marchează cu litera s
– skipped. Însă, la rularea manuală a testului, nu au niciun efect.
Descrierea adnotărilor o găsiți pe pagina Adnotări de test.
Testul poate fi lăsat să fie omis și pe baza îndeplinirii unei condiții proprii folosind Environment::skip()
.
De exemplu, aceasta omite testele pe Windows:
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
Tester\Environment::skip('Requires UNIX.');
}
Structura directoarelor
Recomandăm ca la bibliotecile sau proiectele puțin mai mari să împărțiți directorul cu teste și în subdirectoare după 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 astfel rula teste dintr-un singur spațiu de nume, adică subdirector:
tester tests/NamespaceOne
Situații speciale
Un test care nu apelează nicio metodă de aserțiune este suspect și va fi evaluat ca eronat:
Error: This test forgets to execute an assertion.
Dacă într-adevăr testul fără apeluri de aserțiuni trebuie considerat valid, apelați, de exemplu,
Assert::true(true)
.
De asemenea, poate fi înșelător să folosiți exit()
și die()
pentru a termina testul cu un mesaj
de eroare. De exemplu, exit('Error in connection')
termină testul cu codul de retur 0, ceea ce semnalează succes.
Folosiți Assert::fail('Error in connection')
.