Real Life TDD

540 views

Published on

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!

Published in: Technology, Business
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
540
On SlideShare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
7
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide
  • Korrektheit = Fehlerfrei
    Sauberes Design durch strikte Trennung von SUT und Collaborator
    Seelenfrieden durch hohe Testabdeckung, erleichtert Refactorings

    Test first! Sonst gehen grundlegende Vorteile verloren
    TDD ist grundlegendes Tool und muss mit eingeplant werden
  • Dummy
    Objekte die durchgereicht, aber niemals verwendet werden
    Fake
    Funktionierende Implementierung die aber nicht für den produktiven Einsatz bestimmt ist (z.B. in-memory DBs)
    Stub
    Liefert vorher definierte Antworten (z.B. FTP, E-Mail) und zeichnet ggfs Verwendung auf
    Mock
    Objekte die Erwartungen definieren und überprüft werden können (behaviour!)

    Test state or behaviour?!
  • TemporaryFolder
    Verwalten von temporären Verzeichnissen/Dateien
    Timeout
    Globalen Timeout definieren
    ExpectedException
    Erwartete Exception, bietet zusätzliche Funktionen wie das Überprüfen der Message
    ErrorCollector
    Führt alle Asserts aus und sammelt Fehler
    TestName
    Gibt den Namen des aktuellen Tests wider
  • ExternalResource
    Stellt Ressourcen in einer before() Methode bereit, räumt in after() wieder auf
    TestWatcher
    Benachrichtigung über Testausführung (reagieren auf fail, success)
    Verify
    Überprüfungen nach dem Test
    Code zeigen!
  • ExternalResource
    Stellt Ressourcen in einer before() Methode bereit, räumt in after() wieder auf
    TestWatcher
    Benachrichtigung über Testausführung (reagieren auf fail, success)
    Verify
    Überprüfungen nach dem Test
    Code zeigen!
  • Einfaches Erzeugen von Mock Objekten
    Es müssen keine expectations formuliert werden
    Annotationen
    Mock, InjectMocks, Captor
    Einfache API
    Statische Methoden lassen sich importieren -> sauberer testcode
    Echte API in Mocks -> compile-safe

    Mocking and verification
    Stubbing
    Argument matching
    Verifying exact number of invocations
    Stubbing void methods
  • Vorheriger Titel: 10 Dinge um den Code untestbar zu machen
    Powermock schafft Abhilfe

    Dependencies und Collaborator werden versteckt, Abhängigkeit zu Implementierung
    Mocken von ganzen Objektgraphen
    Wissen implizit, schwer nachvollziehbar
    Verletzt die Unabhängigkeit von Tests
    Gleiche wie global State
    Ähnlich wie 1
    Wir müssen Dinge mocken die wir gar nicht verwenden/testen wollen
    Häufig einfacher und lesbarer Polymorphismus zu verwenden
    Aufbau von solchen Hybriden meist problematisch, Values sollten immutable sein
    Sollte klar sein
  • Warum ist das Objekt schwer zu mocken
    „Clients should not be forced to depend upon interfaces that they do not use“
    Separate Klasse?
    Was will man mocken? Zu kompliziert? Builder!
    Zu viele Abhängigkeiten -> Konsolidieren!
    Schwer zu unterscheiden was setup ist und was getestet wird
  • Real Life TDD

    1. 1. Real Life TDD Techniken und Tools für wartbaren Testcode Real Life TDD, 15. Januar 2014 1
    2. 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 2
    3. 3. Einführung Real Life TDD, 15. Januar 2014 3
    4. 4. Test-Driven-Development a Real Life TDD, 15. Januar 2014 4
    5. 5. Test-Driven-Development a Real Life TDD, 15. Januar 2014 5 • Warum? – Korrektheit – Sauberes Design – Ermöglicht Refactorings – Seelenfrieden  • Wann? – Test first! – TDD ist Teil des Entwicklungsprozesses und sollte immer mit eingeplant werden
    6. 6. Test-Driven-Development a Real Life TDD, 15. Januar 2014 6 • Wie?
    7. 7. Test-Driven-Development a Real Life TDD, 15. Januar 2014 7 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. Refactoring – Vereinfachen, Konsolidieren, Verschieben etc.
    8. 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 8
    9. 9. Tools Real Life TDD, 15. Januar 2014 9
    10. 10. Tools 1. JUnit 2. Mockito 3. Hamcrest 4. AssertJ 5. Powermock Real Life TDD, 15. Januar 2014 10
    11. 11. JUnit • Einführung von Rules seit JUnit 4.7 [2] • Vordefinierte Regeln – TemporaryFolder – Timeout – ExpectedException – ErrorCollector – TestName Real Life TDD, 15. Januar 2014 11
    12. 12. JUnit • Eigene JUnit Regeln erstellen – Interface TestRule implementieren – Vorhandene Templateklassen verwenden • ExternalResource • TestWatcher • Verifier Real Life TDD, 15. Januar 2014 12
    13. 13. JUnit • Parametrisierte Tests – Kompakte, lesbare Testmethoden – DRY Prinzip in den Tests • Aber – Umständlich in plain JUnit – JUnitParams („ Parameterised tests that don't suck“) – Oder gleich Spock ;-) Real Life TDD, 15. Januar 2014 13
    14. 14. • 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 14 Mockito
    15. 15. 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
    16. 16. AssertJ • Fluent Matcher Bibliothek [4] assertThat(fellowshipOfTheRing) .hasSize(9) .contains(frodo, sam) .doesNotContain(sauron); Real Life TDD, 15. Januar 2014 16
    17. 17. Keeping It Clean Real Life TDD, 15. Januar 2014 17
    18. 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 ); Real Life TDD, 15. Januar 2014 18
    19. 19. 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 19 WTF?
    20. 20. 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 20
    21. 21. 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: – Import von statischen Methoden – Code Coverage (z.B. CodePro) Real Life TDD, 15. Januar 2014 21
    22. 22. 10 Dinge um dein Design zu zerstören 1. Services da instanziieren wo sie gebraucht werden 2. Law of Demeter Verletzung 3. Logik im Constructor 4. Global State 5. Singletons 6. Statische Methoden 7. Implementierung vererben 8. Polymorphismus nachprogrammieren 9. Services und Values vermischen 10. Mehr als eine Sache tun (Separation of Concerns) [5] Real Life TDD, 15. Januar 2014 22
    23. 23. Auf die Tests hören 1. Schwer zu mockende Objekte 2. Mocken von konkreten Klassen 3. „Wie mocke ich eine private Methode?“ 4. Mocken von Value Objekten 5. Aufgeblähter Constructor 6. Zu viele Erwartungen Real Life TDD, 15. Januar 2014 23
    24. 24. 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://joel-costigliola.github.io/assertj/ – [5] 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 24
    25. 25. Literatur Real Life TDD, 15. Januar 2014 25
    26. 26. Literatur Real Life TDD, 15. Januar 2014 26
    27. 27. Danke! Real Life TDD, 15. Januar 2014 27

    ×