Beiträge gettagt mit Unit Testing
In case of emergency…
08. Jan
Entwickler sind Menschen. Fehler passieren. Und wenn Entwicklern ein Fehler passiert, dann nennt man das Bug.
Das in einem Projekt nach dem Release nochmal Bugs auftreten, ist nicht wahrscheinlich, es ist absolut sicher. Meist haben diese Bugs dann eine (negative) finanzielle Auswirkung, oder zumindest ist das die Vermutung des Kunden. Der Kunde befürchtet einen Verdienstausfall, der natürlich so kurz wie möglich gehalten werden muss; und das rationale Denken beim Kunden, Manager und Entwickler setzt leider allzu häufig aus.
Die pragmatischen Programmierer Andrew Hunt und David Thomas haben einige Tipps, wie man sich bei der Fehlersuche verhalten sollte:
Lösen Sie das Problem, nicht die Schuldfrage.
Der Bug ist bereits aufgetreten, es wird Ihnen jetzt nichts helfen, wenn Sie wissen, wer Schuld daran ist. Leider ist es oft viel einfacher, rauszufinden, wer schuld ist, als rauszufinden, wie der Bug gefixt werden kann. Deshalb tendieren viele Menschen eher zur ersten Alternative. Was man dagegen tun kann, habe ich bereits in „Denk positiv!” geschrieben.
Wenn Sie auf Grund von Verträgen oder SLAs wissen müssen, wer die Schuld trägt, so ist für die Klärung dieser Frage nach Beseitigung des Fehlers noch genug Zeit.
Keine Panik!
Wenn man so schnell wie möglich versucht, den Bug zu fixen, ohne genauer darüber nachzudenken, ist die Gefahr groß, dass weitere Bugs ins System eingeführt werden, während einer behoben wird. Und natürlich fügen diese Bugs Ihrem Kunden meist einen noch größeren Schaden zu, als der ursprüngliche Bug.
Das absolute Highlight ist, dass in solchen Situationen gerne auch die Unit-Tests (die Folgefehler entdeckt hätten) vor dem Deployment übersprungen werden, um den Fix möglichst schnell live zu bekommen. Näher werden Sie Kamikaze in Ihrem Leben wahrscheinlich nicht kommen. Ich selbst war leider schon so nahe dran.
Bleiben Sie also ruhig und stellen Sie sicher, dass Sie eine saubere Entwicklunsumgebung haben, bevor Sie mit dem Bugfixing beginnen. Fixen Sie den Bug also nicht in einem lokalen Checkout, der Modifikationen enthält, die noch nicht deployed wurden.
Wenn Sie Hufabdrücke sehen, sollten Sie an Pferde denken, nicht an Zebras.
Wenn ein Fehler auftritt, nachdem Sie den Code geändert haben, dann ist es wahrscheinlich, dass Ihre Änderung den Fehler verursacht hat. Suchen Sie also dort. Entwickler sind gerne der Ansicht, dass die letzte Änderung so marginal war, dass sie keinen Fehler verursacht haben kann. „Sicher muss es ein Problem im Kernel sein, dass nur Donnerstags bei Regen auftritt und deshalb nicht entdeckt wurde.” Sehr wahrscheinlich ist das nicht das Problem, sondern die marginale Änderung war der Auslöser.
(Die einzige Ausnahme von dieser Regel ist, wenn Sie mit Destrukturen in PHP arbeiten, hier habe ich schon Phänomene erlebt, die nur von Scully und Mulder gelöst werden können. Beim Einsatz von Destruktoren in PHP gilt also – Trust No One.)
Nicht annehmen, sondern beweisen.
Obwohl ein Bug einem Entwickler beweist, dass er nicht unfehlbar ist, verhält man sich oft weiterhin so, als wäre man unfehlbar. Ein Entwickler nimmt dann bei der Fehlersuche einfach an, dass eine bestimmte Methode auf jeden Fall fehlerfrei funktioniert und überspringt diese beim Debuggen. Tun Sie das nicht. Prüfen Sie jede kleine Eventualität, jede noch so einfache Methode und beweisen Sie, das diese mit den Eingaben, die produktiv zu einem Fehler führen, korrekt arbeitet. Auch dieses Thema hatte ich bereits in „Don’t assume!” tiefergehend diskutiert.
Wenn Sie diese einfachen Regeln befolgen, dann haben Sie beim nächsten Bug deutlich höhere Chancen, diesen schnell, nachhaltig und ohne Folgefehler zu beseitigen.
new-Operator in PHP überladen…
03. Jan
…oder besser nicht? Mit der Extension test_helpers, die Sebastian Bergmann in seinem PHPUnit Channel veröffentlicht hat, ist es möglich, einzugreifen, wenn ein Objekt mit Hilfe des new-Operators erzeugt wird und stattdessen eine andere Klasse zu instanziieren. Das kann vor allem bei Tests nötig werden, wenn es die Architektur einer Anwendung nicht erlaubt, einzelne Klassen auszutauschen:
class NewsFeedGenerator {
public function generateFeed() {
$newsTable = new NewsEntryTable();
foreach ($newsTable->getEntries() as $entry) {
// hier wird der Newsfeed generiert.
}
}
}
Wenn Sie für diese Klasse einen Unit-Test schreiben möchten, der prüft, ob die Klasse einen korrekten Newsfeed generiert, dann sind sie im Test immer davon abhängig, dass sie wissen, welche Einträge aktuell in der Datenbank stehen. Alternativ könnten Sie natürlich auch sicherstellen, dass die Datenbank im Voraus mit den entsprechend erwarteten Einträgen gefüllt wird.
Ein Unittest sollte allerdings eine einzelne Komponente (in diesem Fall den Newsfeed-Generator) isoliert testen, damit der Test nicht fehl schlägt, bloss weil die Datenbank im Moment nicht verfügbar ist. Aus diesem Grund werden in Unit-Tests Mock-Objekte eingesetzt, die das gewünschte Verhalten simulieren. Ein Mock für die Klasse NewsEntryTable könnte ganz einfach implementiert werden:
class NewsEntryTableMock {
public function getEntries() {
return array(
array('id' => 1, 'title' => 'Title 1', 'desc' => 'Description 1'),
array('id' => 2, 'title' => 'Title 2', 'desc' => 'Description 2')
// etc.
);
}
}
Wenn Sie diese Klasse verwenden, um den Newsfeed zu erzeugen, können Sie sich sicher sein, dass Sie immer die selben Daten als Basis verwenden.
Doch wie bekommen Sie die Klasse NewsFeedGenerator dazu, diese Datenquelle zu nutzen?
Mit Hilfe der neuen Extension ist das ganz einfach, Sie ersetzen die Klasse NewsEntryTable durch NewsEntryTableMock:
function overload_callback($className) {
if ($className === 'NewsEntryTable') {
return 'NewsEntryTableMock';
}
return $className;
}
set_new_overload('overload_callback');
Wenn Sie nun eine Instanz von NewsEntryTable erzeugen möchten, dann erzeugt PHP stattdessen eine Instanz von NewsEntryTableMock und sie können Ihren Newsfeed-Generator isoliert testen. Problem gelöst, möchte man meinen.
Das Problem ist jedoch nicht, dass ihr Test nicht isoliert ablaufen kann, sondern dass Sie eine feste Kopplung zwischen NewsFeedGenerator und NewsEntryTable in Ihrer Applikation haben. Was passiert, wenn Sie die Datenquelle auch im Betrieb austauschen müssen, da die Newseinträge nicht mehr in einer Datenbank sondern einer XML- oder CSV-Datei gespeichert werden, oder sogar über einen Service von einer anderen Applikation abgefragt werden müssen? Mit der aktuellen Architektur haben Sie dazu keine Möglichkeiten. Mit dem Einsatz von Dependency Injection jedoch schon:
class NewsFeedGenerator {
protected $dataSource;
public function __construct(NewsDataSource $dataSource) {
$this->dataSource = $dataSource
}
public function generateFeed() {
foreach ($this->dataSource->getEntries() as $entry) {
// hier wird der Newsfeed generiert.
}
}
}
interface NewsDataSource {
public function getEntries();
}
class DBNewsDataSource implements NewsDataSource {
public function getEntries() {
// Aus der Datenbank lesen
}
}
$ds = new DBNewsDataSource();
$generator = new NewsFeedGenerator($ds);
Die beiden Klassen sind nun nicht mehr voneinander abhängig, NewsFeedGenerator hat nur noch eine Abhängigkeit auf das entsprechende Interface, nicht mehr auf die Implementierung. Sie können die Implementierung für den Test oder auch im Live-Betrieb austauschen, ohne dazu die Extension verwenden zu müssen.
Sie mögen Sich nun fragen, wo das Problem liegt, wenn Sie die Klasse durch den Einsatz der test_helpers Extension austauschen, da Sie feste Abhängigkeiten haben. Ein Test für schlecht designten Code ist sicher besser als kein Test für schlecht designten Code. Das hängt jedoch davon ab, was man sich von Tests verspricht. Ich war noch nie ein großer Freund von Unit-Tests, es langweilt mich meistens, Tests zu schreiben. Was mich jedoch dazu gebracht hat, trotzdem Tests zu schreiben, ist, dass ich festgestellt habe, dass die Architektur der Applikation besser wird. Tests sind ein zweiter Client für die Applikation, der uns zwingt, die Applikation aus einem anderen Blickwinkel zu sehen. Durch Tests deckt man Abhängigkeiten auf und wird gezwungen, diese zu lösen. Die ganze Applikation wird loser gekoppelt und einzelne Komponenten leichtegwichtiger.
Das Risiko, das ich also beim Einsatz dieser Extension sehe, ist, dass man nicht mehr gezwungen wird, über die Kopplung nachzudenken. Damit geht ein großer Vorteil von Unit-Tests verloren. Sofern Sie sich sicher sind, dass eine Entkopplung zweier Klassen keine weiteren Vorteile (oder sogar Nachteile) bringt, sollte einem Einsatz der Extension nichts mehr im Wege stehen.
Wenn Sie also test-helpers einsetzen möchten, dann denken Sie jedes Mal darüber nach, ob Sie nicht ein grundlegendes Problem mit Abhängigkeiten haben und besser Ihre Applikation refaktorisieren sollten.
Xing
LinkedIn
Twitter
Ohloh
Slideshare
Facebook
Delicious
Github
Technorati
Flickr
Last.fm
YouTube
Amazon Wishlist