Ein Testfall (englisch: Test Case) wird als öffentliche Methode, die
keinen Parameter erwartet, in einer Klasse definiert, die die
Schnittstelle PHPUnit2_Framework_Test implementiert. In der
Regel wird hierzu eine Klasse verwendet, die sich von
PHPUnit2_Framework_TestCase ableitet. Hierbei sind die
folgenden Namenskonventionen zu beachten:
Der Name einer Klasse, die die Schnittstelle
PHPUnit2_Framework_Test implementiert und Testfälle
enthält, endet mit dem Suffix Test.
Der Name einer Testfallmethode beginnt mit dem Präfix
test.
Plant man seine Testfälle bei der Anwendungsentwicklung nach dem Test-First-Ansatz geschickt, so kann man auch den von Eiffel eingeführten Ansatz "Entwurf durch Vertrag" (englisch: Design-by-Contract) verfolgen. Hierbei wird die Zusammenarbeit von unterschiedlichen Programmteilen (beispielsweise Methoden) in einem Vertrag geregelt. Dieser enthält mit den Vorbedingungen Zusicherungen, die der Aufrufer einer Methode zu erfüllen hat, sowie mit den Nachbedingungen Zusicherungen, die die aufgerufene Methode einzuhalten hat. Auf diese Weise können Anforderungen an die Funktion der Methode im Quelltext festgehalten werden.
In einer Programmiersprache wie PHP, die im Gegensatz zu Eiffel nicht über eine Sprachunterstützung für den Design-by-Contract-Ansatz verfügt, kann man den Vertrag mit Hilfe entsprechender Testfälle formulieren. Dies wollen wir uns am Beispiel einer Klasse, die ein Bankkonto repräsentieren soll, genauer ansehen.
Der Vertrag der Klasse BankAccount verlangt die Einhaltung
der folgenden Bedingungen:
Der Kontostand ist zu Beginn Null.
Der Kontostand kann nicht negativ werden.
Zusätzlich zu diesen Vereinbarungen legen wir die Schnittstelle
fest, über die die Klienten der Klasse
BankAccount mit dieser kommunizieren können:
getBalance() fragt den aktuellen
Kontostand ab.
setBalance($balance) setzt den Kontostand
auf einen neuen Wert.
depositMoney($amount) modelliert das
Einzahlen von Geld auf das Konto.
withdrawMoney($amount) modelliert das
Abheben von Geld von dem Konto.
Bevor wir den Produktionscode, also die Klasse
BankAccount, schreiben, setzen wir die
Vereinbarungen in Testfälle um
(Beispiel 4.1).
Die Wahl von "sprechenden Namen" für die Testfallmethoden,
die die einzelnen Vertragspunkte ausdrücken, wird sich später
noch als nützlich erweisen
(siehe „TestDox“).
Beispiel 4.1: Die Testfälle für die Klasse BankAccount
<?php
require_once 'BankAccount.php';
require_once 'PHPUnit2/Framework/TestCase.php';
class BankAccountTest extends PHPUnit2_Framework_TestCase {
public function testBalanceIsInitiallyZero() {
$ba = new BankAccount;
$this->assertEquals(0, $ba->getBalance());
}
public function testBalanceCannotBecomeNegative() {
$ba = new BankAccount;
$ba->withdrawMoney(1);
$this->assertEquals(0, $ba->getBalance());
$ba = new BankAccount;
$ba->setBalance(-1);
$this->assertEquals(0, $ba->getBalance());
}
}
?>
Wir haben nun gesehen, welche äußere Form ein Testfall bei der
Verwendung von PHPUnit haben muss, doch wie teilen wir PHPUnit
innerhalb einer Testmethode mit, ob ein Testfall erfolgreich
durchlaufen wurde oder nicht? Durch die Ableitung unserer
Testfallklasse von PHPUnit2_Framework_TestCase erbt
sie eine Reihe von Methoden, mit denen eine Zusicherung (englisch:
Assertion) überprüft werden kann. Diese Zusicherungen werden
am Ende des Rumpfes einer Testmethode eingesetzt, um einen
Ist-Zustand mit einem Soll-Zustand zu vergleichen. Das Ergebnis
dieser Überprüfung wird automatisch als Ergebnis des Testfalls
für die spätere Auswertung gespeichert.
PHPUnit stellt die folgenden Zusicherungsmethoden zur Verfügung:
Tabelle 4.1. Die Zusicherungsmethoden von PHPUnit
| Zusicherungsmethode | Beschreibung |
|---|---|
assertContains($needle, $haystack, $message = '') und assertNotContains($needle, $haystack, $message = '') | Stellt sicher, dass ein Objekt oder Wert ($needle) in einem Array, Iterator-Objekt oder String ($haystack) enthalten beziehungsweise nicht enthalten ist. |
assertEquals($expected, $actual, $message = '', $delta = 0) | Stellt sicher, dass ein Wert ($actual) einem erwarteten Wert ($expected) entspricht. Mit $delta kann eine Toleranzschranke für Abweichungen bei Zahlenwerten angegeben werden. |
assertNull($object, $message = '') und assertNotNull($object, $message = '') | Stellt sicher, dass ein Objekt ($object) NULL beziehungsweise nicht NULL ist. |
assertSame($expected, $actual, $message = '') und assertNotSame($expected, $actual, $message = '') | Stellt sicher, dass eine Variable ($actual) auf ein vorgegebenes Objekt ($expected) zeigt beziehungsweise nicht zeigt. |
assertTrue($condition, $message = '') und assertFalse($condition, $message = '') | Stellt sicher, dass eine gegebene boolesche Bedingung ($condition) TRUE beziehungsweise FALSE ergibt. |
assertRegExp($pattern, $string, $message = '') und assertNotRegExp($pattern, $string, $message = '') | Stellt sicher, dass ein String ($string) einem regulären Ausdruck ($pattern) genügt beziehungsweise nicht genügt. |
assertType($expected, $actual, $message = '') und assertNotType($expected, $actual, $message = '') | Stellt sicher, dass der Typ einer gegebenen Variablen $actual dem in $expected angegebenen Typ entspricht beziehungsweise nicht entspricht. |
Der optionale Parameter $message definiert die
Nachricht, die beim Scheitern des Tests zurückgegeben wird. Wird
er nicht angegeben, so wird eine Standardnachricht erzeugt.
Nach dieser Übersicht über die möglichen Zusicherungsmethoden, die
wir für die Umsetzung unserer Testfälle verwenden können, ist es
an der Zeit, den eigentlichen Produktionscode zu schreiben.
Beispiel 4.2
zeigt eine mögliche Implementierung der Klasse
BankAccount, die den Anforderungen der
Testfälle entspricht.
Beispiel 4.2: Die Klasse BankAccount
<?php
class BankAccount {
private $fBalance = 0;
public function getBalance() {
return $this->fBalance;
}
public function setBalance($balance) {
if ($balance >= 0) {
$this->fBalance = $balance;
}
}
public function depositMoney($amount) {
if (is_numeric($amount) &&
$amount >= 0) {
$this->fBalance += $amount;
}
}
public function withdrawMoney($amount) {
if (is_numeric($amount) &&
$amount >= 0 &&
$this->fBalance >= $amount) {
$this->fBalance -= $amount;
}
}
}
?>