• Save
AdvancedTdd
Upcoming SlideShare
Loading in...5
×
 

AdvancedTdd

on

  • 5,492 views

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.

Statistics

Views

Total Views
5,492
Views on SlideShare
5,489
Embed Views
3

Actions

Likes
3
Downloads
0
Comments
0

1 Embed 3

http://www.slideshare.net 3

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

AdvancedTdd AdvancedTdd Presentation Transcript

  • Fortgeschrittene Techniken der Testgetriebenen Entwicklung JAX Powerworkshop, 23.04.2007 Tammo Freese, Johannes Link
  • 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
  • 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
  • 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)
  • 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!
  • 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!
  • 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
  • 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.
  • Column Fixture (1)
  • 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; } }
  • Action Fixture (1)
  • 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; } }
  • Row Fixture (1) public class AccountListingItem { public String accountNumber; public double balance() { return 0; } public String customerName() { return "dummy"; } }
  • 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; } }
  • 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)
  • Übung 1: Akzeptanztestgetriebene Entwicklung • Bringen Sie die Testfälle in acceptance-tests/RichCalc.html schrittweise zum laufen! • (TaschenrechnerFixture.java ist eine ColumnFixture)
  • 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
  • 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
  • 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()); } }
  • 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
  • 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 { }
  • Eclipse: JUnit Testrunner
  • Test/Code/Refactor – Zyklus JUnit: Failure JUnit: OK
  • 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.
  • 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.
  • 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.
  • 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.
  • 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
  • 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.
  • Isoliertes Testen
  • 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
  • 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/
  • 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
  • Ü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
  • 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
  • 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.“
  • 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
  • Testen von bestehendem Code • Änderung des bestehenden Systems • neue Features • Entfernen von Bugs • Designverbesserungen • Optimierungen • Vergrößerung der Testabdeckung • Testgetriebene Entwicklung • Charakterisierende Tests • Integrationstests
  • 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
  • Vorgehen 1. Identifiziere Änderungspunkte 2. Finde Testpunkte 3. Brich Abhängigkeiten 4. Schreibe automatisierte Tests 5. Mache Änderung 6. Räume auf (Refactoring)
  • Werkzeugkasten • Automatisierte Refactorings • JUnit Test-Framework • Mock-Objekte als Attrappen • Framework for Integrated Test (FIT) • Aspektorientierte Programmierung
  • 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
  • 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
  • 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(); } }
  • Extrahiere und Überschreibe (2) public class TestableA extends A { protected B makeB() { return new DummyB(); } } public class DummyB extends B { public void doStuff() { } }
  • 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(); } } } }
  • 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()); } }
  • 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
  • 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
  • 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
  • Application Facade <<interface>> BookSearchFacade BookSearchDialog searchTitles(searchExpression:String) : List Book Title Author
  • Application Facade mit Observer <<interface>> Observer changeOccurred() <<implements>> <<interface>> BookSearchFacade BookSearchDialog searchTitles(searchExpression:String) : List registerObserver(observer: Observer) changeOccurred() Book Title Author
  • Model View Presenter <<interface>> <<interface>> BookSearchView BookSearchPresenter displayList(books:List) search (searchExpression:String) <<implements>> <<implements>> BookSearchDialog BookSearcher displayList(books:List) search (searchExpression:String)
  • 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
  • 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) { ... }
  • 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"
  • 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
  • Ü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(); }
  • Fragen & Diskussion
  • 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.