Your SlideShare is downloading. ×
AdvancedTdd
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Introducing the official SlideShare app

Stunning, full-screen experience for iPhone and Android

Text the download link to your phone

Standard text messaging rates apply

AdvancedTdd

2,975
views

Published on

A power workshop during JAX 2007 on advanced techniques of test-driven development. It deals with acceptance tests using FIT as well as with mock objects, GUI testing and Groovy as a testing language …

A power workshop during JAX 2007 on advanced techniques of test-driven development. It deals with acceptance tests using FIT as well as with mock objects, GUI testing and Groovy as a testing language for Java.

Published in: Technology

0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
2,975
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
0
Comments
0
Likes
3
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Fortgeschrittene Techniken der Testgetriebenen Entwicklung JAX Powerworkshop, 23.04.2007 Tammo Freese, Johannes Link
  • 2. Agenda • Testgetriebene Entwicklung im Überblick • Akzeptanztests mit FIT • Unit Tests mit JUnit/Mockobjekte mit EasyMock • Legacy Code und Code Coverage • Testen von GUIs und Web-Applikationen • Testen mit Skriptsprachen • Diskussion
  • 3. Motivation • Software hat Geschäftswert durch zwei Qualitäten • Funktionale Qualität (Funktionalität, Fehlerfreiheit) • Strukturelle Qualität (Design/Codestruktur für Weiterentwicklung) • Hinzufügen neuer Funktionalität gefährdet funktionale und strukturelle Qualität • Verbesserung der strukturellen Qualität gefährdet die funktionale Qualität
  • 4. Testgetriebene Entwicklung • Testgetriebene Programmierung: Motiviere jede Verhaltensänderung am Code durch einen automatisierten Test (ständige Absicherung der funktionalen Qualität) • Refactoring: Vereinfache das Code-Design soweit wie möglich (funktionale Qualität ist durch Tests abgesichert) • (Häufige Integration: Integriere den Code so häufig wie nötig)
  • 5. Entwickler schreiben Unit Tests • testen Komponenten des Systems in Isolation • geben uns konkretes Feedback • ermöglichen sichere Änderungen • sichern Erhalt der vorhandenen Funktionalität • müssen bei jeder Integration zu 100% laufen • können funktionale Tests auf Systemebene nicht ersetzen!
  • 6. Kunden spezifizieren Akzeptanztests • testen das System überwiegend als Ganzes • geben unseren Kunden Vertrauen in die gelieferte Software • klären die Anforderungen frühzeitig an konkreten Beispielen • machen den Projektfortschritt sichtbar • müssen vom Kunden erstellt und gepflegt werden können • Unsere Aufgabe ist es, den entsprechenden Rahmen für ihre Automatisierung zu schaen!
  • 7. Akzeptanztests mit FIT • FIT: Framework for Integrated Test • Zum Schreiben und Ausführen automatischer Akzeptanztests • Testdaten werden tabellarisch erstellt (in HTML, mit Excel oder im Wiki) • Anbindung ans System in Java • Portierung für aktuelle Sprachen verfügbar • http://fit.c2.com
  • 8. Drei Fixture-Klassen • ColumnFixture testet Ein-/Ausgabewertemengen. • ActionFixture spielt ein Benutzerszenario durch und ist deshalb gut für GUI-orientierte Tests geeignet. • RowFixture prüft eine Ergebnismenge von Objekten mit ihren Attributen.
  • 9. Column Fixture (1)
  • 10. Column Fixture (2) import fit.ColumnFixture; public class AccountTransferFixture extends ColumnFixture { public String sourceAccount; public String targetAccount; public double amountTransferred; public boolean wasTransactionSuccessful() throws AccountException { return false; } }
  • 11. Action Fixture (1)
  • 12. Action Fixture (2) import fit.ActionFixture; public class AccountAdministrationFixture extends ActionFixture { public void newAccount() {} public void customerName(String name) {} public String accountNumber() { return "0"; } public void deposit(double money) throws AccountException { } public double balance() { return 0; } }
  • 13. Row Fixture (1) public class AccountListingItem { public String accountNumber; public double balance() { return 0; } public String customerName() { return "dummy"; } }
  • 14. Row Fixture (2) import fit.RowFixture; public class AccountListingFixture extends RowFixture { public Object[] query() throws Exception { List result = new ArrayList(); return result.toArray(); } public Class getTargetClass() { return AccountListingItem.class; } }
  • 15. Domänenspezifische Fixtures • wenn keine Fixture-Grundart so richtig passt • Erweiterungen von fit.Fixture über Hook- Methoden: • doTables(Parse tables) • doTable(Parse table) • doRows(Parse rows) • doRow(Parse row) • doCells(Parse cells) • doCell(Parse cell, int index)
  • 16. Übung 1: Akzeptanztestgetriebene Entwicklung • Bringen Sie die Testfälle in acceptance-tests/RichCalc.html schrittweise zum laufen! • (TaschenrechnerFixture.java ist eine ColumnFixture)
  • 17. Nachlese Übung 1 • Probleme bei akzeptanztestgetriebener Entwicklung • (Zu) große Schritte: Es dauert oft zu lange, einen Test auf grün zu bringen • Keine Möglichkeit, Design zu erzwingen • Keine Möglichkeit, Module isoliert ins Leben zu testen • Kombinatorische Explosion • Lösung: Unit Tests • Viele kleine Unit Tests schreiben • Jeden Akzeptanztest schrittweise erfüllen
  • 18. JUnit - Testframework für Java • Java-Framework zum Schreiben und Ausführen automatischer Unit Tests • Tests werden in Java codiert • Ist auch für zahlreiche andere Programmiersprachen erhältlich • http://junit.org
  • 19. Beispiel: Test import org.junit.Test; import static org.junit.Assert.*; public class EuroTest { @Test public void cents() { Euro two = new Euro(2.00); assertEquals(200, two.getCents()); } }
  • 20. Anatomie eines Testfallklasse • Testfallmethoden @Test public void ...() • Verwendung der statisch importierten Methoden assert...() und fail() • Fehlschlag von assert...() beendet den Testfall • Instanzvariablen für Testobjekte • @Before public void ...() zum Aufbau von Testobjekten und Testressourcen • @After public void ...() zur Ressourcenfreigabe
  • 21. Beispiel: Test Suite import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; @RunWith(value = Suite.class) @SuiteClasses( { CalculatorTest.class }) public class AllTests { }
  • 22. Eclipse: JUnit Testrunner
  • 23. Test/Code/Refactor – Zyklus JUnit: Failure JUnit: OK
  • 24. Test/Code/Refactor – Schritte • grün-rot: Schreibe einen Test, der zunächst fehlschlagen sollte. Schreibe gerade soviel Code, dass der Test fehlschlägt. • rot-grün: Schreibe gerade soviel Code, dass alle Tests laufen. • grün-grün: Eliminiere Duplikation und andere üble Codegerüche.
  • 25. Programmierzug grün - rot • Programming by Intention: Der Test verwendet die zu testende Funktion, als wäre sie schon realisiert. • Die Klassenschnittstelle wird im Test aus der Perspektive eines Verwenders entworfen. • Jeder Test geht einen kleinen Schritt. • Den Test zunächst fehlschlagen zu sehen, testet den Test selbst.
  • 26. Programmierzug rot - grün • Nur soviel Code schreiben, wie die Tests erfordern. • Mehr wäre weder durch die Tests spezifiziert, noch abgesichert. • Erst weitere Tests beweisen, dass die Lösung eventuell noch zu einfach ist.
  • 27. Refactoringzug grün - grün • Eliminierung von Duplikation! • Verbesserungen am Design des Programms, ohne sein beobachtbares Verhalten zu ändern, • um die Verständlichkeit zu erhöhen • um Designschwächen zu beheben • um die Änderbarkeit zu erhalten.
  • 28. Strikte Unit Tests • Ein Unit Test soll eine Klasse oder ein Klassenteam in Isolation testen. • Probleme: • Programmeinheiten arbeiten nicht isoliert. • Aufbau der Testumgebung ist oft aufwändig. • Testen von Ausnahmesituationen • langsame Tests bei Überschreiten der Systemgrenzen
  • 29. Isoliertes Testen • Für die Dauer der Tests ersetzen wir Abhängigkeiten zu mitwirkenden Programmeinheiten durch die Einführung einfacher Attrappen.  • Vorteile: • Tests laufen schnell. • Auftretende Fehler sind leicht zu lokalisieren. • Notwendige Testkombinatorik ist kleiner als beim Testen mehrerer Objekte.
  • 30. Isoliertes Testen
  • 31. Mock-Objekte • Ersetzen von der zu testenden Unit verwendeten Klassen oder Schnittstellen während des Tests • Vorgehen: • Verhalten und Erwartungen spezifizieren • Unit unter Verwendung des Mockobjekts testen • korrekte Verwendung verifizieren
  • 32. EasyMock – Dynamische Mock-Objekte • Erzeugung eines Mock-Objekts „on the fly“: • MyInterface mock = createMock(MyInterface.class); • Aufzeichnen des erwarteten Verhaltens: • expect(mock.myMethod(42))andReturn("Y"); replay(mock); • Verifikation der Verwendung des Verhaltens: • verify(mock); • http://easymock.org/
  • 33. Systemgrenzen im Test • Dummies und Mocks: • Was wir zur Isolation innerhalb des Systems verwenden, funktioniert auch an Systemgrenzen. • Bei komplexen Systemgrenzen: • Fassade/Adapter zur zusätzlichen Indirektion einführen • Testen vom System zu Fassade/Adapter • Testen von Fassade/Adapter zur Systemgrenze • funktionale Tests testen das Zusammenspiel
  • 34. Übung 2: Isolierte Unit Tests • Bringen Sie die Testfälle in acceptance-tests/RichCalc.html schrittweise zum laufen! • Diesmal aber: • Schreiben Sie Unit Tests für die Akzeptanztests • Setzen Sie Mockobjekte ein, falls nötig • Startlinie: calculator.unittests.CalculatorTest
  • 35. Code Coverage • Wie gut sind unsere Tests? • Wie groß ist die Abdeckung des Codes durch unsere Tests? • http://www.cenqua.com/clover/ • http://works.dgic.co.jp/djunit/ • http://www.eclemma.org/ • Welche Code-Änderungen werden durch unsere Tests entdeckt? • http://jester.sourceforge.net/ • Metriken als Spiegel der Gewohnheit
  • 36. Testen von Legacy Code • Legacy Code ist Code ohne automatisierte Tests! • Michael Feathers: „Code without tests is bad code. It doesn't matter how well written it is [...] With tests, we can change the behavior of our code quickly and verifiably. Without them, we really don't know if our code is getting better or worse.“
  • 37. Legacy Code Dilemma • Um Software sicher zu ändern, benötigen wir automatisierte Tests. • Um automatisierte Tests zu schreiben, müssen wir die Software ändern. • Refactoring ohne Tests unumgänglich
  • 38. Testen von bestehendem Code • Änderung des bestehenden Systems • neue Features • Entfernen von Bugs • Designverbesserungen • Optimierungen • Vergrößerung der Testabdeckung • Testgetriebene Entwicklung • Charakterisierende Tests • Integrationstests
  • 39. Störende Abhängigkeiten • Eine Klasse lässt sich nicht instanzieren • Eine Methode lässt sich nicht ausführen • Das Testergebnis lässt sich nicht ermitteln
  • 40. Vorgehen 1. Identifiziere Änderungspunkte 2. Finde Testpunkte 3. Brich Abhängigkeiten 4. Schreibe automatisierte Tests 5. Mache Änderung 6. Räume auf (Refactoring)
  • 41. Werkzeugkasten • Automatisierte Refactorings • JUnit Test-Framework • Mock-Objekte als Attrappen • Framework for Integrated Test (FIT) • Aspektorientierte Programmierung
  • 42. Einschleusen von Mocks • als Parameter • über testbare Unterklasse • mittels Überlagerung im Klassenpfad • mit aspektorientierter Programmierung • in C/C++ auch über Linker und Präprozessor • Jedes Testproblem lässt sich über eine weitere Indirektion lösen
  • 43. Meine Klasse lehnt sich gegen den Test auf • Ursachen: • Klasse kann nicht instanziert werden • Hinderliche Konstruktor-Parameter • Konstruktor hat Seiteneekte • Techniken: • Extrahiere und überschreibe Methode • Extrahiere Interface • Parametrisiere Konstruktor
  • 44. Extrahiere und Überschreibe (1) public class A { public class A { public A() { public A() { B b = makeB(); B b = new B(); b.doStuff(); b.doStuff(); ... ... } } protected B makeB() { } return new B(); } }
  • 45. Extrahiere und Überschreibe (2) public class TestableA extends A { protected B makeB() { return new DummyB(); } } public class DummyB extends B { public void doStuff() { } }
  • 46. Extrahiere Interface public class A { public class A { public A() { public A() { IB b = makeB(); B b = makeB(); b.doStuff(); b.doStuff(); ... ... } } protected IB makeB() { protected B makeB() { return new B(); return new B(); } } } }
  • 47. Parametrisiere Konstruktor public class A { public class A { private IB b; public A() { public A(IB b) { IB b = makeB(); this.b = b; b.doStuff(); b.doStuff(); ... ... } } } public A() { this(new B()); } }
  • 48. Weitere Techniken • Abzweigende Methode • Abzweigende Klasse • Statisch gemachte Methoden • Primitivere Parametertypen • Führe statischen Setter ein • Führe delegierende Instanz ein • Ersetze Singleton durch direkte Abhängigkeit • Extrahiere Methoden • Extrahiere Methodenobjekt
  • 49. Verbesserungen über die Zeit • Viele der Techniken machen das Design zunächst einmal hässlicher • Durch die ersten installierten Tests sind kleine Verbesserungen möglich • Kleine Verbesserungen lassen weitere Refactoringkandidaten erkennen • Schrittweise zu evolutionärem Design
  • 50. Testen von GUIs und Web-Applikationen • Wichtigster Grundsatz: Keine Logik im View • Entwurfsmuster zur Trennung von Präsentation und Geschäftslogik: • Application Facade • Model View Presenter
  • 51. Application Facade <<interface>> BookSearchFacade BookSearchDialog searchTitles(searchExpression:String) : List Book Title Author
  • 52. Application Facade mit Observer <<interface>> Observer changeOccurred() <<implements>> <<interface>> BookSearchFacade BookSearchDialog searchTitles(searchExpression:String) : List registerObserver(observer: Observer) changeOccurred() Book Title Author
  • 53. Model View Presenter <<interface>> <<interface>> BookSearchView BookSearchPresenter displayList(books:List) search (searchExpression:String) <<implements>> <<implements>> BookSearchDialog BookSearcher displayList(books:List) search (searchExpression:String)
  • 54. Testen mit Skriptsprachen • Dynamisch typisierte Skriptsprachen können das Testen vereinfachen • Beispiel Groovy: • Alle Java-Features und Bibliotheken verfügbar • Typisierung ist optional • „Duck-Typing“ ist möglich • Einfache Syntax für Blocks und Iteratoren
  • 55. Groovy: Vereinfachte Syntax • Viele Elemente optional: • public, Typisierung, Parameterklammern, Semikolon class CalculatorTest extends GroovyTestCase { private calculator void setUp() { calculator = new Calculator() ... } void testDisplayThreeDigits() { checkDisplay '1', '5', '9' } def checkDisplay(String[] buttons) { ... }
  • 56. Groovy: Closures und Collections • Einfache Blocksyntax • Mächtige Collections • Iteratoren mittels Blöcken def printer = { line -> println line } printer('Test') def buttons = ["1", "2", "3", "4"].reverse() def displayText = "" buttons.each { displayText += it } assert displayText == "4321"
  • 57. Groovy: Dynamisch typisierte Objekte • „Anonyme“ Objekte on the fly def display = {assert it == displayText} as IDisplay def display = [display: {println it}, enableDecimalSeparatorKey: { ... }, disableDecimalSeparatorKey: { ... } ] as IDisplay
  • 58. Übung 3: Wie es euch gefällt • Variante 1: Schreiben Sie mit Hilfe von Jemmy eine Taschenrechner-Fixture, die direkt die Swing-GUI bedient • Variante 2: Erweitern Sie die Groovy-Testsuite im Package groovy.calculator.unittests • Variante 3: Erweitern Sie GUI und GUI-Tests: public interface IDisplay { void display(String toDisplay); void disableDecimalSeparatorKey(); void enableDecimalSeparatorKey(); }
  • 59. Fragen & Diskussion
  • 60. Referenzen • Michael Feathers: Working Eectively with Legacy Code. Prentice Hall, 2004. • Johannes Link: Softwaretests mit JUnit - Techniken der testgetriebenen Entwicklung. Dpunkt-Verlag, 2005. • Rick Mugridge, Ward Cunningham: Fit for Developing Software. Prentice Hall, 2005. • Frank Westphal: Testgetriebene Entwicklung mit JUnit & FIT. Dpunkt-Verlag, 2005.