Συγγραφή δοκιμών
Η συγγραφή δοκιμών για το Nette Tester είναι μοναδική στο ότι κάθε δοκιμή είναι ένα PHP script που μπορεί να εκτελεστεί αυτόνομα. Αυτό κρύβει μεγάλο δυναμικό. Ήδη όταν γράφετε τη δοκιμή, μπορείτε απλά να την εκτελείτε και να διαπιστώνετε αν λειτουργεί σωστά. Αν όχι, μπορείτε εύκολα να την κάνετε βήμα-βήμα στο IDE και να αναζητήσετε το σφάλμα.
Μπορείτε ακόμη και να ανοίξετε τη δοκιμή στον browser. Αλλά κυρίως – με την εκτέλεσή της, εκτελείτε τη δοκιμή. Διαπιστώνετε αμέσως αν πέρασε ή απέτυχε.
Στο εισαγωγικό κεφάλαιο δείξαμε μια πραγματικά τετριμμένη δοκιμή εργασίας με πίνακα. Τώρα θα δημιουργήσουμε τη δική μας κλάση που θα δοκιμάσουμε, αν και θα είναι επίσης απλή.
Θα ξεκινήσουμε από την τυπική δομή καταλόγων για μια βιβλιοθήκη ή ένα project. Είναι σημαντικό να διαχωρίσουμε τις δοκιμές από τον υπόλοιπο κώδικα, για παράδειγμα λόγω του deployment, επειδή δεν θέλουμε να ανεβάσουμε τις δοκιμές στον παραγωγικό server. Η δομή μπορεί να είναι κάπως έτσι:
├── src/ # κώδικας που θα δοκιμάσουμε
│ ├── Rectangle.php
│ └── ...
├── tests/ # δοκιμές
│ ├── bootstrap.php
│ ├── RectangleTest.php
│ └── ...
├── vendor/
└── composer.json
Και τώρα θα δημιουργήσουμε τα επιμέρους αρχεία. Θα ξεκινήσουμε από
την κλάση που θα δοκιμάσουμε, την οποία θα τοποθετήσουμε στο
αρχείο 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('Η διάσταση δεν πρέπει να είναι αρνητική.');
}
$this->width = $width;
$this->height = $height;
}
public function getArea(): float
{
return $this->width * $this->height;
}
public function isSquare(): bool
{
return $this->width === $this->height;
}
}
Και θα δημιουργήσουμε μια δοκιμή γι' αυτήν. Το όνομα του αρχείου με τη
δοκιμή θα πρέπει να αντιστοιχεί στη μάσκα *Test.php
ή *.phpt
, θα
επιλέξουμε για παράδειγμα την παραλλαγή RectangleTest.php
:
<?php
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
// γενικό ορθογώνιο
$rect = new Rectangle(10, 20);
Assert::same(200.0, $rect->getArea()); # επαληθεύουμε τα αναμενόμενα αποτελέσματα
Assert::false($rect->isSquare());
Όπως βλέπετε, οι λεγόμενες μέθοδοι assertion
όπως Assert::same()
χρησιμοποιούνται για να επιβεβαιώσουν ότι η
πραγματική τιμή αντιστοιχεί στην αναμενόμενη τιμή.
Απομένει το τελευταίο βήμα και αυτό είναι το αρχείο bootstrap.php
.
Αυτό περιέχει κώδικα κοινό για όλες τις δοκιμές, για παράδειγμα autoloading
κλάσεων, διαμόρφωση περιβάλλοντος, δημιουργία προσωρινού καταλόγου,
βοηθητικές συναρτήσεις και παρόμοια. Όλες οι δοκιμές φορτώνουν το
bootstrap και στη συνέχεια ασχολούνται μόνο με τις δοκιμές. Το Bootstrap μπορεί
να μοιάζει ως εξής:
<?php
require __DIR__ . '/../vendor/autoload.php'; # φορτώνει τον Composer autoloader
Tester\Environment::setup(); # αρχικοποίηση του Nette Tester
// και άλλη διαμόρφωση (είναι μόνο παράδειγμα, στην περίπτωσή μας δεν χρειάζονται)
date_default_timezone_set('Europe/Prague');
define('TmpDir', '/tmp/app-tests');
Το αναφερόμενο bootstrap προϋποθέτει ότι ο autoloader του Composer θα είναι
σε θέση να φορτώσει και την κλάση Rectangle.php
. Αυτό μπορεί να
επιτευχθεί για παράδειγμα με ρύθμιση της ενότητας autoload στο
composer.json
κ.λπ.
Μπορούμε τώρα να εκτελέσουμε τη δοκιμή από τη γραμμή εντολών όπως οποιοδήποτε άλλο αυτόνομο PHP script. Η πρώτη εκτέλεση θα μας αποκαλύψει πιθανά συντακτικά λάθη και αν δεν υπάρχει πουθενά ορθογραφικό λάθος, θα εκτυπωθεί:
$ php RectangleTest.php
OK
Αν αλλάζαμε στη δοκιμή τον ισχυρισμό σε ψευδή
Assert::same(123, $rect->getArea());
θα συνέβαινε αυτό:
$ php RectangleTest.php Failed: 200.0 should be 123 in RectangleTest.php(5) Assert::same(123, $rect->getArea()); FAILURE
Κατά τη συγγραφή δοκιμών είναι καλό να καλύπτονται όλες οι ακραίες περιπτώσεις. Για παράδειγμα, όταν η είσοδος είναι μηδέν, αρνητικός αριθμός, σε άλλες περιπτώσεις ίσως κενή συμβολοσειρά, null κ.λπ. Στην πραγματικότητα, σας αναγκάζει να σκεφτείτε και να αποφασίσετε πώς πρέπει να συμπεριφέρεται ο κώδικας σε τέτοιες καταστάσεις. Οι δοκιμές στη συνέχεια σταθεροποιούν τη συμπεριφορά.
Στην περίπτωσή μας, η αρνητική τιμή πρέπει να δημιουργήσει μια εξαίρεση, την οποία επαληθεύουμε χρησιμοποιώντας το Assert::exception():
// το πλάτος δεν πρέπει να είναι αρνητικό
Assert::exception(
fn() => new Rectangle(-1, 20),
InvalidArgumentException::class,
'Η διάσταση δεν πρέπει να είναι αρνητική.',
);
Και μια παρόμοια δοκιμή προσθέτουμε για το ύψος. Τέλος, ελέγχουμε ότι
το isSquare()
επιστρέφει true
, εάν και οι δύο διαστάσεις είναι
ίδιες. Δοκιμάστε ως άσκηση να γράψετε τέτοιες δοκιμές.
Πιο ευανάγνωστες δοκιμές
Το μέγεθος του αρχείου με τη δοκιμή μπορεί να αυξηθεί και γρήγορα να γίνει δυσανάγνωστο. Γι' αυτό είναι πρακτικό να ομαδοποιούμε τις επιμέρους δοκιμαζόμενες περιοχές σε ξεχωριστές συναρτήσεις.
Πρώτα θα δείξουμε μια απλούστερη, αλλά κομψή παραλλαγή, και αυτό με τη
χρήση της καθολικής συνάρτησης test()
. Ο Tester δεν τη δημιουργεί
αυτόματα, για να μην υπάρξει σύγκρουση, αν είχατε στον κώδικα μια
συνάρτηση με το ίδιο όνομα. Τη δημιουργεί η μέθοδος setupFunctions()
,
την οποία καλέστε στο αρχείο bootstrap.php
:
Tester\Environment::setup();
Tester\Environment::setupFunctions();
Με τη βοήθεια αυτής της συνάρτησης μπορούμε να διαρθρώσουμε όμορφα το αρχείο δοκιμής σε ονομασμένες ενότητες. Κατά την εκτέλεση θα εκτυπώνονται διαδοχικά οι περιγραφές.
<?php
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
test('γενικό ορθογώνιο', function () {
$rect = new Rectangle(10, 20);
Assert::same(200.0, $rect->getArea());
Assert::false($rect->isSquare());
});
test('γενικό τετράγωνο', function () {
$rect = new Rectangle(5, 5);
Assert::same(25.0, $rect->getArea());
Assert::true($rect->isSquare());
});
test('οι διαστάσεις δεν πρέπει να είναι αρνητικές', function () {
Assert::exception(
fn() => new Rectangle(-1, 20),
InvalidArgumentException::class,
);
Assert::exception(
fn() => new Rectangle(10, -1),
InvalidArgumentException::class,
);
});
Εάν χρειάζεστε να εκτελέσετε κώδικα πριν ή μετά από κάθε δοκιμή,
περάστε τον στη συνάρτηση setUp()
αντίστοιχα tearDown()
:
setUp(function () {
// κώδικας αρχικοποίησης που εκτελείται πριν από κάθε test()
});
Η δεύτερη παραλλαγή είναι αντικειμενοστραφής. Θα δημιουργήσουμε το λεγόμενο TestCase, το οποίο είναι μια κλάση όπου οι επιμέρους ενότητες αντιπροσωπεύουν μεθόδους, τα ονόματα των οποίων ξεκινούν με 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);
}
}
// Εκτέλεση των μεθόδων δοκιμής
(new RectangleTest)->run();
Για τον έλεγχο των εξαιρέσεων χρησιμοποιήσαμε αυτή τη φορά την annotation
@throws
. Περισσότερα θα μάθετε στο κεφάλαιο TestCase.
Βοηθητικές συναρτήσεις
Το Nette Tester περιέχει αρκετές κλάσεις και συναρτήσεις που μπορούν να σας διευκολύνουν, για παράδειγμα, στον έλεγχο του περιεχομένου ενός εγγράφου HTML, στον έλεγχο συναρτήσεων που εργάζονται με αρχεία και ούτω καθεξής.
Η περιγραφή τους βρίσκεται στη σελίδα Βοηθητικές κλάσεις.
Annotations και παράλειψη δοκιμών
Η εκτέλεση των δοκιμών μπορεί να επηρεαστεί από annotations με τη μορφή σχολίου phpDoc στην αρχή του αρχείου. Μπορεί να μοιάζει για παράδειγμα έτσι:
<?php
/**
* @phpExtension pdo, pdo_pgsql
* @phpVersion >= 7.2
*/
Οι αναφερόμενες annotations λένε ότι η δοκιμή πρέπει να εκτελεστεί μόνο με
την έκδοση PHP 7.2 ή νεότερη και εάν είναι παρούσες οι επεκτάσεις PHP pdo και
pdo_pgsql. Αυτές οι annotations καθοδηγούν τον εκτελεστή δοκιμών από τη γραμμή εντολών, ο
οποίος στην περίπτωση που οι συνθήκες δεν πληρούνται, παραλείπει τη
δοκιμή και στην έξοδο την επισημαίνει με το γράμμα s
– skipped.
Ωστόσο, κατά τη χειροκίνητη εκτέλεση της δοκιμής δεν έχουν καμία
επίδραση.
Η περιγραφή των annotations βρίσκεται στη σελίδα Annotations δοκιμών.
Μια δοκιμή μπορεί να παραλειφθεί επίσης βάσει της εκπλήρωσης μιας
δικής της συνθήκης χρησιμοποιώντας το Environment::skip()
. Για
παράδειγμα, αυτή παραλείπει τις δοκιμές στα Windows:
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
Tester\Environment::skip('Απαιτεί UNIX.');
}
Δομή καταλόγων
Συνιστούμε σε λίγο μεγαλύτερες βιβλιοθήκες ή projects να χωρίσετε τον κατάλογο με τις δοκιμές επιπλέον σε υποκαταλόγους σύμφωνα με το namespace της δοκιμαζόμενης κλάσης:
└── tests/
├── NamespaceOne/
│ ├── MyClass.getUsers.phpt
│ ├── MyClass.setUsers.phpt
│ └── ...
│
├── NamespaceTwo/
│ ├── MyClass.creating.phpt
│ ├── MyClass.dropping.phpt
│ └── ...
│
├── bootstrap.php
└── ...
Έτσι θα μπορείτε να εκτελείτε δοκιμές από ένα μόνο namespace δηλαδή υποκατάλογο:
tester tests/NamespaceOne
Ειδικές καταστάσεις
Μια δοκιμή που δεν καλεί ούτε μία μέθοδο assertion είναι ύποπτη και αξιολογείται ως λανθασμένη:
Error: This test forgets to execute an assertion.
Εάν πραγματικά μια δοκιμή χωρίς κλήση assertions πρέπει να θεωρηθεί
έγκυρη, καλέστε για παράδειγμα Assert::true(true)
.
Επίσης, μπορεί να είναι παραπλανητική η χρήση των exit()
και
die()
για τον τερματισμό της δοκιμής με μήνυμα σφάλματος. Για
παράδειγμα, το exit('Error in connection')
τερματίζει τη δοκιμή με κωδικό
επιστροφής 0, που σηματοδοτεί επιτυχία. Χρησιμοποιήστε το
Assert::fail('Error in connection')
.