HowToUnitTest

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:

  1. <?php
  2.  
  3. /*
  4. * Copyright (C) 2010 - Marcus Lunzenauer <mlunzena@uos.de>
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License as
  8. * published by the Free Software Foundation; either version 2 of
  9. * the License, or (at your option) any later version.
  10. */
  11.  
  12. // make sure that the code under test is available
  13. require_once 'lib/functions.php';
  14.  
  15. class FunctionsTest extends UnitTestCase {
  16.  
  17.     function testSomething()
  18.     {
  19.         ...
  20.     }
  21. }

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:

  1. /**
  2. * Splits a string by space characters and returns these words as an array.
  3. *
  4. * @param  string       the string to split
  5. *
  6. * @return array        the words of the string as array
  7. */
  8. function words($string) {
  9.   return preg_split('/ /', $string, -1, PREG_SPLIT_NO_EMPTY);
  10. }

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:

  1. [...]
  2.  
  3. class FunctionsTest extends UnitTestCase {
  4.  
  5.     function testWords()
  6.     {
  7.  
  8.         // setup fixture
  9.  
  10.         // exercise system under test
  11.         $words = words("one two three");
  12.  
  13.         // validate outcome
  14.         $this->assertEqual($words, array('one', 'two', 'three'))
  15.  
  16.         // teardown fixture
  17.     }
  18. }

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.

Letzte Änderung am December 18, 2014, at 02:02 PM von tgloeggl.