Testfälle und Zusicherungen

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:

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:

Zusätzlich zu diesen Vereinbarungen legen wir die Schnittstelle fest, über die die Klienten der Klasse BankAccount mit dieser kommunizieren können:

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

ZusicherungsmethodeBeschreibung
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;
    }
  }
}
?>