Ισχυρισμοί

Οι ισχυρισμοί χρησιμοποιούνται για να επιβεβαιώσουν ότι η πραγματική τιμή αντιστοιχεί στην αναμενόμενη τιμή. Πρόκειται για μεθόδους της κλάσης Tester\Assert.

Επιλέξτε τους καταλληλότερους ισχυρισμούς. Είναι καλύτερο το Assert::same($a, $b) από το Assert::true($a === $b), επειδή σε περίπτωση αποτυχίας εμφανίζει ένα ουσιαστικό μήνυμα σφάλματος. Στη δεύτερη περίπτωση, μόνο το false should be true το οποίο δεν μας λέει τίποτα για το περιεχόμενο των μεταβλητών $a και $b.

Οι περισσότεροι ισχυρισμοί μπορούν επίσης να έχουν μια προαιρετική περιγραφή στην παράμετρο $description, η οποία εμφανίζεται στο μήνυμα σφάλματος εάν η προσδοκία αποτύχει.

Τα παραδείγματα προϋποθέτουν τη δημιουργία ενός alias:

use Tester\Assert;

Assert::same ($expected, $actual, ?string $description=null)

Το $expected πρέπει να είναι ταυτόσημο με το $actual. Το ίδιο με τον τελεστή PHP ===.

Assert::notSame ($expected, $actual, ?string $description=null)

Το αντίθετο του Assert::same(), δηλαδή το ίδιο με τον τελεστή PHP !==.

Assert::equal ($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false)

Το $expected πρέπει να είναι ίδιο με το $actual. Σε αντίθεση με το Assert::same(), αγνοείται η ταυτότητα των αντικειμένων, η σειρά των ζευγών κλειδιού ⇒ τιμής στους πίνακες και οι οριακά διαφορετικοί δεκαδικοί αριθμοί, κάτι που μπορεί να αλλάξει ρυθμίζοντας τα $matchIdentity και $matchOrder.

Οι ακόλουθες περιπτώσεις είναι ίδιες από την άποψη του equal(), αλλά όχι του same():

Assert::equal(0.3, 0.1 + 0.2);
Assert::equal($obj, clone $obj);
Assert::equal(
	['first' => 11, 'second' => 22],
	['second' => 22, 'first' => 11],
);

Ωστόσο, προσοχή, οι πίνακες [1, 2] και [2, 1] δεν είναι ίδιοι, επειδή διαφέρουν μόνο στη σειρά των τιμών, όχι των ζευγών κλειδιού ⇒ τιμής. Ο πίνακας [1, 2] μπορεί επίσης να γραφτεί ως [0 => 1, 1 => 2] και επομένως ως ίδιος θα θεωρηθεί ο [1 => 2, 0 => 1].

Επιπλέον, στο $expected μπορούν να χρησιμοποιηθούν οι λεγόμενες Προσδοκίες.

Assert::notEqual ($expected, $actual, ?string $description=null)

Το αντίθετο του Assert::equal().

Assert::contains ($needle, string|array $actual, ?string $description=null)

Εάν το $actual είναι string, πρέπει να περιέχει το substring $needle. Εάν είναι array, πρέπει να περιέχει το στοιχείο $needle (συγκρίνεται αυστηρά).

Assert::notContains ($needle, string|array $actual, ?string $description=null)

Το αντίθετο του Assert::contains().

Assert::hasKey (string|int $needle, array $actual, ?string $description=null)

Το $actual πρέπει να είναι array και πρέπει να περιέχει το κλειδί $needle.

Assert::notHasKey (string|int $needle, array $actual, ?string $description=null)

Το $actual πρέπει να είναι array και δεν πρέπει να περιέχει το κλειδί $needle.

Assert::true ($value, ?string $description=null)

Το $value πρέπει να είναι true, δηλαδή $value === true.

Assert::truthy ($value, ?string $description=null)

Το $value πρέπει να είναι αληθές (truthy), δηλαδή να ικανοποιεί τη συνθήκη if ($value) ....

Assert::false ($value, ?string $description=null)

Το $value πρέπει να είναι false, δηλαδή $value === false.

Assert::falsey ($value, ?string $description=null)

Το $value πρέπει να είναι ψευδές (falsey), δηλαδή να ικανοποιεί τη συνθήκη if (!$value) ....

Assert::null ($value, ?string $description=null)

Το $value πρέπει να είναι null, δηλαδή $value === null.

Assert::notNull ($value, ?string $description=null)

Το $value δεν πρέπει να είναι null, δηλαδή $value !== null.

Assert::nan ($value, ?string $description=null)

Το $value πρέπει να είναι Not a Number. Για τον έλεγχο της τιμής NAN χρησιμοποιείτε αποκλειστικά το Assert::nan(). Η τιμή NAN είναι πολύ συγκεκριμένη και οι assertions Assert::same() ή Assert::equal() μπορεί να λειτουργήσουν απροσδόκητα.

Assert::count ($count, Countable|array $value, ?string $description=null)

Ο αριθμός των στοιχείων στο $value πρέπει να είναι $count. Δηλαδή το ίδιο με count($value) === $count.

Assert::type (string|object $type, $value, ?string $description=null)

Το $value πρέπει να είναι του δεδομένου τύπου. Ως $type μπορούμε να χρησιμοποιήσουμε μια συμβολοσειρά:

  • array
  • list – πίνακας με δείκτες σε αύξουσα σειρά αριθμητικών κλειδιών από το μηδέν
  • bool
  • callable
  • float
  • int
  • null
  • object
  • resource
  • scalar
  • string
  • όνομα κλάσης ή απευθείας αντικείμενο, τότε το $value πρέπει να είναι instanceof $type

Assert::exception (callable $callable, string $class, ?string $message=null, $code=null)

Κατά την κλήση του $callable πρέπει να δημιουργηθεί μια εξαίρεση της κλάσης $class. Εάν αναφέρουμε $message, πρέπει να αντιστοιχεί στο πρότυπο και το μήνυμα της εξαίρεσης, και αν αναφέρουμε $code, πρέπει να ταιριάζουν αυστηρά και οι κωδικοί.

Η ακόλουθη δοκιμή αποτυγχάνει, επειδή το μήνυμα της εξαίρεσης δεν αντιστοιχεί:

Assert::exception(
	fn() => throw new App\InvalidValueException('Zero value'),
	App\InvalidValueException::class,
	'Value is to low',
);

Το Assert::exception() επιστρέφει την εξαίρεση που δημιουργήθηκε, οπότε μπορεί να ελεγχθεί και μια ένθετη εξαίρεση.

$e = Assert::exception(
	fn() => throw new MyException('Something is wrong', 0, new RuntimeException),
	MyException::class,
	'Something is wrong',
);

Assert::type(RuntimeException::class, $e->getPrevious());

Assert::error (string $callable, int|string|array $type, ?string $message=null)

Ελέγχει ότι η συνάρτηση $callable δημιούργησε τα αναμενόμενα σφάλματα (δηλ. προειδοποιήσεις, ειδοποιήσεις κ.λπ.). Ως $type αναφέρουμε μία από τις σταθερές E_..., δηλαδή για παράδειγμα E_WARNING. Και αν αναφέρουμε $message, πρέπει να αντιστοιχεί στο πρότυπο και το μήνυμα σφάλματος. Για παράδειγμα:

Assert::error(
	fn() => $i++,
	E_NOTICE,
	'Undefined variable: i',
);

Εάν το callback δημιουργήσει περισσότερα σφάλματα, πρέπει να τα περιμένουμε όλα με την ακριβή σειρά. Σε αυτήν την περίπτωση, περνάμε στο $type έναν πίνακα:

Assert::error(function () {
	$a++;
	$b++;
}, [
	[E_NOTICE, 'Undefined variable: a'],
	[E_NOTICE, 'Undefined variable: b'],
]);

Εάν ως $type αναφέρετε όνομα κλάσης, συμπεριφέρεται το ίδιο με το Assert::exception().

Assert::noError (callable $callable)

Ελέγχει ότι η συνάρτηση $callable δεν δημιούργησε καμία προειδοποίηση, σφάλμα ή εξαίρεση. Είναι χρήσιμο για τον έλεγχο κομματιών κώδικα όπου δεν υπάρχει καμία άλλη assertion.

Assert::match (string $pattern, $actual, ?string $description=null)

Το $actual πρέπει να ταιριάζει με το πρότυπο $pattern. Μπορούμε να χρησιμοποιήσουμε δύο παραλλαγές προτύπων: κανονικές εκφράσεις ή μπαλαντέρ.

Εάν ως $pattern περάσουμε μια κανονική έκφραση, για τον οριοθέτησή της πρέπει να χρησιμοποιήσουμε ~ ή #, άλλοι οριοθέτες δεν υποστηρίζονται. Για παράδειγμα, μια δοκιμή όπου το $var πρέπει να περιέχει μόνο δεκαεξαδικούς αριθμούς:

Assert::match('#^[0-9a-f]$#i', $var);

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

  • %a% ένας ή περισσότεροι χαρακτήρες, εκτός από τους χαρακτήρες τέλους γραμμής
  • %a?% κανένας ή περισσότεροι χαρακτήρες, εκτός από τους χαρακτήρες τέλους γραμμής
  • %A% ένας ή περισσότεροι χαρακτήρες, συμπεριλαμβανομένων των χαρακτήρων τέλους γραμμής
  • %A?% κανένας ή περισσότεροι χαρακτήρες, συμπεριλαμβανομένων των χαρακτήρων τέλους γραμμής
  • %s% ένας ή περισσότεροι λευκοί χαρακτήρες, εκτός από τους χαρακτήρες τέλους γραμμής
  • %s?% κανένας ή περισσότεροι λευκοί χαρακτήρες, εκτός από τους χαρακτήρες τέλους γραμμής
  • %S% ένας ή περισσότεροι χαρακτήρες, εκτός από τους λευκούς χαρακτήρες
  • %S?% κανένας ή περισσότεροι χαρακτήρες, εκτός από τους λευκούς χαρακτήρες
  • %c% οποιοσδήποτε ένας χαρακτήρας, εκτός από τον χαρακτήρα τέλους γραμμής
  • %d% ένα ή περισσότερα ψηφία
  • %d?% κανένα ή περισσότερα ψηφία
  • %i% προσημασμένη ακέραια τιμή
  • %f% αριθμός με δεκαδικό σημείο
  • %h% ένα ή περισσότερα δεκαεξαδικά ψηφία
  • %w% ένας ή περισσότεροι αλφαριθμητικοί χαρακτήρες
  • %% ο χαρακτήρας %

Παραδείγματα:

# Πάλι έλεγχος για δεκαεξαδικό αριθμό
Assert::match('%h%', $var);

# Γενίκευση της διαδρομής αρχείου και του αριθμού γραμμής
Assert::match('Error in file %a% on line %i%', $errorMessage);

Assert::matchFile (string $file, $actual, ?string $description=null)

Η assertion είναι ταυτόσημη με την Assert::match(), αλλά το πρότυπο φορτώνεται από το αρχείο $file. Αυτό είναι χρήσιμο για τον έλεγχο πολύ μεγάλων συμβολοσειρών. Το αρχείο με τη δοκιμή παραμένει ευανάγνωστο.

Assert::fail (string $message, $actual=null, $expected=null)

Αυτή η assertion αποτυγχάνει πάντα. Μερικές φορές αυτό είναι απλά χρήσιμο. Προαιρετικά, μπορούμε να αναφέρουμε και την αναμενόμενη και την πραγματική τιμή.

Προσδοκίες

Όταν θέλουμε να συγκρίνουμε πιο σύνθετες δομές με μη σταθερά στοιχεία, οι παραπάνω assertions μπορεί να μην είναι επαρκείς. Για παράδειγμα, ελέγχουμε μια μέθοδο που δημιουργεί έναν νέο χρήστη και επιστρέφει τα χαρακτηριστικά του ως πίνακα. Την τιμή του hash του κωδικού πρόσβασης δεν τη γνωρίζουμε, αλλά ξέρουμε ότι πρέπει να είναι μια δεκαεξαδική συμβολοσειρά. Και για ένα άλλο στοιχείο ξέρουμε μόνο ότι πρέπει να είναι ένα αντικείμενο DateTime.

Σε αυτές τις καταστάσεις μπορούμε να χρησιμοποιήσουμε το Tester\Expect μέσα στην παράμετρο $expected των μεθόδων Assert::equal() και Assert::notEqual(), με τις οποίες μπορούμε εύκολα να περιγράψουμε τη δομή.

use Tester\Expect;

Assert::equal([
	'id' => Expect::type('int'),                   # περιμένουμε ακέραιο αριθμό
	'username' => 'milo',
	'password' => Expect::match('%h%'),            # περιμένουμε συμβολοσειρά που ταιριάζει με το πρότυπο
	'created_at' => Expect::type(DateTime::class), # περιμένουμε παρουσία της κλάσης
], User::create(123, 'milo', 'RandomPaSsWoRd'));

Με το Expect μπορούμε να εκτελέσουμε σχεδόν τις ίδιες assertions όπως με το Assert. Δηλαδή, έχουμε στη διάθεσή μας τις μεθόδους Expect::same(), Expect::match(), Expect::count() κ.λπ. Επιπλέον, μπορούμε να τις συνδέσουμε:

Expect::type(MyIterator::class)->andCount(5);  # περιμένουμε MyIterator και αριθμό στοιχείων 5

Ή μπορούμε να γράψουμε τους δικούς μας handlers assertions.

Expect::that(function ($value) {
	# επιστρέφουμε false, εάν η προσδοκία αποτύχει
});

Διερεύνηση λανθασμένων assertions

Όταν μια assertion αποτυγχάνει, ο Tester εκτυπώνει πού είναι το λάθος. Εάν συγκρίνουμε πιο σύνθετες δομές, ο Tester δημιουργεί dumps των συγκρινόμενων τιμών και τις αποθηκεύει στον κατάλογο output. Για παράδειγμα, κατά την αποτυχία της φανταστικής δοκιμής Arrays.recursive.phpt, τα dumps θα αποθηκευτούν ως εξής:

app/
└── tests/
	├── output/
	│   ├── Arrays.recursive.actual    # πραγματική τιμή
	│   └── Arrays.recursive.expected  # αναμενόμενη τιμή
	│
	└── Arrays.recursive.phpt          # αποτυχημένη δοκιμή

Το όνομα του καταλόγου μπορούμε να το αλλάξουμε μέσω του Tester\Dumper::$dumpDir.