real life tdd
DESCRIPTION
Viele Softwareprojekte starten mittlerweile mit dem Anspruch testgetrieben entwickelt zu werden. Der Anfang gestaltet sich in der Regel einfach und die Entwickler erkennen schnell die Vorzüge von TDD. Leider kommt es in vielen Fällen im Laufe der Zeit dazu, dass die Wartung der Tests mehr und mehr Zeit in Anspruch nimmt. Die Entwickler sind frustriert und TDD wird vom Management als „zu wartungsintensiv“ abgewiesen. Ziel dieses Vortrags soll es sein, dort einzusteigen wo Tutorials und Einsteigerseminare und -bücher aufhören. Wir wollen dem erfahrenen Entwickler Tools und Vorgehensweisen an die Hand zu geben, um diesem „Wartungsalbtraum“ zu entgehen. Das echte Leben ist komplexer als das Taschenrechnerbeispiel!TRANSCRIPT
1
Real Life TDD
Techniken und Tools für wartbaren Testcode
Real Life TDD, 15. Januar 2014
2
Agenda
• Einführung– (sehr) kurze Einführung in TDD– Test Doubles– Vorstellung der Tools
• Keeping it clean– Best Practices– 10 Dinge um dein Design zu zerstören– Auf die Tests hören
Real Life TDD, 15. Januar 2014
3
Einführung
Real Life TDD, 15. Januar 2014
4
Test-Driven-Development a
Real Life TDD, 15. Januar 2014
5
Test-Driven-Development a
Real Life TDD, 15. Januar 2014
• Warum?– Korrektheit– Sauberes Design– Seelenfrieden
• Wann?– Test first!– TDD ist Teil des Entwicklungsprozesses und sollte
immer mit eingeplant werden
6
Test-Driven-Development a
Real Life TDD, 15. Januar 2014
• Wie?
7
Test-Driven-Development a
Real Life TDD, 15. Januar 2014
1. Test schlägt fehl– Vor der Funktionalität kommt der Test• assertEquals(„foobar“, cut.toString())
2. Test erfolgreich– Die Annahmen im Test werden bedient• return „foobar“
3. Refaktorierung– Vereinfachen, Konsolidieren, Verschieben etc.
8
Test Doubles
• „generic term for any kind of pretend object used in place of a real object for testing purposes“ – Martin Fowler [1]– Dummy– Fake– Stubs– Mocks
• Was wollen wir testen?– State oder Behaviour
Real Life TDD, 15. Januar 2014
9
Tools
Real Life TDD, 15. Januar 2014
10
Tools
1. JUnit2. Mockito3. Hamcrest4. Powermock
Real Life TDD, 15. Januar 2014
11
JUnit
• Einführung von Rules seit JUnit 4.7 [2]• Vordefinierte Regeln– TemporaryFolder– Timeout– ExpectedException– ErrorCollector– TestName
Real Life TDD, 15. Januar 2014
12
JUnit
• Eigene JUnit Regeln erstellen– Interface TestRule implementieren– Vorhandene Templateklassen verwenden• ExternalResource• TestWatcher• Verifier
Real Life TDD, 15. Januar 2014
13
• Mocking framework• Warum Mockito?– Einfaches Erzeugen von Mock Objekten– Annotationen erleichtern das Setup– Einfache API, compile-safe– Unterstützt viele Anwendungsfälle• Mocks, Stubs, Partial-Mocks, Spies
– Hervorragende Dokumentation
Real Life TDD, 15. Januar 2014
Mockito
14
Hamcrest
• Matcher Bibliothek [3]• Komplexe assertXY Ausdrücke werden häufig
unleserlich• Seit JUnit 4.4: – assertThat(T actual,
org.hamcrest.Matcher<T> matcher)
Real Life TDD, 15. Januar 2014
15
Keeping It Clean
Real Life TDD, 15. Januar 2014
16
Keeping It Clean public void testCreatePurchaseOrder() throws Exception { Map<String, Object> ctx = FastMap.newInstance(); ctx.put( "partyId", "Company" ); ctx.put( "orderTypeId", "PURCHASE_ORDER" ); ctx.put( "currencyUom", "USD" ); ctx.put( "productStoreId", "9000" );
GenericValue orderItem = delegator.makeValue( "OrderItem", UtilMisc.toMap( "orderItemSeqId", "00001", "orderItemTypeId", "PRODUCT_ORDER_ITEM", "prodCatalogId", "DemoCatalog", "productId", "GZ-1000", "quantity", new BigDecimal( "2" ), "isPromo", "N" ) ); orderItem.set( "unitPrice", new BigDecimal( "1399.5" ) ); orderItem.set( "unitListPrice", BigDecimal.ZERO ); orderItem.set( "isModifiedPrice", "N" ); orderItem.set( "statusId", "ITEM_CREATED" ); List<GenericValue> orderItems = FastList.newInstance(); orderItems.add( orderItem ); ctx.put( "orderItems", orderItems );
GenericValue orderContactMech = delegator.makeValue( "OrderContactMech", UtilMisc.toMap( "contactMechPurposeTypeId", "SHIPPING_LOCATION", "contactMechId", "9000" ) ); List<GenericValue> orderContactMechs = FastList.newInstance(); orderContactMechs.add( orderContactMech ); ctx.put( "orderContactMechs", orderContactMechs );
GenericValue orderItemContactMech = delegator.makeValue( "OrderItemContactMech", UtilMisc.toMap( "contactMechPurposeTypeId", "SHIPPING_LOCATION", "contactMechId", "9000", "orderItemSeqId", "00001" ) ); List<GenericValue> orderItemContactMechs = FastList.newInstance(); orderItemContactMechs.add( orderItemContactMech ); ctx.put( "orderItemContactMechs", orderItemContactMechs );
Real Life TDD, 15. Januar 2014
17
Keeping It Clean GenericValue orderItemShipGroup = delegator.makeValue( "OrderItemShipGroup", UtilMisc.toMap( "carrierPartyId", "UPS", "contactMechId", "9000", "isGift", "N", "maySplit", "N", "shipGroupSeqId", "00001", "shipmentMethodTypeId", "NEXT_DAY" ) ); orderItemShipGroup.set( "carrierRoleTypeId", "CARRIER" ); List<GenericValue> orderItemShipGroupInfo = FastList.newInstance(); orderItemShipGroupInfo.add( orderItemShipGroup ); ctx.put( "orderItemShipGroupInfo", orderItemShipGroupInfo );
List<GenericValue> orderTerms = FastList.newInstance(); ctx.put( "orderTerms", orderTerms );
List<GenericValue> orderAdjustments = FastList.newInstance(); ctx.put( "orderAdjustments", orderAdjustments );
ctx.put( "billToCustomerPartyId", "Company" ); ctx.put( "billFromVendorPartyId", "DemoSupplier" ); ctx.put( "shipFromVendorPartyId", "Company" ); ctx.put( "supplierAgentPartyId", "DemoSupplier" ); ctx.put( "userLogin", userLogin );
Map<String, Object> resp = dispatcher.runSync( "storeOrder", ctx ); orderId = (String) resp.get( "orderId" ); statusId = (String) resp.get( "statusId" ); assertNotNull( orderId ); assertNotNull( statusId ); }
Real Life TDD, 15. Januar 2014
WTF?
18
Keeping It Clean public void testCreatePurchaseOrder() throws Exception { Map<String, Object> ctx = FastMap.newInstance(); ctx.put( "partyId", "Company" ); ctx.put( "orderTypeId", "PURCHASE_ORDER" ); ctx.put( "currencyUom", "USD" ); ctx.put( "productStoreId", "9000" );
GenericValue orderItem = delegator.makeValue( "OrderItem", UtilMisc.toMap( "orderItemSeqId", "00001", "orderItemTypeId", "PRODUCT_ORDER_ITEM", "prodCatalogId", "DemoCatalog", "productId", "GZ-1000", "quantity", new BigDecimal( "2" ), "isPromo", "N" ) ); orderItem.set( "unitPrice", new BigDecimal( "1399.5" ) ); orderItem.set( "unitListPrice", BigDecimal.ZERO ); orderItem.set( "isModifiedPrice", "N" ); orderItem.set( "statusId", "ITEM_CREATED" ); List<GenericValue> orderItems = FastList.newInstance(); orderItems.add( orderItem ); ctx.put( "orderItems", orderItems );
GenericValue orderContactMech = delegator.makeValue( "OrderContactMech", UtilMisc.toMap( "contactMechPurposeTypeId", "SHIPPING_LOCATION", "contactMechId", "9000" ) ); List<GenericValue> orderContactMechs = FastList.newInstance(); orderContactMechs.add( orderContactMech ); ctx.put( "orderContactMechs", orderContactMechs );
GenericValue orderItemContactMech = delegator.makeValue( "OrderItemContactMech", UtilMisc.toMap( "contactMechPurposeTypeId", "SHIPPING_LOCATION", "contactMechId", "9000", "orderItemSeqId", "00001" ) ); List<GenericValue> orderItemContactMechs = FastList.newInstance(); orderItemContactMechs.add( orderItemContactMech ); ctx.put( "orderItemContactMechs", orderItemContactMechs );
GenericValue orderItemShipGroup = delegator.makeValue( "OrderItemShipGroup", UtilMisc.toMap( "carrierPartyId", "UPS", "contactMechId", "9000", "isGift", "N", "maySplit", "N", "shipGroupSeqId", "00001", "shipmentMethodTypeId", "NEXT_DAY" ) ); orderItemShipGroup.set( "carrierRoleTypeId", "CARRIER" ); List<GenericValue> orderItemShipGroupInfo = FastList.newInstance(); orderItemShipGroupInfo.add( orderItemShipGroup ); ctx.put( "orderItemShipGroupInfo", orderItemShipGroupInfo );
List<GenericValue> orderTerms = FastList.newInstance(); ctx.put( "orderTerms", orderTerms );
List<GenericValue> orderAdjustments = FastList.newInstance(); ctx.put( "orderAdjustments", orderAdjustments );
ctx.put( "billToCustomerPartyId", "Company" ); ctx.put( "billFromVendorPartyId", "DemoSupplier" ); ctx.put( "shipFromVendorPartyId", "Company" ); ctx.put( "supplierAgentPartyId", "DemoSupplier" ); ctx.put( "userLogin", userLogin );
Map<String, Object> resp = dispatcher.runSync( "storeOrder", ctx ); orderId = (String) resp.get( "orderId" ); statusId = (String) resp.get( "statusId" ); assertNotNull( orderId ); assertNotNull( statusId ); }
Real Life TDD, 15. Januar 2014
19
Best Practices
• Eine Testklasse pro Class-under-Test• Testname soll ausdrücken was getestet wird• Ein assert/verify pro Test• Unabhängige Tests• Blitzschnell!
• Tipps:– Eclipse: Import von statischen Methoden– Code Coverage (z.B. CodePro)
Real Life TDD, 15. Januar 2014
20
10 Dinge um dein Design zu zerstören
1. Services da instanziieren wo sie gebraucht werden2. Law of Demeter Verletzung3. Logik im Constructor4. Global State5. Singletons6. Statische Methoden7. Implementierung vererben8. Polymorphismus nachprogrammieren9. Services und Values vermischen10. Mehr als eine Sache tun (Separation of Concerns) [4]Real Life TDD, 15. Januar 2014
21
Auf die Tests hören
1. Schwer zu mockende Objekte2. Mocken von konkreten Klassen3. Mocken von Value Objekten4. Aufgeblähter Constructor5. Zu viele Erwartungen
Real Life TDD, 15. Januar 2014
22
Quellen
• Links– [1] http://martinfowler.com/articles/mocksArentStubs.html– [2] http://marcphilipp.tumblr.com/post/14610767542/junit-
rules– [3] http://stefanroock.blogspot.de/2008/03/junit-44-
assertthat.html– [4] http://misko.hevery.com/2008/07/30/top-10-things-
which-make-your-code-hard-to-test/• Code
– https://github.com/marcphilipp/junit-rules– https://github.com/smartsquare/RealLifeTDD
Real Life TDD, 15. Januar 2014
23
Literatur
Real Life TDD, 15. Januar 2014
24
Literatur
Real Life TDD, 15. Januar 2014
25
Danke!
Real Life TDD, 15. Januar 2014