Wer Komponenten entwickelt kennt das bestimmt. Hier ist die Rede von der „Design by contract“ Entwicklungs-Methodik. Ziel ist das reibungslose Zusammenspiel einzelner Programmkomponenten durch die Definition von strikten „Verträge“ zur Verwendung von Schnittstellen, die über deren statische Definition hinausgehen. Mit Verträge – sind hiermit die Bedingungen gemeint die bei der Verwendung einer Methode einzuhalten sind.
Sie die Bedingungen werden wie folgt definiert und eingesetzt:
Vorbedingungen = das sind Zusicherungen, die beim Aufrufen einzuhalten sind.
Nachbedingungen = die Zusicherungen, die nach dem Aufruf geliefert werden.
Invarianten = die Zusicherungen zum Aufbau der Klasse.
Die Bedingungen können sich auf die gesamte verfügbare Information beziehen, also auf Variablen- und Parameter-Inhalte ebenso wie auf Objektzustände des betroffenen Objekts oder anderer zugreifbarer Objekte. Sofern sich der Aufrufende an Vorbedingungen und Invarianten hält, können keine Fehler auftreten und die Methode liefert garantiert keine unerwarteten Ergebnisse.
Soweit dazu, und was hat das mit PHPUnit zu tun? Nun die PHPUnit “assertions” können in diesem Fall als Prüfwerkzeug für die Bedingungen in der „Design by contract“ Entwicklungs-Methodik eingesetzt werden. Die Überprüfung der Typen und der Bedingungen mittels der InvalidArgumentException wurde komplett auf das PHPUnit_Framework_Assert übertragen.
Hier ein Beispiel aus dem PHPUnit Manual um die Umsetzung zu veranschaulichen.
< ?php require_once 'PHPUnit/Framework.php'; class BankAccount { private $balance = 0; public function getBalance() { return $this->balance; } public function setBalance($balance) { PHPUnit_Framework_Assert::assertTrue($balance >= 0); $this->balance = $balance; } public function depositMoney($amount) { PHPUnit_Framework_Assert::assertTrue($amount >= 0); $this->balance += $amount; } public function withdrawMoney($amount) { PHPUnit_Framework_Assert::assertTrue($amount >= 0); PHPUnit_Framework_Assert::assertTrue($this->balance >= $amount); $this->balance -= $amount; } } ?>
Wenn eine der Bedingungen nicht erfüllt wird, dann wird eine PHPUnit_Framework_AssertionFailedError Exception geworfen. Diese kann explicit abgefangen und verarbeitet werden. Hierdurch entsteht ein Vorteil: die Methoden beinhalten viel weniger Quellcode, und die Überprüfung der Bedingungen ist leichter zu lesen und verstehen. Ein Nachteil entsteht jedoch, nämlich die Laufzeit Abhängigkeit zu PHPUnit. Allgemein finde ich, dass es eine gute Alternative ist schnell und sicher seine Bedingungen in der „Design by contract“ Entwicklungs-Methodik umzusetzen. Was haltet Ihr davon?
Finde ich gar nicht gut. Gerade in dem Beispiel sind die Prüfungen der Werte eigentlich Bestandteil der Businesslogik, daher sollte das auch explizit nur mit Code der Klasse abgebildet werden. Ungültige Parameter sollten entsprechend mit einer InvalidArgumentException o.ä. zurückgewiesen werden.
Die Prüfung in withdrawMoney() ob der Kontostand nach Abzug des Betrages noch größer 0 ist benötigt IMHO dann eine spezifische Exception. Gerade diese Prüfung ist auch etwas was ich im benutzenden Code noch gar nicht weiß:
try {
$account->withdrawMoney(500);
} catch (BalanceTooLowException $e) {
// do something
}
Somit habe ich im benutzenden Code nur die Abhängigkeit zu den Klassen des Account-Moduls. Mit Deinem Code wäre hier sofort auch wieder die Abhängigkeit zu PHPUnit präsent.
Also an sich ist das ein interessanter Ansatz. Jedoch bieten die meisten Frameworks für so etwas auch schicke Validatoren an, die man genauso schnell einsetzen kann und die auch lesbar sind. Zum Beispiel Zend_Validate_*
Ebenso sollte eine konkrete Exception geworfen werden, welche auch sagt was das Problem ist. Eben das bei Balance nur ein positiver Wert gesetzt werden darf. Dein Ansatz verlagert zuviel der Businesslogik aus der Klasse hinaus in die verwendende Klasse. Ziel sollte sein, dass man möglichst wenig über eine Modelklasse wissen muss um sie zu verwenden.
PHP hat auch einen eigenen kleinen Contract-Mechanismus:
http://www.php.net/assert bzw.
http://www.php.net/assert_options
Bei assert_options() hab ich mal mit der Callback Option bisschen rumgespielt und es kamen ganz interessante Mechanismen raus:
Pre-, Postcondition & Invariante in dem Docblock automatisch in assertions umwandeln (siehe aktuelle Annotations Diskusion), Exceptions werfen und gleich behandeln, Fallback Klassen u.v.m. :)
Die Callback-Funktion kann übrigens 3 Parameter annehmen: $script, $line und $message (ähnlich dem Errorhandler).
@all: ich stimme euch vollkommen zu. Ich bin auch nicht sehr begeistert von der festen Abhängigkeit zur PHPUnit_Framework. Der Ansatz von Frank Kleine deckt am besten die Bedingungen in der „Design by contract“ Entwicklungs-Methodik ab. Ergänzend würde ich die Bedingungen direkt in der Methoden-Dokumentation leicht verständlich dazu schreiben.
I think the manual phiunpt (a php unit testing framework) has the adequate manual to get you started into unit testing.You can start with the chapter (which is a fine introduction) and move onward.