Δοκιμές γραφής

Η συγγραφή δοκιμών για το Nette Tester είναι μοναδική στο ότι κάθε δοκιμή είναι ένα PHP script που μπορεί να εκτελεστεί αυτόνομα.. Αυτό έχει μεγάλες δυνατότητες. Καθώς γράφετε τη δοκιμή, μπορείτε απλά να την εκτελέσετε για να δείτε αν λειτουργεί σωστά. Αν όχι, μπορείτε εύκολα να το εξετάσετε στο IDE και να αναζητήσετε ένα σφάλμα.

Μπορείτε ακόμη και να ανοίξετε τη δοκιμή σε ένα πρόγραμμα περιήγησης. Αλλά πάνω απ' όλα – εκτελώντας το, θα εκτελέσετε τη δοκιμή. Θα μάθετε αμέσως αν πέρασε ή απέτυχε.

Στο εισαγωγικό κεφάλαιο, δείξαμε ένα πραγματικά τετριμμένο τεστ χρήσης του πίνακα της PHP. Τώρα θα δημιουργήσουμε τη δική μας κλάση, την οποία θα δοκιμάσουμε, αν και θα είναι επίσης απλή.

Ας ξεκινήσουμε με μια τυπική διάταξη καταλόγου για μια βιβλιοθήκη ή ένα έργο. Είναι σημαντικό να διαχωρίσουμε τις δοκιμές από τον υπόλοιπο κώδικα, για παράδειγμα λόγω ανάπτυξης, επειδή δεν θέλουμε να ανεβάζουμε τις δοκιμές στον διακομιστή. Η δομή μπορεί να είναι η εξής:

├── src/           # code that we will test
│   ├── Rectangle.php
│   └── ...
├── tests/         # 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('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;
	}
}

Και θα δημιουργήσουμε μια δοκιμή για αυτήν. Το όνομα του αρχείου δοκιμής θα πρέπει να ταιριάζει με τη μάσκα *Test.php ή *.phpt, εμείς θα επιλέξουμε την παραλλαγή RectangleTest.php:

<?php
use Tester\Assert;

require __DIR__ . '/bootstrap.php';

// γενική επιμήκης
$rect = new Rectangle(10, 20);
Assert::same(200.0, $rect->getArea());  # we will verify the expected results
Assert::false($rect->isSquare());

Όπως μπορείτε να δείτε, μέθοδοι ισχυρισμού όπως η Assert::same() χρησιμοποιούνται για να βεβαιώσουν ότι μια πραγματική τιμή ταιριάζει με μια αναμενόμενη τιμή.

Το τελευταίο βήμα είναι η δημιουργία του αρχείου bootstrap.php. Περιέχει έναν κοινό κώδικα για όλες τις δοκιμές. Για παράδειγμα, κλάσεις αυτόματης φόρτωσης, διαμόρφωση περιβάλλοντος, δημιουργία προσωρινού καταλόγου, βοηθητικά προγράμματα και παρόμοια. Κάθε δοκιμή φορτώνει το bootstrap και δίνει προσοχή μόνο στον έλεγχο. Το bootstrap μπορεί να μοιάζει ως εξής:

<?php
require __DIR__ . '/vendor/autoload.php';  # load Composer autoloader

Tester\Environment::setup();               # initialization of Nette Tester

// και άλλες ρυθμίσεις (απλώς ένα παράδειγμα, στην περίπτωσή μας δεν χρειάζονται)
date_default_timezone_set('Europe/Prague');
define('TmpDir', '/tmp/app-tests');

Αυτό το bootstrap υποθέτει ότι ο αυτόματος φορτωτής Composer θα είναι σε θέση να φορτώσει και την κλάση Rectangle.php. Αυτό μπορεί να επιτευχθεί, για παράδειγμα, θέτοντας το τμήμα autoload στο composer.json, κ.λπ.

Μπορούμε τώρα να εκτελέσουμε το τεστ από τη γραμμή εντολών όπως κάθε άλλο αυτόνομο PHP script. Η πρώτη εκτέλεση θα αποκαλύψει τυχόν συντακτικά λάθη, και αν δεν κάνατε κάποιο τυπογραφικό λάθος, θα δείτε:

$ php RectangleTest.php

OK

Αν αλλάξουμε στη δοκιμή τη δήλωση σε false 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,
	'The dimension must not be negative.',
);

Και προσθέτουμε έναν παρόμοιο έλεγχο για το ύψος. Τέλος, ελέγχουμε ότι το isSquare() επιστρέφει true αν και οι δύο διαστάσεις είναι ίδιες. Προσπαθήστε να γράψετε τέτοιες δοκιμές ως άσκηση.

Καλά οργανωμένες δοκιμές

Το μέγεθος του αρχείου δοκιμών μπορεί να αυξηθεί και να γίνει γρήγορα ακατάστατο. Ως εκ τούτου, είναι πρακτικό να ομαδοποιούνται οι επιμέρους δοκιμαζόμενες περιοχές σε ξεχωριστές λειτουργίες.

Αρχικά, θα παρουσιάσουμε μια απλούστερη αλλά κομψή παραλλαγή, χρησιμοποιώντας την παγκόσμια συνάρτηση test(). Ο ελεγκτής δεν τη δημιουργεί αυτόματα, για να αποφευχθεί η σύγκρουση αν είχατε μια συνάρτηση με το ίδιο όνομα στον κώδικά σας. Δημιουργείται μόνο από τη μέθοδο setupFunctions(), την οποία καλείτε στο αρχείο bootstrap.php:

Tester\Environment::setup();
Tester\Environment::setupFunctions();

Χρησιμοποιώντας αυτή τη συνάρτηση, μπορούμε να χωρίσουμε όμορφα το αρχείο δοκιμής σε ονομαστικές μονάδες. Κατά την εκτέλεση, οι ετικέτες θα εμφανίζονται η μία μετά την άλλη.

<?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,
	);
});

Αν χρειάζεται να εκτελέσετε τον κώδικα πριν ή μετά από κάθε δοκιμή, περάστε τον στις διευθύνσεις 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();

Αυτή τη φορά χρησιμοποιήσαμε ένα σχόλιο @throw για να ελέγξουμε για εξαιρέσεις. Δείτε το κεφάλαιο TestCase για περισσότερες πληροφορίες.

Συναρτήσεις βοήθειας

Το Nette Tester περιλαμβάνει αρκετές κλάσεις και συναρτήσεις που μπορούν να σας διευκολύνουν τον έλεγχο, για παράδειγμα, βοηθητικά προγράμματα για τον έλεγχο του περιεχομένου ενός εγγράφου HTML, για τον έλεγχο των λειτουργιών εργασίας με αρχεία κ.ο.κ.

Μπορείτε να βρείτε μια περιγραφή τους στη σελίδα Helpers.

Σχολιασμός και παράλειψη δοκιμών

Η εκτέλεση των δοκιμών μπορεί να επηρεαστεί από τις σημειώσεις στο σχόλιο phpDoc στην αρχή του αρχείου. Για παράδειγμα, μπορεί να μοιάζει με αυτό:

/**
 * @phpExtension pdo, pdo_pgsql
 * @phpVersion >= 7.2
 */

Οι σχολιασμοί λένε ότι η δοκιμή πρέπει να εκτελείται μόνο με PHP έκδοση 7.2 ή νεότερη και αν υπάρχουν οι επεκτάσεις PHP pdo και pdo_pgsql. Αυτές οι επισημάνσεις ελέγχονται από τον εκτελεστή δοκιμών γραμμής εντολών, ο οποίος, αν δεν πληρούνται οι προϋποθέσεις, παραλείπει τη δοκιμή και τη σημειώνει με το γράμμα s – skipped. Ωστόσο, δεν έχουν καμία επίδραση όταν η δοκιμή εκτελείται χειροκίνητα.

Για μια περιγραφή των σχολίων, ανατρέξτε στην ενότητα Σχόλια δοκιμών.

Η δοκιμή μπορεί επίσης να παραλειφθεί με βάση τη δική της συνθήκη με το Environment::skip(). Για παράδειγμα, θα παραλείψουμε αυτή τη δοκιμή στα Windows:

if (defined('PHP_WINDOWS_VERSION_BUILD')) {
	Tester\Environment::skip('Requires UNIX.');
}

Δομή καταλόγου

Για λίγο μεγαλύτερες βιβλιοθήκες ή έργα, συνιστούμε να χωρίσετε τον κατάλογο δοκιμών σε υποκαταλόγους ανάλογα με το χώρο ονομάτων της δοκιμαζόμενης κλάσης:

└── tests/
	├── NamespaceOne/
	│   ├── MyClass.getUsers.phpt
	│   ├── MyClass.setUsers.phpt
	│   └── ...
	│
	├── NamespaceTwo/
	│   ├── MyClass.creating.phpt
	│   ├── MyClass.dropping.phpt
	│   └── ...
	│
	├── bootstrap.php
	└── ...

Θα είστε σε θέση να εκτελείτε δοκιμές από έναν μόνο χώρο ονομάτων δηλαδή υποκατάλογο:

tester tests/NamespaceOne

Περιπτώσεις αιχμής

Μια δοκιμή που δεν καλεί καμία μέθοδο επιβεβαίωσης είναι ύποπτη και θα αξιολογηθεί ως λανθασμένη:

Error: This test forgets to execute an assertion.

Εάν η δοκιμή χωρίς κλήση των ισχυρισμών πρέπει πραγματικά να θεωρηθεί έγκυρη, καλέστε για παράδειγμα το Assert::true(true).

Μπορεί επίσης να είναι ύπουλη η χρήση των exit() και die() για να τελειώσει η δοκιμή με ένα μήνυμα σφάλματος. Για παράδειγμα, το exit('Error in connection') τερματίζει τη δοκιμή με κωδικό εξόδου 0, ο οποίος σηματοδοτεί επιτυχία. Χρησιμοποιήστε το Assert::fail('Error in connection').