Universität Karlsruhe (TH) Forschungsuniversität gegründet 1825 Testgetriebene Entwicklung
Testgetriebene Entwicklung Test/Code/Refactor-Zyklus: Motiviere jede Verhaltensänderung am Code durch einen automatisierten Test. Refaktorisierung und einfaches Design: Bringe den Code immer in die Einfache Form. Häufige Integration: Integriere den Code so häufig wie nötig. Paarprogrammierung: Besiege den inneren Schweinehund. 2
Zustandsdiagramm der testgetriebenen Entwicklung umstrukturieren Keine Ideen für mehr Testcode Grün (Alle Tests laufen) Zustandsdiagramm idealisiert Testcode vor Anwendungscode Kleine Schritte Inkrementelles Design Testcode schreiben Anwendungscode schreiben Rot (Mindestens ein Test scheitert) 3
Test/Code/Refactor-Zyklus grün rot: Schreibe einen Test der fehlschlägt. Schreibe gegebenenfalls gerade soviel Code, dass der Test übersetzt werden kann. rot grün: Schreibe gerade soviel Code, dass alle Tests erfolgreich laufen. grün grün: Eliminiere Duplikationen und andere üble Gerüche im Code. 4
Unit Tests werden vom Entwickler selbst geschrieben geben konkretes Feedback und Sicherheit ermöglichen sichere Änderungen sichern Erhalt der vorhandenen Funktionalität müssen bei jeder Code-Integration zu 100 % laufen 5
Effizientes Testen möglichst zeitnah zur Programmierung automatisiert und damit wiederholbar muss Spaß machen Testen so oft und so einfach wie Kompilieren Fehler finden, nicht Fehlerfreiheit beweisen 6
Testrahmenwerke gibt es für fast jede denkbare Programmiersprache http://de.wikipedia.org/wiki/liste_von_modultest-software vereinfachen das Schreiben der Testfälle automatische Ausführung der Testfälle und Anzeige der Ergebnisse Zusammenfassen von Testfällen zu Testmengen bekannteste Vertreter für Java: JUnit & TestNG 7
JUnit 4: Ein Testrahmenwerk für Java erlaubt schnellen und hierarchischen Aufbau von Testmengen liefert die fehlgeschlagenen Testfälle standardmäßig nur textuelle Anzeige, grafische Anzeige wird von der Programmierumgebung realisiert in Eclipse integriert Autoren: Erich Gamma, Kent Beck http://www.junit.org 8
Ausführung in Eclipse Im Kontextmenü einer Testklasse Run As JUnit Test zu wählen; dann werden die Testmethoden ausgeführt. Testklassen können auch in Paketen zusammengefasst werden, und diese wiederum in größere Pakete; bei Auswahl mit Run As JUnit Test erfolgt das Ausführen aller enthaltenen Testmethoden. Die Anzeige erfolgt in einem eigenen Fenster. Fehlgeschlagener Testfall 9
JUnit 4: Testklasse enthält zusammengehörige Testfälle in Form von Methoden hält i.d.r. Referenzen auf die zu prüfenden Testobjekte Vergleiche von Soll- und Ist-Werten mittels Zusicherungen aus der Klasse org.junit.assert Kennzeichnung der Test-Methoden durch Annotationen 10
JUnit 4: Annotationen zur Makierung von Tests @Test Markiert einen Testfall Erwartete Ausnahmen können wie folgt angegeben werden: @Test(expected = SQLException.class) Es kann auch die maximale Laufzeit in Millisekunden festgelegt werden: @Test(timeout = 42) @Ignore Markiert einen Testfall, der nicht ausgeführt werden soll Die Bergründung wird als Zeichenkette angegeben: @Ignore("Funktionalität fehlt noch.") @Test public void buchausleihen() { 11
JUnit: Annotationen zum Aufund Abbau von Testressourcen @Before Markiert eine Methode, die vor jedem Testfall ausgeführt wird. @After Markiert eine Methode, die nach jedem Testfall ausgeführt wird. @BeforeClass Markiert eine Klassenmethode, die vor Instanzierung der Testklasse ausgeführt wird. @AfterClass Markiert eine Klassenmethode, die nach allen anderen bisher genannten Methoden ausgeführt wird. 12
JUnit 4: Einige Zusicherungen assertequals(object expected, Object actual) Schlägt fehl, wenn der Aufruf der equals-methode von expected mit dem Parameter actual den Wert falsch zurück gibt. assertequals(object[] expecteds, Object[] actuals) Schlägt fehl, wenn der Aufruf der equals-methode auf einem der Feldelemente von expecteds mit dem entsprechenden Feldelement aus actuals den Wert falsch zurück gibt oder wenn die Felder unterschiedlich lang sind. assertequals(double expected, double actual, double delta) Schlägt fehl, wenn sich der erwartete und der tatsächliche Wert um mehr als die angegebene Differenz unterscheiden. 13
JUnit 4: Noch mehr Zusicherungen asserttrue(boolean condition) assertfalse(boolean condition) Schlägt fehl, wenn der Ausdruck falsch/wahr ist. assertnull(object object) assertnotnull(object object) Schlägt fehl, wenn die Referenz nicht null/null ist. assertsame(object expected, Object actual) Schlägt fehl, wenn die Referenzen auf unterschiedliche Objekte zeigen. fail() Schlägt immer fehl. Zu all diesen Methoden gibt es eine Variante mit einer Zeichenkette als ersten Parameter für ein Nachricht im Fehlerfall. 14
JUnit 4: Beispiel package demo; import static org.junit.assert.asserttrue; import org.junit.after; import org.junit.before; import org.junit.test; public class BibliothekTest { private Bibliothek bib; @Before public void baueauf() { bib = new Bibliothek(); @Test public void buchistinbibliothek() { boolean b = bib.pruefeverfuegbarkeit("faust"); asserttrue("faust muss in der Bibliothek sein.", b); @After public void raeumeauf() { bib = null; Zusicherungen werden per import static eingeblendet. Methoden mit @Before dienen zum Aufbau gemeinsamer Testressourcen. @Test markiert einen Testfall. Methoden mit @After dienen zum Abbau gemeinsamer Testressourcen. 15
JUnit 4: Ausführungsreihenfolge [ ] public class BibliothekTest { [ ] 1 2 3 6 4 8 5 7 @BeforeClass public void verbindemitdb() { @Before public void baueauf() { @Test public void buchistinbibliothek() { @Test public void buchausleihen() { @After public void raeumeauf() { @AfterClass public void trennevondb() { Die Reihenfolge der Testfälle ist nicht garantiert. Die Punkte 3 und 6 könnten auch vertauscht sein 16
Auf erwartete Ausnahmen testen [ ] @Test(expected = NUllPointerException.class) public void keinnull() throws Exception { bib.pruefeverfügbarkeit(null); [ ] Möglichst robustes Verhalten im Fehlerfall testen. Unerwartete Ausnahmen werden von JUnit als Fehler registriert. 17
Übung 1: Eine Testklasse für die Klasse bank.account public Account(String customer) public String getcustomer() public int getbalance() public void deposit(int amount) public void withdraw(int amount) public void transfer(int amount, Account recipient) Für deposit, withdraw, transfer sind nur positive Beträge erlaubt, ansonsten wird eine IllegalArgumentException geworfen. 18
Wir überlegen uns erste Testfälle Erzeuge neues Konto (Account) für Kunden. Mache eine Einzahlung (deposit). Mache eine Abhebung (withdraw). Überweisung zwischen zwei Konten. Verbiete negative Beträge. 19
Wir entwerfen einen Test, der zunächst fehlschlagen sollte public class AccountTest { @Test public void createaccount() { Account a = new Account("Customer"); assertequals("customer", a.getcustomer()); assertequals(0, a.getbalance()); 20
Wir schreiben gerade soviel Code, dass sich der Test übersetzen lässt public class Account { public Account(String customer) { public String getcustomer() { return null; public int getbalance() { return 0; 21
Wir prüfen, ob der Test fehlschlägt 22
Wir schreiben gerade soviel Code, dass der Test erfüllt sein sollte public class Account { public Account(String customer) { public String getcustomer() { return "Customer"; public int getbalance() { return 0; 23
Wir prüfen, ob der Test durchläuft 24
Wir entfernen Duplikation Aber wo ist sie? public class Account { public Account(String customer) { public String getcustomer() { return "Customer"; public int getbalance() { return 0; public class AccountTest extends TestCase { public void testcreateaccount() { Account a = new Account("Customer"); assertequals("customer", a.getcustomer()); assertequals(0, a.getbalance()); 25
Wir entfernen Duplikation public class Account { private String customer; public Account(String customer) { this.customer = customer; public String getcustomer() { return customer; public int getbalance() { return 0; 26
Wir prüfen, ob der Test weiterhin läuft 27
Tests und Code im Wechselspiel Der fehlschlagende Test entscheidet, welchen Code wir als nächstes schreiben, um die Entwicklung der Programmlogik voranzutreiben. Wir entscheiden anhand des bisher geschriebenen Code, welchen Test wir als nächstes angehen, um die Entwicklung des Designs weiter voranzutreiben. 28
Auswahl des nächsten Testfalls Erzeuge neues Konto (Account) für Kunden. Mache eine Einzahlung (deposit). Mache eine Abhebung (withdraw). Überweisung zwischen zwei Konten. Verbiete negative Beträge. 29
Nächster Test: Einzahlen public class AccountTest extends TestCase { [ ] public void testdeposit() { Account a = new Account("Customer"); a.deposit(100); assertequals(100, a.getbalance()); account.deposit(50); assertequals(150, a.getbalance()); public class Account { [ ] private int balance = 0; public int getbalance() { return balance; public void deposit(int amount) { balance += amount; 30
Rolle der Unit Tests Tests zur Qualitätssicherung: Die Tests sichern die vorhandene Funktionalität Test-First führt zu evolutionärem Design : Das Systemdesign entsteht Stück für Stück. Tests zur Schnittstellendefinition: Test verwendet schon Klassen und Methoden, bevor sie überhaupt definiert sind. Tests zur Modularisierung: Testbarkeit erfordert Entkopplung der Programmteile. Tests als ausführbare Spezifikation und Dokumentation 31
Übung 2: Schritt für Schritt Der Quellcode der Euro-Klasse ging verloren, nur die Test wurden gerettet. Zur Erinnerung: Kleine Schritte! Von rot nach grün! Die einfachste Lösung! Mach es lauffähig! Mach es richtig! 32
Nachbereitung zu Übung 2 Euro-Klasse ist durch die vorhandenen Tests unterspezifiziert. Viele Tests laufen ohne Codeänderung! Sind diese Tests sinnlos? Welche Tests sollten ergänzt werden? Welche Implementierungsunterschiede sind möglich? 33
Testheuristik: Eigenschaften testen, nicht Methoden Testfälle orientieren sich an Anforderungen und Eigenschaften, nicht an den zu testenden Methoden. Es gibt keine 1:1 Entsprechung von Testfallmethode zu getesteter Methode. Testfälle sollen am Beispiel dokumentieren, wie die Klassen korrekt zu verwenden sind. 34
Testheuristik: Implementierungsunabhängigkeit Tests sind gegen die öffentliche Klassenschnittstelle gerichtet. Tests, die auf den Innereien einer Klasse basieren, sind äußerst fragil. Sich Zugriff auf Variablen oder private Methoden zu wünschen zeigt, dass dem Code noch eine entscheidende Designidee fehlt. 35
Testheuristik: An den Rändern testen Die meisten algorithmischen Fehler treten an den Rändern der erlaubten Wertebereiche auf: Auswahl der Testexemplare dort, anstatt irgendwo! Lässt sich der Wertebereich in mehrere für den Test äquivalente Proben unterteilen? Pro Äquivalenzklasse mindestens eine Probe! 36
Testheuristik: Ergebnisse im Test festschreiben Erwartete Werte werden als Konstanten kodiert, nicht noch mal im Test berechnet. Reproduzieren wir Anwendungslogik im Test, reproduzieren wir auch ihre Fehler. 37
Testheuristik: Orthogonale Testfälle Unabhängig voneinander sind Testfälle dann, wenn sie sich auf orthogonale Aspekte beziehen. Häufig lässt sich ein Test extrem vereinfachen, indem er Annahmen macht, die ein anderer Test bereits verifiziert hat. Kommt man in die Not, zu viele Tests anpassen zu müssen, nur um eine Codeänderung machen zu können, sind die Tests nicht orthogonal. 38
Testheuristik: Testklassen 1:1 Entsprechung zwischen Testklasse und Anwendungsklasse ist nicht zwingend. Testklassen sind ein Mechanismus für die Wiederverwendung von Testcode. Testklassen, die einen ähnlichen Testrahmen benötigen, sind in einem Paket. 39
Weitere Testheuristiken Vergiss Ausnahmen und Fehlerfälle nicht! Wähle aussagekräftige Testklassen- und Testmehtodennamen! Entferne Redundanz im Testcode! Halte Testfälle kurz und verständlich! 40
Testtechnik: Mock-Objekte Testen von innen zahlreiche Bibliotheken: EasyMock, jmock ersetzen eine echte Implementierung und verifizieren korrektes Verhalten des Klienten Unterscheide Mock-Objekte von Stummel- Objekten und Attrappen Stummel-Objekte und Attrappen ersetzen auch echte Implementierung für Testzwecke Stummel-Objekte und Attrappen verifizieren selbst nichts 41
Vorteile von Mock-Objekten gut zum Testen von Protokollen, der richtigen Reihenfolge von Methodenaufrufen und den dabei übergebenen Parametern reduzieren Abhängigkeiten reduzieren Wartezeiten kapseln/beschreiben erwartetes Verhalten können Verhalten zeigen, dass ansonten schwer zu reproduzieren ist fördern Programmierung gegen Schnittstellen 42
Indikatoren gegen das Testen mit Mock-Objekten Mock-Objekt enthält viel Logik statt fest verdrahteter Werte Mock-Objekt dupliziert Anwendungslogik echte Objekte leicht(er) verwendbar Mock-Objekt ruft andere Mock-Objekte auf 43
Testmuster für Mock-Objekte Instanzen der Mock-Objekte erstellen initiale Zustände der Mock-Objekte setzen erwartete Werte der Mock-Objekte setzen Anwendungscode mit Mock-Objekten als Parameter aufrufen Verifizieren der erwarteten Werte in den Mock- Objekten 44
Referenzen Kent Beck: Extreme Programming Explained - Embrace Change. Addison-Wesley, 2005. Kent Beck: Test-Driven Development: By Example. Addison-Wesley, 2003. Martin Lippert et al.: Software entwickeln mit extreme Programming. Erfahrungen aus der Praxis. dpunkt.verlag, 2002. Johannes Link: Softwaretests mit JUnit, 2. Auflage. dpunkt.verlag, 2005. Frank Westphal. Testgetreibene Entwicklung mit JUnit & FIT. dpunkt.verlag, 2006. http://www.agiledata.org/essays/tdd.html 45