Entwicklungs-HOWTO
VERALTET - Aktuelle Doku unter: HowToTest
Unit-Tests in Stud.IP
TODO (mlunzena) Was sind Unit-Tests? Wofür sind sie gut? Welches Tool wird verwendet?
Wie führt man die Unit-Tests aus?
Rufe in deiner Stud.IP-Working-Copy folgendes Kommando im Terminal auf:
php test/all_tests.php
Dieses Kommando durchsucht das Test-Verzeichnis nach Unit-Tests und führt diese aus. Daraufhin erhält man als Ausgabe die Zahl der ausgeführten und erfolgreichen Tests. Das sieht dann z.B. so aus:
All tests
OK
Test cases run: 10/10, Passes: 186, Failures: 0, Exceptions: 0
Ein Fehler würde in etwa so aussehen:
All tests
1) at [/test/lib/classes/assets_class_test.php line 43]
in test_class_should_exist
in AssetsTestCase
FAILURES!!!
Test cases run: 10/10, Passes: 185, Failures: 1, Exceptions: 0
Wie schreibt man einen Unit-Test?
Als Beispiel werden wir einen kleinen Test für die Funktion #words
schreiben, die sich in der Datei lib/functions.php
befindet.
Wohin kommen die Tests?
Der Unit-Test-Runner, den wir oben ausführten, findet Test-Dateien in den Verzeichnissen test/lib
und test/lib/classes
. Dabei müssen die Namen dieser Dateien auf test.php
enden.
In unserem Beispiel legen wir also eine Datei test/lib/functions_test.php
an. Per Konvention sollen Tests, die sich auf Code in lib
beziehen, im Verzeichnis test/lib
liegen, und Tests, die sich auf Klassen aus lib/classes
beziehen, im Verzeichnis test/lib/classes
.
Jeder Unit-Test sieht minimal so aus:
<?php
/*
* Copyright (C) 2010 - Marcus Lunzenauer <mlunzena@uos.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*/
// make sure that the code under test is available
require_once 'lib/functions.php';
class FunctionsTest extends UnitTestCase {
function testSomething()
{
...
}
}
Man halt also eine Klasse, die von UnitTestCase
abstammt, und die Test-Methoden enthält, deren Name mit test
beginnt. Der Name sollte andeuten, was man eigentlich gerade testet. Er darf gerne lang sein. Niemand wird diese Methoden per Hand aufrufen müssen.
Eine Test-Funktion
In unserem ersten Test wollen wir den happy path der Funktion #words
testen. Schauen wir uns noch einmal diese Funktion an:
/**
* Splits a string by space characters and returns these words as an array.
*
* @param string the string to split
*
* @return array the words of the string as array
*/
function words($string) {
return preg_split('/ /',
$string,
-1, PREG_SPLIT_NO_EMPTY
);
}
Diese Funktion erlaubt es also offenbar, aus einem String ein Array zu machen, das die Wörter des Strings als Elemente hat.
Damit sieht der erste Test so aus:
[...]
class FunctionsTest extends UnitTestCase {
function testWords()
{
// setup fixture
// exercise system under test
$words = words("one two three");
// validate outcome
$this->
assertEqual($words,
array('one',
'two',
'three'))
// teardown fixture
}
}
In diesem Test wird ein String mit drei Wörtern angelegt, dann an #words
verfüttert, und zum Schluss mit Hilfe der Methode #assertEqual
verglichen. Ist diese Assertion wahr, ist der Test erfolgreich.
Ausserdem erkennt man gut das four phase test pattern. Die Kommentare trennen die 4 Phasen eines Tests. Unter "fixture" versteht man die Ausgangssituation, die geschaffen werden muss, um den Test auszuführen. In unserem sehr einfachen Beispiel ist dafür nichts notwendig. In der "exercise"-Phase wird dannn das zu testende Verhalten herbeigeführt, was in diesem Fall dem Aufruf der Funktion #words
entspricht. Danach wird überprüft, ob das Ergebnis des Verhaltens den Erwartungen entspricht. Und ganz am Ende räumt man alles ab, damit der nächste Test in einer klar definierten Umgebung ablaufen kann. Bei unserem Test ist dafür nichts zu tun. Auf die Phasen 1 und 4 (Fixture-Setup und -Teardown) wird weiter unten vertieft eingegangen.
Die kleinste Einheit eines Unit-Tests ist die Assertion. Im Beispiel wollten wir sicherstellen, dass die Funktion tatsächlich das erwartete Array zurückliefert. UnitTestCase#assertEqual
ist erfolgreich, wenn der erste Parameter == dem zweiten Parameter ist.
Es gibt eine noch viele weitere Assertions und selbstverständlich kann der Programmierer jederzeit durch Erweiterung der Klasse UnitTestCase
neue hinzufügen.
Die grundsätzlichen Assertions werden in der folgenden Tabelle aufgelistet:
assertTrue($x) | schlägt fehl, es sei denn $x entspricht true |
assertFalse($x) | schlägt fehl, es sei denn $x entspricht false |
assertNull($x) | schlägt fehl, es sei denn $x ist nicht gesetzt |
assertNotNull($x) | schlägt fehl, es sei denn $x ist gesetzt |
assertIsA($x, $t) | schlägt fehl, es sei denn die Klasse oder der Typ von $x entspricht $t |
assertNotA($x, $t) | schlägt fehl, es sei denn die Klasse oder der Typ von $x entspricht nicht $t |
assertEqual($x, $y) | schlägt fehl, es sei denn $x == $y ist wahr |
assertNotEqual($x, $y) | schlägt fehl, es sei denn $x == $y ist falsch |
assertWithinMargin($x, $y, $margin) | schlägt fehl, es sei denn $x und $y liegen weniger als $margin auseinander |
assertOutsideMargin($x, $y, $margin) | schlägt fehl, es sei denn $x und $y liegen mehr als $margin auseinander |
assertIdentical($x, $y) | schlägt fehl, es sei denn $x === $y für primitive Variablen, $x == $y für Objekte desselben Typs |
assertNotIdentical($x, $y) | schlägt fehl, wenn $x === $y falsch ist, oder zwei Objekte ungleich oder von unterschiedlichem Typ sind |
assertReference($x, $y) | schlägt fehl, es sei denn $x und $y sind dieselbe Variable |
assertCopy($x, $y) | schlägt fehl, wenn $x und $y dieselbe Variable sind |
assertSame($x, $y) | schlägt fehl, es sei denn $x und $y sind dieselben Objekte |
assertClone($x, $y) | schlägt fehl, es sei denn $x und $y sind identisch, aber nicht dieselben Objekte |
assertPattern($p, $x) | schlägt fehl, es sei denn der reguläre Ausdruck $p matched $x |
assertNoPattern($p, $x) | schlägt fehl, wenn denn der reguläre Ausdruck $p matched $x |
expectError($e) | schlägt fehl, wenn der Fehler $e nicht vor Ende dieses Tests eintritt |
expectException($e) | schlägt fehl, wenn die Exception $e nicht vor Ende dieses Tests geworfen wird |
Setup und TearDown
TODO (mlunzena) fehlt noch
Weitere Informationen
TODO (mlunzena) Hier fehlen noch ein paar weitere Links.