Schlagwort-Archive: TDD

Interview mit Daniel Marbach mitgestalten

Am 18. März wird mir Daniel Marbach ein Video Interview zu dem BDD-Framework Machine.Specifications geben. Ein kleiner Vorgeschmack gefällig?

Wodurch hebt sich MSpec von anderen BDD-Frameworks ab?

oder

Welche Features sind gerade in der Pipeline?

oder

Welche anderen Frameworks ergänzen MSpec gut?

Das Video veröffentliche ich dann auf meinem YouTube Channel. Ihr könnt die Richtung des Gesprächs mitgestalten, indem ihr mir rechtzeitig eure Fragen in die Kommentare postet.

Werbung

Testen von Datenbankabfragen – Es geht auch einfach

In diesem Screencast zeige ich wie selbst Abfragen mit mehreren Bedingungen und Sortierungen einfach getestet werden können. Unter der Haube kommt das Entity Framework zum Einsatz. Davon kriegt man dank Dependency Injection aber nichts mit.

Die erwähnte Webcast Serie, sowie weiteren Beispielcode und das Projekt auf Github gibt es hier. Feedback oder Fragen nehme ich gerne entgegen. Wenn ihr wollt, dass ich die Serie fortsetze, sprecht mich über einen Kanal eurer Wahl an.

TDD as if you meant it

Als Ralf Westphal im Juli in der .NET UserGroup Rhein Neckar eine Session zu TDD as if you meant it hielt, wollten meine Karlsruhe Kollegen und ich uns das nicht entgehen lassen. Neue Denkansätze zu TDD als state-of-the-art Praktik sind für mich persönlich aktuell ein Dauerthema, da ich immer noch Potential nach oben sehe.

Als Vorbereitung las ich die zugehörige Artikelserie aus der dotnet pro. Ich will jetzt nicht das dort Geschriebene kommentieren oder gar hier ausführen, denn als .NET Developer haben hoffentlich die meisten Zugriff auf den Inhalt. Stattdessen hier kurz die Punkte, die für mich nochmal klarer wurden:

 

  • Für TDDaiymi wird eine feststehende API benötigt, d.h. im Vorhinein muss der Contract stehen
  • TDDaiymi hilft dann dabei die Aspekte innerhalb der Klasse zu identifizieren, sprich die innere Struktur wird entwickelt
  • TDDaiymi ist (zeit-)aufwendiger als “klassisches” TDD und bietet sich v.a. bei komplexen, ggf. nicht gänzlich durchdrungenen Problemen an
  • Als Praktik ist es sicherlich für Coding Dojos interessant, da man für saubere Aspekttrennung und implizite Annahmen (die nicht implizit sein sollten) sensibilisiert wird

 

Das Video habe ich nachbearbeitet, um die Ton- und Bildqualität zu steigern, allerdings ist es zugegebenermaßen nicht optimal. Danke an Ralf nochmal für den Vortrag und die Freigabe des Videos. Den Code gibt es ähnlich in der dotnet pro. Wenn jemand schon Erfahrungen mit der Praktik gesammelt hat, dann würde ich mich über Feedback freuen.

TDD 2.0

Ralf Westphal schreibt in seinem Artikel Ab ins Grüne in der dotnet pro Ausgabe 04/2013 über die Probleme mit TDD. Auf den Punkt gebracht, schildert er seinen Eindruck, dass die Fokussierung auf T(est)D(riven) einen Lösungsansatz perpetuiert hat, der D(evelopment) zu wenig bis gar nicht integriert. Will heißen: Durch die Konzentration auf das Schreiben und Lösen der Tests, wird die Analyse- und Lösungsphase fast gänzlich außer Acht gelassen. Deshalb stünden, so seine Meinung, in Coding Dojos am Ende auch nur selten fertige Ergebnisse.

Aus meiner Sicht trifft Ralf damit voll ins Schwarze. Persönlich störte ich mich seit längerer Zeit an diesem Problem. Ich denke sogar, dass das Development der weit schwierigere Teil ist. Test zu schreiben und den Code dafür entsprechend zu präparieren ist eine Art Training wie das Lösen von Gleichungen 2ter Ordnung in der Schule. Je nach Kenntnisstand des Entwicklers werden noch Vereinfachungen oder schnellere Lösungsansätze wie die binomische Formel erkannt. Ein solides Level stellt sich allerdings nach relativ kurzer Zeit ein. Neue Probleme wie Gleichungen vierter Ordnung bedürfen aber der analytische Fähigkeiten.

Deshalb haben wir bei unseren Coding Dojos die Anregungen aufgegriffen und implementiert. Interessanterweise, was ich persönlich als sehr positiv empfinde, gab es im ersten Dojo mit dem neuen Verfahren keine einzige Codezeile, da eine Stunde lang das Problem eruiert und Begrifflichkeiten diskutiert wurden. Wohlgemerkt handelte es sich dabei um ein echtes Szenario aus einer unserer Problemdomäne, d.h. wir verwendeten keine der üblichen Katas. Umso schneller kamen wir dann im zweiten Training zu einer fertigen Lösung. Mit ein wenig Überziehung erreichten wir nach 1.15h die Ziellinie.

TDD 2.0 empfinde ich als längst überfällige Erweiterung bzw. Kritik. Wir werden in Zukunft auf diese Weise weiterarbeiten. Ein Punkt, der für mich allerdings weiterhin in Frage gestellt werden sollte: Müssen Coding Dojos an Katas durchgeführt werden? Warum nicht an einem echten, auf ein notwendiges Minimum reduzierten Praxisbeispiels aus der Problemdomäne trainieren? Oder alternativ für die Administration ein kleines Hilfsprogramm schreiben, dass die tägliche Arbeit automatisiert? Kürzlich half ich meinem ehemaligen Fussballverein beim Realisieren des DFB Reglements bzgl. Stammspieler. Gelöst habe ich es vorerst über Excel, sodass immer noch Handarbeit seitens des Vorstands notwendig ist. Doch dieses Thema kommt auf unsere Coding Dojo Agenda. Es gibt viele gemeinnützige Organisationen oder Sportvereine, die sich über Hilfe der IT zu kleinen Wehwehchen freuen würden!

Workshop Test Driven Development

Nach dem DDD Workshop nahm ich ebenfalls bei Dennis an der TDD Session teil. Persönlich habe ich hier einige Erfahrungen. Angefangen bei dem praktischen Einsatz in unserem ERP System über betriebsinterne Coding Dojos bis hin zu Katas in der Karlsruhe User Group.

In dem Kontext brachte mir der Workshop leider nicht weiter. Meine eigentlichen Erwartungen waren dahingehend gerichtet:

  • Besonders gute TDD/BDD Frameworks kennen lernen
  • Best Practices für konkrete Spezialfälle (Parallelisierung, Zeitstempel, Datenbankzugriff) exerzieren

Allerdings konnte ich den Einblick bekommen, dass es (leider!) immer noch Entwickler gibt, die vom Arbeitgeber kein ReSharper zur Verfügung gestellt bekommen oder mit Windows XP arbeiten müssen. Meiner Ansicht nach ein absolutes No-Go! Hier lässt sich nur hoffen, dass ein Umdenken in den verantwortlichen Stellen noch stattfinden wird.

Zusammenarbeit mit der Fachabteilung – BDD und TDD helfen

Test Driven Development (TDD) steht dafür, dass Tests vor der eigentlichen Implementierung zu schreiben sind. Es zwingt den Entwickler sich vorher genaue Gedanken über die Architektur und die Implementierung zu machen, sodass “einfaches loslaufen” und ggf. damit einhergehendes “falschlaufen” vermieden wird. Saubere Codequalität wird gefördert.

Behaviour Driven Development bringt nun einen weiteren Ansatz mit ins Spiel: Code soll ergebnisorientiert getrieben sein, d.h. die fachlichen Spezifikationen stehen im Vordergrund. Entsprechende BDD Frameworks wie Machine.Specifications (Link, gibt es auch auf NuGet) unterstützen nun dabei, dieses Ziel umzusetzen.

Damit sich auch Nicht-IT-ler und weniger Test-affine Entwickler etwas darunter vorstellen können, hier ein kleines Beispiel aus der Praxis. Wir praktizieren dies bereits seit einigen Monaten in der Form mit unseren Fachabteilungen:

Aufgabenstellung: Mehrere Produktartikel sind in einer gemeinsamen Bestellmappe zusammengefasst. Diese Mappe besitzt die Kopfdaten ‘Lieferzeit’ (LZ), ‘Bearbeitungszeit’ (BZ), ‘Bestellintervall’ (BI) und ‘nächster Mappentermin’ (MT). Der nächste Mappentermin besagt, wann die Mappe sich beim zuständigen Benutzer melden, sodass dieser eine neue Bestellung auslöst. Das Bestellintervall gibt an in welchen Abständen die Mappe neu bearbeitet werden muss, z.B. alle 3 Wochen. Die restlichen Begriffe sprechen für sich. Nun wird von der Fachabteilung gefordert, dass sogenannte ‘fiktiven Wareneingänge’ (fWE) visuell dargestellt werden (siehe Screenshot). Dabei handelt es sich um Eingänge, die rein rechnerisch in der Zukunft eingehen würden, wenn die Bestellungen so getätigt würden. Also bedarf es einer Möglichkeit, um diese Termin zu berechnen.

 

image

 

Die Tests visualisiert für die Übergabe an die Fachabteilung:

Die Tests visualisiert für die Übergabe an die Fachabteilung

Es handelt sich hier um 7 Tests. Der obere Abschnitt besagt:

Wenn von einer Bestellmappe die die Lieferzeit 5 Tage, die Bearbeitungszeit 1 Tag und das Bestellintervall 14 Tage sind und hierfür die nächsten 3 fiktiven Wareneingänge aus heutiger Sicht berechnet werden sollen:

  • Dann sollte der erste fiktive Wareneingang in 6 Tagen sein
  • Dann sollte der zweite fiktive Wareneingang in 21 Tagen sein
  • Dann sollte der dritte fiktive Wareneingang in 36 Tagen sein
  • Dann sollt es insgesamt 3 Datumsangaben errechnen

Der zweite Abschnitt ist analog zu lesen, wenn die Abkürzungen ausgeschrieben werden. Das Wesentliche daran ist, dass diese Spezifikationen von der Fachabteilungen kommen und diese so sich im Code 1 zu 1 widerspiegeln. Damit können die Entwickler die Brücke schlagen und sich mit den Anwendern in deren Domänensprache unterhalten. Die Spezifikationen garantieren, dass der Code sich wie gewünscht verhält. Die Tests lassen sich automatisiert wiederholen und werden vor jeder neuen Versionsauslieferung durchlaufen.

 

 

Zur Gänze folgen noch der Code für die Tests und die eigentliche Implementierung:

Der Code für die Tests:

   1: [Subject("Fiktive Wareneingänge")]

   2: public class Wenn_von_einer_Mappe_die_LZ_5T_die_BZ_1T_und

   3: _das_BI_14T_sind_und_3_fWE_ab_heute_berechnet_werden_sollen

   4: {

   5:     Establish context = () =>

   6:     {

   7:         Clock = new DummyClock();

   8:         Folder = new OrderProposalFolder { DeliveryPeriodInDays = 5, 

   9:                 HandlingTimeInDays = 1, OrderIntervalInDays = 14 };

  10:         Sut = new FictitiousIntakes(Clock);

  11:     };

  12:  

  13:     Because of = () =>

  14:     {

  15:         Actual = Sut.FromNow(Folder, 3);

  16:     };

  17:  

  18:     It dann_sollten_exakt_3_Datumsangaben_errechnet_werden = () => Actual.Count.ShouldEqual(3);

  19:     It dann_sollte_der_erste_fWE_in_6T_sein = () => Actual.Min().ShouldEqual(Clock.Today.AddDays(6));

  20:     It dann_sollte_der_zweite_fWE_in_21T_sein = () => Actual.ElementAt(1).ShouldEqual(Clock.Today.AddDays(21));

  21:     It dann_sollte_der_dritte_fWE_in_36T_sein = () => Actual.Max().ShouldEqual(Clock.Today.AddDays(36));

  22:  

  23:     static FictitiousIntakes Sut;

  24:     static OrderProposalFolder Folder;

  25:     static IList<DateTime> Actual;

  26:     static IClock Clock;

  27: }

  28:  

  29: [Subject("Fiktive Wareneingänge")]

  30: public class Wenn_von_einer_Mappe_die_LZ_5T_die_BZ_1T_und_das_BI_14T_sind_

  31: und_der_nächste_MT_übermorgen_ist_und_2_fWE_ab_dem_nächsten_MT_berechnet_werden_sollen

  32: {

  33:     Establish context = () =>

  34:     {

  35:         Clock = new DummyClock();

  36:         Folder = new OrderProposalFolder {NextOrderDate = Clock.Now.AddDays(2) ,DeliveryPeriodInDays = 5,

  37:                                          HandlingTimeInDays = 1, OrderIntervalInDays = 14 };

  38:         Sut = new FictitiousIntakes(Clock);

  39:     };

  40:  

  41:     Because of = () =>

  42:     {

  43:         Actual = Sut.FromNextFolderDate(Folder, 2);

  44:     };

  45:  

  46:     It dann_sollten_exakt_2_Datumsangaben_errechnet_werden = () => Actual.Count.ShouldEqual(2);

  47:     It dann_sollte_der_erste_fWE_in_8T_sein = () => Actual.Min().ShouldEqual(Clock.Today.AddDays(8));

  48:     It dann_sollte_der_dritte_fWE_in_23T_sein = () => Actual.Max().ShouldEqual(Clock.Today.AddDays(23));

  49:  

  50:     static FictitiousIntakes Sut;

  51:     static OrderProposalFolder Folder;

  52:     static IList<DateTime> Actual;

  53:     static IClock Clock;

  54: }

 

Der produktive Code, der im Programm zur Berechnung verwendet wird:

   1: public class FictitiousIntakes : ICalculateFictitiousIntakes

   2: {

   3:     readonly IClock _clock;

   4:  

   5:     public FictitiousIntakes(IClock clock)

   6:     {

   7:         _clock = clock;

   8:     }

   9:  

  10:     public IList<DateTime> FromNow(OrderProposalFolder folder, int toOrdinal)

  11:     {

  12:         return FromThis(_clock.Today, folder, toOrdinal);

  13:     }

  14:  

  15:     public IList<DateTime> FromNextFolderDate(OrderProposalFolder folder, int toOrdinal)

  16:     {

  17:         return FromThis(folder.NextOrderDate, folder, toOrdinal);

  18:  

  19:     }

  20:  

  21:     private IList<DateTime> FromThis(DateTime startDate, OrderProposalFolder folder, int toOrdinal)

  22:     {

  23:         if (toOrdinal < 1)

  24:             throw new ArgumentOutOfRangeException("toOrdinal");

  25:  

  26:         var firstIntake = startDate.AddDays(folder.DeliveryPeriodInDays + folder.HandlingTimeInDays);

  27:  

  28:         var result = new List<DateTime>();

  29:         for (int currentOrdinal = 0; currentOrdinal <= toOrdinal - 1; currentOrdinal++)

  30:             result.Add(firstIntake.AddDays(currentOrdinal * 

  31:                     (folder.OrderIntervalInDays + folder.HandlingTimeInDays)));

  32:  

  33:         return result;

  34:     }

  35: }

%d Bloggern gefällt das: