Einführung ins Testen
Bevor wir mit dem Testen loslegen können, sollten erstmal ein paar grundsätzliche Aussagen zum Testing geklärt werden. Hierdurch sollte einige Klarheit geschaffen werden, wofür testing gut ist, was man testen kann, was man testen will und was alles dazu gehört.
Negatives Beispiel: Eine mail sende, checken ob die ankommt, noch eine senden, checken ob Dublette.
Bessere Lösung: Ein test, ob mail ankommt und im System passiert was soll (Datenbank Eintrag). Zweiter test, Datenbank in definierten Zustand bringen (mocken) und dann eine mail versenden.
Beispiel: testExceptionOnNull() -> erwartet, dass Exception geworfen wird wenn null als parameter übergeben wird. Das hilft auch bei gefundenen Fehlern eine genaue Beschreibung zu bekommen.
assertNotNull() statt assertFalse(is_null(...))
Test die nicht geschafft wurden zu implementieren. z.B. weil das mocking zu komplex ist.
Wenn Tests nicht möglich sind und ggf. der Code umgebaut werden muss.
Begrifflichkeiten
- Test
- Test eines einzelnen Verhaltens einer Funktionlität. Ein Test sollte immer so klein wie möglich sein. Beispiel: Rufe eine Funktion mit dem Paramter "5" auf und erwarte, dass 7 zurückkommt.
- Test Case
- Sammlung von Tests einer Funktionalität/Modul. Ein Test case kann sich z.B. auf eine Funktion beziehen und einzelne Tests beinhalten wie "Rufe die Methode mit dem Parameter 0 auf, Rufe die Methode mit einem String auf. Rufe die Methode mit null auf.
- Test Suite
- Sammlung von TestCases
- Mocking
- Benötigte Komponenten simulieren/in definierten Zustand bringen. Das kann zum Beispiel das erzeugen eines Datenbankeintrags sein. In komplizierteren Fällen könnte man aber z.B. auch eine Mail-Funktionalität simulieren (Redspark_RsMail austauschen gegen MyMail) um zu testen, ob eine mail versendet werden würde.
Tests sind atomar
Atomar heißt, dass ein Test so klein wie möglich sein sollte und nur einfache, nicht komplexe Sachverhalte testet.Negatives Beispiel: Eine mail sende, checken ob die ankommt, noch eine senden, checken ob Dublette.
Bessere Lösung: Ein test, ob mail ankommt und im System passiert was soll (Datenbank Eintrag). Zweiter test, Datenbank in definierten Zustand bringen (mocken) und dann eine mail versenden.
Tests sind unabhängig
Ein test darf nicht von einem anderen Test abhängen. Die Beispiele von oben sind auch hier gültig. Vor jedem Test sollten alle beteiligten Komponenten in einen definierten Zustand gebracht werden (reset, mocking).Test sind deterministisch
Ein Test sollte immer unter gleichen Bedingungen ausgeführt werden. Zufallszahlen sind eine schlechte Praxis, da gefundene Fehler auf diese Weise nicht zuverlässig lokalisiert werden können. Stattdessen sollte man lieber Grenzwerte testen oder ggf. per Schleife viele Werte simulieren.Performance
Tests dürfen lange dauern. Ein test sollte nicht optimiert werden um Zeit zu sparen. Dadurch schleichen sich eher Fehler ein. Das Ausführen der Tests soll ja automatisiert passieren, kann also auch mal nachts durchlaufen.Tests sind konkret
Ein Test sollte sich ruhig auf konkrete Daten beziehen. Feste Texte oder konkretes Markup im Test sind durchaus erwünscht. Somit werden z.B. Folgefehler vermieden (Kaputtes Translate) und bei Änderungen ein Bewusstsein für die Auswirkungen geschaffen.Dokumentation, Kommentare, Sprechende Name
Test sollten sprechende Namen haben. Am besten sollte direkt formulieren, was man testet bzw. erwartet.Beispiel: testExceptionOnNull() -> erwartet, dass Exception geworfen wird wenn null als parameter übergeben wird. Das hilft auch bei gefundenen Fehlern eine genaue Beschreibung zu bekommen.
Abstraktion ist nur bedingt gut
Durch Abstraktion kommen schnell Fehler zustande. Es besteht dort immer die Gefahr, dass der gleiche Fehler aus der Programmierung dort ebenfalls gemacht wird. Deshalb sollte man auf Abstraktion weitestgehend verzichten. Einfache Schleifen sind ok, Vererbung und komplexere Strukturen sollten vermieden werden.Eigene Assert
Eigene Asserts sind hilfreich, um präzise und sprechend Erwartungen zu formulieren. Beispiel:assertNotNull() statt assertFalse(is_null(...))
3 Schritte eines Tests
Ein Test besteht in der Regel aus 3 Phasen:- Setup: Mocking, Daten in definierten Zustand bringen
- Aufruf: z.B. das dispatchen einer Action, oder der Aufruf einer Methode
- Erwartungen: Asserts formulieren. Formulieren, was hätte passieren sollen. Am besten auch mit sprechenden Meldungen.
Testbarkeit
Damit der Code sinnvoll getestet werden kann, sollten bereits bei der Programmierung bestimmte Regeln eingehalten werden.Feste Abhängigkeiten sind gift:
<?php
$mail = new Zend_Mail();
?>
Abhängigkeiten via Broker sind mockbar:
<?php
Broker = new Broker_Mock();
Broker::setMail(new Redspark_Mail_Mock());
$mail = Broker::newMail();
?>
Beste, aber komplexeste Lösung - Dependency injection
<?php
$x->setMail(new Redspark_Mail_Mock());
$mail = $this->getMail();
?>
Was tun, wenn code nicht testbar ist?
Man sollte an entsprechner Stelle im Test beschreiben, warum es nicht Testbar ist.Test die nicht geschafft wurden zu implementieren. z.B. weil das mocking zu komplex ist.
$this->markIncomplete();
Wenn Tests nicht möglich sind und ggf. der Code umgebaut werden muss.
$this->markSkipped();