Schlagwort-Archive: UnitTest

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

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: }

Clean Code Sample

In einem Gespräch mit unseren Webentwicklern habe ich den Kollegen einen Vorschlag für die Implementierung des Zugangs zu unserem Kundenportals unterbreitet: Unsere PHP Entwickler sind inzwischen ebenfalls auf den Ansatz des Testens umgestiegen, sodass sie ihre PHP und JavaScript Architektur anpassen müssen. Leider fehlt es an Frameworks, die einem ein Teil der Arbeit abnehmen könnten, wie z.B. Mocking Frameworks a la FakeItEasy. Falls jemand Frameworks kennt, dann melde er sich bitte bei mir.

Anforderungen (vereinfacht): An unserem Kundenportal ‘MyHeco’ soll man sich einloggen, ausloggen und das Password zurücksetzen können. Die Webanwendung konsumiert dabei .NET basierende Webservices!

Dies soll nun möglichst clean unter Einhaltung gängiger Prinzipien (wie z.B. dem Single Responsibility Principle) umgesetzt werden. Wie bereits erwähnt, muss die Architektur so gewählt werden, dass der Code einfach zu testen ist. Das ist v.a. auch deswegen nicht ganz trivial, weil Aufrufe nach ‘Außen’ zu Webservices gehen.

Hier also mein Vorschlag (natürlich in C# formuliert):

   1: interface IAuthenticateMyHecoUser

   2: {

   3:     MyHecoUser Login(string email, string password);

   4: }

   5:  

   6: interface ILogoutMyHecoUser

   7: {

   8:     void Logout(string session);

   9: }

  10:  

  11: interface IResetMyHecoPassword

  12: {

  13:     void ResetPassword(string email);

  14: }

Schreibe 3 Interfaces für die eigentlichen Aktionen, z.B. Login. Natürlich könnte dafür auch nur ein Interface geschrieben werden werden, allerdings empfinde ich es so als sauberer und die Interfaces lassen sich sprechend benennen (ohne Weasel Words, also Füllwörter; tollen Artikel dazu gibt es von Johannes).

Danach benötigen wir ein Interface für die Webservice-Kommunikation, welches ich an dieser Stelle leer lasse:

   1: public interface IERPCommunication

   2: {

   3:      

   4: }

Was ich ebenfalls zu den Kontrakten zähle (vergleicht dazu diesen Artikel), ist das DTO MyHecoUser, welches später die gefüllten Daten zum Austausch innerhalb von PHP enthalten soll.

   1: public class MyHecoUser

   2: {

   3:     public readonly int Kundennummer;

   4:     public readonly string Session;

   5:     //und diverse weitere

   6: }

Zu guter Letzt benötigen wir noch die Implementierung, die so aussehen könnte. Je nach Umfang und Größe des Codes, wäre es auch durchaus denkbar, die Implementierung auf verschiedene Klassen aufzusplitten. Aber in diesem Fall implementiert eine Klasse die 3 Interfaces aus dem Code Snippet ganz oben.

   1: public class MyHeco : IAuthenticateMyHecoUser, ILogoutMyHecoUser, IResetMyHecoPassword

   2: {

   3:     readonly IERPCommunication _webservice;

   4:  

   5:     public MyHeco(IERPCommunication webservice)

   6:     {

   7:         _webservice = webservice;

   8:     }

   9:  

  10:     public MyHecoUser Login(string email, string password)

  11:     {

  12:         throw new System.NotImplementedException();

  13:     }

  14:  

  15:     public void Logout(string session)

  16:     {

  17:         throw new System.NotImplementedException();

  18:     }

  19:  

  20:     public void ResetPassword(string email)

  21:     {

  22:         throw new System.NotImplementedException();

  23:     }

  24: }

 

Durch die Abstraktion der Webservice-Kommunikation hinter einem Interface und dem Injizieren im Konstruktor, können die Kollegen nun für den Test einen selbstgeschriebenen Fake hineinreichen. Ich validiere hier zugegebenermaßen den Parameter nicht, da ich in unserer Architektur auf IoC mit Castle.Windsor setze. Die Benennung spricht mich persönlich auch an, da sie sehr aussagekräftig ist:

MyHeco.Login oder Myheco.Logout sind eindeutig. Die Kohäsion scheint mir ebenfalls recht hoch, da die 3 möglichen von der Fachabteilung definierten MyHeco Aktionen in einer Klasse gekapselt sind. Trotzdem wäre eine spätere Auftrennung sehr einfach, da in der konsumierenden Klasse nur das konkrete Interface reingegeben wird. Beispiel: In einer Bestellung muss sich der Kunde erst einloggen. Also bekommt die Logik für eine Bestellung im Konstruktor ein IAuthenticateMyHecoUser reingereicht. Ist in Zukunft die Implementierung nicht mehr in der Klasse MyHeco, ändert sich die Logik der Bestellung in keiner Weise.

Castle Windsor – Handy Ruse Part III

In this blog entry I published code for an IoC Initializer. The following container Integration Test tries to create an instance of each registered service that is not of type IAmNotTestable. Definitely an Integration Tests that every application needs!

Integration Test of the container using Machine.Specifications as UnitTesting Framework:

   1: using System;

   2: using System.Diagnostics;

   3: using System.Linq;

   4: using Castle.MicroKernel;

   5: using Castle.Windsor;

   6: using comWORK.Contracts;

   7: using comWORK.Infrastructure.IoC;

   8: using Machine.Specifications;

   9: using Machine.Specifications.Utility;

  10:  

  11: namespace UAR.IntegrationTests

  12: {

  13:     public abstract class ContainerSpecs

  14:     {

  15:         protected static IWindsorContainer CreateContainer()

  16:         {

  17:             return new IoCInitializer()

  18:                     .RegisterComponents()

  19:                     .Container;

  20:         }

  21:     }

  22:  

  23:     [Subject("Container Specs")]

  24:     public class When_the_application_starts_up : ContainerSpecs

  25:     {

  26:         static IHandler[] _handlers;

  27:         static IWindsorContainer _container;

  28:  

  29:         Establish context = () => { _container = CreateContainer(); };

  30:  

  31:         Because of = () => { _handlers = _container.Kernel.GetAssignableHandlers(typeof(object)); };

  32:  

  33:         Cleanup after = () => _container.Dispose();

  34:  

  35:         It should_be_able_to_create_an_instance_of_each_registered_service =

  36:             () => _handlers

  37:                       .Where(

  38:                           handler =>

  39:                           handler.ComponentModel.Implementation.GetInterfaces().Contains(typeof(IAmNotTestable)) == false)

  40:                       .Each(handler => handler.ComponentModel.Services

  41:                                            .Each(service =>

  42:                                            {

  43:                                                Debug.WriteLine(String.Format("{0}: {1}/{2}",

  44:                                                                              handler.ComponentModel.Name,

  45:                                                                              service.Name,

  46:                                                                              handler.ComponentModel.Implementation.Name));

  47:  

  48:                                                if (service.ContainsGenericParameters)

  49:                                                {

  50:                                                    service.GetGenericArguments()

  51:                                                        .Each(argument => argument.GetGenericParameterConstraints()

  52:                                                                              .Each(

  53:                                                                                  constraint =>

  54:                                                                                  _container.Resolve(service.MakeGenericType(constraint))));

  55:                                                }

  56:                                                else

  57:                                                {

  58:                                                    _container.Resolve(service);

  59:                                                }

  60:                                            }));

  61:     }

  62: }

Implementation of IAmNotTestable

   1: public interface IAmNotTestable{}

Notice: I will publish a new version soon that addresses some open problems that i don’t want to mention here.

UnitTests & Threading – Ist grün wirklich grün?

Ich bin kürzlich auf ein Problem gestoßen: In ReSharper wurden UnitTests grün angezeigt, obwohl diese fehlschlugen. Im konkreten Fall handelt es sich um einen UnitTest im Rahmen von Logik, die parallel abläuft. Tritt in den Threads eine Exception auf, so wird dies nicht als Fehler behandelt. Der UnitTest ist weiterhin grün, wenn der Assert trotzdem korrekt ist.

Beispiel: Stellen wir uns zwei Worker Threads vor, die bei einem Controller nach neuen Druckaufträgen fragen. Der Controller ist als Singleton implementiert (oder idealerweise wird dies schon out of the box vom IoC Container gemacht). Tritt nun eine Exception in den Threads auf, der Assert ist aber trotzdem korrekt, so wird der UnitTest grün angezeigt. Hier der Code dazu:

Implementierung Controller:

   1: class PrintJobMediator : IMediatePrintjobs

   2: {

   3:     //lock object for the method

   4:     private object _lockObject = new object();

   5:  

   6:     //instance of the singleton

   7:     private static volatile PrintJobMediator instance;

   8:  

   9:     //lock object for singleton

  10:     private static object syncRoot = new Object();

  11:  

  12:     //private ctor

  13:     private PrintJobMediator() { }

  14:  

  15:     //returns the singleton instance

  16:     public static PrintJobMediator Instance

  17:     {

  18:         get

  19:         {

  20:             if (instance == null)

  21:             {

  22:                 lock (syncRoot)

  23:                 {

  24:                     if (instance == null)

  25:                         instance = new PrintJobMediator();

  26:                 }

  27:             }

  28:  

  29:             return instance;

  30:         }

  31:     }

  32:  

  33:     int _result;

  34:     public int Next()

  35:     {

  36:         lock (_lockObject)

  37:         {

  38:             System.Threading.Thread.Sleep(1000);

  39:             _result += 1;

  40:  

  41:             if (_result == 1)

  42:                 throw new ApplicationException();

  43:         }

  44:  

  45:         return _result;

  46:     }

  47: }

Unit Test:

   1: [Subject(typeof(PrintJobMediator))]

   2: public class When_2_worker_ask_for_new_jobs

   3: {

   4:     static PrintJobMediator _jobMediator;

   5:     static Thread _thread1;

   6:     static Thread _thread2;

   7:     static TimeSpan _timeTaken;

   8:  

   9:     Establish context = () =>

  10:     {

  11:         _jobMediator = PrintJobMediator.Instance;

  12:         _thread1 = new Thread(() => _jobMediator.Next());

  13:         _thread2 = new Thread(() => _jobMediator.Next());

  14:     };

  15:  

  16:     Because of = () =>

  17:     {

  18:         _timeTaken = Measure.Time(() =>

  19:         {

  20:             _thread1.Start();

  21:             _thread2.Start();

  22:             _thread1.Join();

  23:             _thread2.Join();

  24:         });

  25:     };

  26:  

  27:     It should_run_jobs_sequentially = () => _timeTaken

  28:         .ShouldBeGreaterThanOrEqualTo(TimeSpan.FromSeconds(2));

  29: }

  30:  

  31: public static class Measure

  32: {

  33:     public static TimeSpan Time(Action timedAction)

  34:     {

  35:         var watch = new System.Diagnostics.Stopwatch();

  36:         watch.Start();

  37:  

  38:         timedAction();

  39:  

  40:         watch.Stop();

  41:         return watch.Elapsed;

  42:     }

  43: }

 

In ReSharper wird der Test als erfolgreich angezeigt:

image

Wenn man sich die Details anschaut, sieht man folgendes:

image

In der Implementierung schmeiße ich im ersten Thread eine Exception (Zeile 42). Da trotzdem die Gesamtdauer der Ausführung 2 Sekunden ist, ist der Assert korrekt. Der UnitTest bricht nicht ab, weil seit .NET 2.0 eine neue Policy greift (Name fällt mir gerade nicht mehr ein), die den Abbruch des Main Thread durch Exceptions in separaten Threads verhindert.

Fake Implementation of IDbSet

   1: public class FakeDbSet<T> : IDbSet<T> where T:class 

   2:  {

   3:      readonly HashSet<T> _set;

   4:      private readonly IQueryable<T> _queryableSet; 

   5:  

   6:      public FakeDbSet()

   7:      {

   8:          _set = new HashSet<T>();

   9:          _queryableSet = _set.AsQueryable();

  10:      } 

  11:  

  12:      public IEnumerator<T> GetEnumerator()

  13:      {

  14:          return _set.GetEnumerator();

  15:      }

  16:  

  17:      IEnumerator IEnumerable.GetEnumerator()

  18:      {

  19:          return GetEnumerator();

  20:      }

  21:  

  22:      public Expression Expression

  23:      {

  24:          get { return _queryableSet.Expression; }

  25:      }

  26:  

  27:      public Type ElementType

  28:      {

  29:          get { return _queryableSet.GetType(); }

  30:      }

  31:  

  32:      public IQueryProvider Provider

  33:      {

  34:          get { return _queryableSet.Provider; }

  35:      }

  36:  

  37:      public T Find(params object[] keyValues)

  38:      {

  39:          throw new NotImplementedException();

  40:      }

  41:  

  42:      public T Add(T entity)

  43:      {

  44:          _set.Add(entity);

  45:          return entity;

  46:      }

  47:  

  48:      public T Remove(T entity)

  49:      {

  50:          _set.Remove(entity);

  51:          return entity;

  52:      }

  53:  

  54:      public T Attach(T entity)

  55:      {

  56:          _set.Add(entity);

  57:          return entity;

  58:      }

  59:  

  60:      public T Create()

  61:      {

  62:          throw new NotImplementedException();

  63:      }

  64:  

  65:      public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T

  66:      {

  67:          throw new NotImplementedException();

  68:      }

  69:  

  70:      public ObservableCollection<T> Local

  71:      {

  72:          get { throw new NotImplementedException(); }

  73:      }

  74:  }

ReSharper Settings für Unit Tests

Wer Probleme bei seinen Unit Tests hat, weil für den Unit Tests die Assemblies in ein temporäres Verzeichnis kopiert und dort ausgeführt werden, der kann dies in den ReSharper Settings über die Settings “Shadow-copy assemblies being tested” deaktivieren.

Bei mir traten Probleme in einem Unit Test für meinen IoC-Container auf. Hintergrund ist der, dass im Test alle DLLs nach IWindsorInstaller Implementierungen durchsucht und registriert werden. Da nicht alle DLLs vorhanden waren, kam es zu Fehlern.

image

Fakes, Mocks, statischer Code und fehlende Interfaces

Kürzlich habe ich meinen Kollegen aus der PHP Entwicklung gezeigt, wie man mit statischem Code aus dem Framework umgeht, zu dem es keine Interfaces gibt. Heute kam auf der User Group Karlsruhe nochmals das Thema in einem Dialog auf sodass ich das Beispiel hier publizieren will.

Dazu dient mir die Console-Klasse aus dem .NET Framework. Ich könnte jetzt auf die häufig verwendete WriteLine-Methode eingehen, allerdings will ich die Aufgabe ein wenig erschweren, indem ich die ReadKey-Methode mocken bzw. faken will. Damit haben wir auch gleich das Thema User Interaktion abgedeckt.

Zunächst noch eine Klarstellung: Von Fakes spricht man, wenn man Dummy-Implementierungen selbst schreibt. Just in diesem Moment habe ich Dr. House geschaut und da kam der Ausspruch “das war bloß gefakt” vor. Eine gute Eselbrücke, wie ich finde, da man für einen Fake eben aktiv werden muss. Mocking bezeichnet das automatische Erstellen von Fakes anhand von Interfaces dynamisch zur Laufzeit. Die dazu benötigten Proxies werden können mit Mocking Frameworks erzeugt werden. Beide Verfahren werde ich zeigen.

Schauen wir uns den eigentlichen Code an, den wir dann verbessern, um ihn zu testen:

   1: public void Process()

   2: {

   3:     var userChoice = Console.ReadKey();

   4:     switch (userChoice.Key)

   5:     {

   6:         case ConsoleKey.S:

   7:             //Logik A ausführen

   8:             break;

   9:  

  10:         default:

  11:             //Logik B ausführen

  12:             break;

  13:     }

  14: }

Wie können wir das testen? Ideal wäre, wenn es ein Interface für die Console Klasse gäbe. Gibt es aber nicht. Also erstellen wir unser eigenes:

   1: public interface IConsoleHelper

   2: {

   3:     ConsoleKeyInfo ReadKey();

   4: }

Die Implementierung für unser eigentliches Programm ist das nichts anderes als ein Wrapper für die ursprüngliche Logik:

   1: public class ConsoleHelper : IConsoleHelper

   2: {

   3:     public ConsoleKeyInfo ReadKey()

   4:     {

   5:         return Console.ReadKey();

   6:     }

   7: }

So weit so gut. Nun muss das ursprüngliche Programm diese Abhängigkeit reingereicht bekommen. Wie, das ist euch überlassen. Entweder ihr übergebt die Implementierung per  Constructor Injection oder per Method Injection. Ich halte es immer so, dass alle für den eigentlich Logikablauf essentielle Abhängigkeiten über den Konstruktor reingereicht und alle optionalen Abhängigkeiten (wie einen Logger) über eine Methode (z.B. SetLogger(ILogger logger)) injiziert werden. Egal wie ihr das implementiert, innerhalb des Programms gibt es dann die Instanzvariable _consoleHelper vom Typ IConsoleHelper. Der Code sieht dann wie folgt aus:

   1: public void Process()

   2: {

   3:     var userChoice = _consoleHelper.ReadKey(); //<- Hier liegt der Unterschied

   4:     switch (userChoice.Key)

   5:     {

   6:         case ConsoleKey.S:

   7:             //Logik A ausführen

   8:             break;

   9:  

  10:         default:

  11:             //Logik B ausführen

  12:             break;

  13:     }

  14: }

 

Nun zum Testen. Euer Unit Test müsste nun prüfen, o bei der Eingabe von S Logik A und bei sonstigen Eingaben Logik B ausgeführt wird.

Zuerst die Möglichkeit mit einem selbstgeschriebenen Fake, den ich zum Testen des ersten Falls, nämlich der Eingabe von S, nehme:

   1: /// <summary>

   2: /// Eigene Fake-Implementierung zum faken von Konsoleneingaben

   3: /// </summary>

   4: public class ConsoleFake : IConsoleHelper

   5: {

   6:     public ConsoleKeyInfo ReadKey()

   7:     {

   8:         return new ConsoleKeyInfo('s', ConsoleKey.S,false,false,false);

   9:     }

  10: }

 

Und hier die Implementierung eines Mocks mit dem Framework Fake It Easy:

   1: var consoleFake = A.Fake<IConsoleHelper>();

   2: A.CallTo(() => consoleFake.ReadKey())

   3:        .Returns(new ConsoleKeyInfo(

   4:             'a', ConsoleKey.A, false, false, false)

   5:         );

 

Schaut euch auch den Artikel über das Mocken von DateTime.Now an.

Data Annotations für valide DTOs

Wer viel mit DTOs arbeitet, der sollte sich in jedem Fall Data Annotations aus dem .NET Framework anschauen. Ich habe diese kürzlich wieder für ein DTO zur Speicherung der Database Configuration verwendet. Das DTO sieht wie folgt aus:

   1: public abstract class DatabaseConfig

   2: {

3: [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Bitte den

Instanznamen der Datenbank angeben.")]

   4:     public string InstanceName { get; set; }

   5:  

6: [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Bitte den

Datenbanknamen angeben.")]

   7:     public string DatabaseName { get; set; }

   8:  

   9:     public string UserName { get; set; }

  10:  

  11:     public string Password { get; set; }

  12:  

13: [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Bitte die

Einstellung für IntegratedSecurity setzen.")]

  14:     public bool IntegratedSecurity { get; set; }

  15: }

 

Dabei handelt es sich nur um ein sehr einfach Beispiel. Es ist problemlos auch möglich Ranges für Zahlen anzugeben, String-Längen zu definieren oder gar ganz eigene Annotations zu bauen, um z.B. Emailadressen zu validieren.

Was jetzt noch fehlt ist eine Implementierung eines Validator. Ich habe das Interface wie folgt definiert:

   1: public interface IValidator<in TIn>

   2: {

   3:     /// <summary>

   4:     /// Liefert die Fehlermeldungen zu allen ungültigen Werden als String zurück

   5:     /// </summary>

   6:     /// <param name="input"></param>

   7:     /// <returns></returns>

   8:     string ToString(TIn input);

   9:  

  10:     /// <summary>

  11:     /// Liefert eine Auflistung aller ungültigen Werte

  12:     /// </summary>

  13:     /// <param name="input"></param>

  14:     /// <returns></returns>

  15:     ICollection<string> GetMessages(TIn input);

  16:  

  17:     /// <summary>

  18:     /// Prüft, ob das zu validierende Objekt gültig ist

  19:     /// </summary>

  20:     /// <param name="input">Das zu validierende Objekt</param>

  21:     /// <returns>true, wenn valide</returns>

  22:     bool IsValid(TIn input);

  23: }

 

Die Implementierung speziell für die Validierung von Data Annotations (das Interface lässt sich schließlich auch noch für andere Validierungen verwenden) sieht so aus:

   1: public class DataAnnotationsValidator<TIn> : IValidator<TIn>

   2: {

   3:     public string ToString(TIn input)

   4:     {

   5:         var invalidProperties = new StringBuilder();

   6:         foreach (var errorMessage in GetErrors(input))

   7:         {

   8:             invalidProperties.AppendLine(errorMessage);

   9:         }

  10:  

  11:         return invalidProperties.ToString();

  12:     }

  13:  

  14:     public ICollection<string> GetMessages(TIn input)

  15:     {

  16:         return GetErrors(input).ToList();

  17:     }

  18:  

  19:     public bool IsValid(TIn instance)

  20:     {

  21:         var invalidProperties = from prop in typeof (TIn).GetProperties()

22: from attribute in prop.GetCustomAttributes(false).

OfType<ValidationAttribute>()

  23:                                 where !attribute.IsValid(prop.GetValue(instance, null))

  24:                                 select attribute;

  25:  

  26:         return invalidProperties.Count() == 0;

  27:     }

  28:  

  29:     private static IEnumerable<string> GetErrors(TIn instance)

  30:     {

  31:         return from prop in typeof(TIn).GetProperties()

  32:                from attribute in prop.GetCustomAttributes(false).OfType<ValidationAttribute>()

  33:                where !attribute.IsValid(prop.GetValue(instance, null))

  34:                select attribute.FormatErrorMessage(prop.Name);

  35:     }

  36: }

 

Und schon in der Bibel steht geschrieben: Entwickle testgetrieben. Deswegen noch zum Abschluss meine Specs (alles in eine Klasse mit dem Namen DataAnnotationsValidatorSpecs.cs einfügen), geschrieben mit dem Framework Machine.Specifications (gibt es auch als NuGet-Package: install-package Machine.Specifications):

   1: public class DataAnnotationsValidatorTestBase

   2: {

   3:     protected static DtoTestClass Config = new DtoTestClass();

   4:  

   5:     public class DtoTestClass

   6:     {

7: [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Bitte den

Datenbanknamen angeben.")]

   8:         public string Database { get; set; }

   9:         public string Server { get; set; }

  10:         public string User { get; set; }

  11:     }

  12: }

  13:  

  14: public class When_an_invalid_object_is_passed : DataAnnotationsValidatorTestBase

  15: {

  16:     Establish context = () =>

  17:     {

  18:         _validator = new DataAnnotationsValidator<DtoTestClass>();

  19:         Config.Database = string.Empty;

  20:  

  21:     };

  22:  

  23:     private Because of = () => _isValid = _validator.IsValid(Config);

  24:  

  25:     private It should_evaluate_it_as_invalid = () => _isValid.ShouldBeFalse();

  26:  

  27:     private static DataAnnotationsValidator<DtoTestClass> _validator;

  28:     private static bool _isValid = true;

  29: }

  30:  

  31: public class When_an_valid_object_is_passed : DataAnnotationsValidatorTestBase

  32: {

  33:     Establish context = () =>

  34:     {

  35:         _validator = new DataAnnotationsValidator<DtoTestClass>();

  36:         Config.Database = "bac";

  37:  

  38:     };

  39:  

  40:     private Because of = () => _isValid = _validator.IsValid(Config);

  41:  

  42:     private It should_evaluate_it_as_valid = () => _isValid.ShouldBeTrue();

  43:  

  44:     private static DataAnnotationsValidator<DtoTestClass> _validator;

  45:     private static bool _isValid = false;

  46: }

  47:  

  48: public class When_an_object_has_no_annotations

  49: {

  50:     internal class test

  51:     {

  52:  

  53:     }

  54:  

  55:     Establish context = () =>

  56:     {

  57:         _validator = new DataAnnotationsValidator<test>();

  58:  

  59:     };

  60:  

  61:     private Because of = () => _isValid = _validator.IsValid(new test());

  62:  

  63:     private It should_evaluate_it_as_valid = () => _isValid.ShouldBeTrue();

  64:  

  65:     private static DataAnnotationsValidator<test> _validator;

  66:     private static bool _isValid = false;

  67: }

  68:  

69: public class When_an_object_with_one_invalid_property_is_passed :

DataAnnotationsValidatorTestBase

  70: {

  71:     Establish context = () =>

  72:     {

  73:         _validator = new DataAnnotationsValidator<DtoTestClass>();

  74:         Config.Database = string.Empty;

  75:  

  76:     };

  77:  

  78:     private Because of = () => _messages = _validator.GetMessages(Config);

  79:  

  80:     private It should_evaluate_exactly_this_property_as_invalid = () =>

  81:     {

  82:         (_messages.Count == 1).ShouldBeTrue();

  83:         _messages.Aggregate(string.Empty, (current, message) => current + message)

  84:             .Contains("Datenbanknamen").ShouldBeTrue();

  85:     };

  86:  

  87:     private static DataAnnotationsValidator<DtoTestClass> _validator;

  88:     private static ICollection<string> _messages;

  89: }

LINQ und Fakes

Kürzlich stand ich vor dem Problem, dass ich einen Unit Test schreiben musste, bei dem geprüft wird, ob eine spezielle Methode aufgerufen wurde.

Beispiel: Ich habe einen Controller, der Daten von der DB in die UI lädt. Der Controller ist meine zu testende Unit. Nun will ich prüfen, ob der Controller, wenn er den Load-Befehl erhält, diesen auch korrekt an mein Repository weiterleitet. Die DB Logik habe ich dazu mit FakeItEasy gefakt. Über MustHaveHappened() kann man prüfen, ob auf dem Fake die Save-Methode aufgerufen wurde (vgl. diesen Artikel zu dem Thema).

Nun gibt es ein Problem:

1: var controlsToTranslate = from ctrl in _form.TranslatableControls select

ctrl.TranslationKey;

2: var translations = _translationRepository.LoadTranslations(_languageId,

controlsToTranslate);

Ich ermittle den zu übergebenden Parameter in meiner zu testenden Klasse per LINQ, sodass der Aufruf von Load auf meinem Repository als Parameter eine LINQ Expression ist. Damit ist der Parameter ein Objekt, welches ich in meinem Unit Test nicht nachstellen kann, sprich die Prüfung von FakeItEasy mit (vgl. Zeile 2)

   1: Fake.A

   2:     .CallTo(() => translationRepository.LoadTranslations(0, translationKeys))

   3:     .MustHaveHappened();

 

wird nie erfolgreich sein, da FakeItEasy einen Equals-Vergleich ausführt. Der zweite Parameter in meinem    Test wird immer ein anderes Objekt sein, als das, was in der zu testenden Unit (dem Controller) erzeugt wird.

Die Lösung sieht wie folgt aus:

   1: Fake.A

   2:     .CallTo(() => translationRepository.LoadTranslations(0, 

   3:      A<IEnumerable<string>>.That.Matches(x => x.SequenceEqual(translationKeys))))

   4:     .MustHaveHappened();

 

In Zeile 3 steht die Lösung für meinen Parameter. Hier sage ich, dass der Parameter vom Typ IEnumerable<string> sein muss und dass lediglich die Reihenfolge der Strings, wie sie in IEnumerable stehen, gleich sein muss. Damit habe ich den Equals-Vergleich für Parameter 2 anders implementiert.

Factory Method Design Pattern

Ich konnte kürzlich das Factory Method Pattern schön im Kontext der Web/SharePoint Entwicklung einsetzen, als eine Klasse ihre SPWeb Abhängigkeit per Constructor Injection reingereicht bekommen sollte. Hier die zwei Samples dazu:

   1: public class Sample1

   2: {

   3:     private SPWeb _web;

   4:     public Sample1(SPWeb web)

   5:     {

   6:         _web = web;

   7:     }

   8: }

   9:  

  10: public class Sample2

  11: {

  12:     private Func<SPWeb> _web;

  13:     public Sample1(Func<SPWeb> webFactory)

  14:     {

  15:         _web = webFactory;

  16:     }

  17: }

Während in Sample1 immer das gleiche Objekt für Aufrufe auf dem SPWeb-Kontext verwendet wird, ist es bei Sample2 so, dass man jedes Mal ein neues SPWeb Objekt erhält und darauf seine Abfragen ausführt.

Das hat in diesem Szenario primär den Vorteil, dass ich in Sample 2 mit Using arbeiten könnte, sodass die Methode, die das SPWeb Objekt verwenden will, sich selbstständig um das disposen kümmern kann. Das ist gerade bei Web/SharePoint Anwendungen wichtig, da Microsoft hier kaum Interfaces anbietet, die man bei UnitTests verwenden kann. Dementsprechend muss man sich eigene Interfaces und Wrapper-Implementierungen bauen, die man mocken kann. Allerdings würde das bedeuten, dass das aufrufende Objekt des Fakes sich um den Lifecycle der Fake-Abhängigkeiten kümmern muss, was sehr lästig ist. Für die konkrete Implementierung sähe das natürlich nicht anders aus…

Auf folgender Seite findet ihr noch eine sinnvolle Anwendungsmöglichkeit: http://www.dofactory.com/Patterns/PatternFactory.aspx#_self2

Wikipedia kann euch weitere Details zu dem Pattern nennen.

How to test static code: Sample with the DateTime-Object

Static Methods are everywhere, especially if the code is from Microsoft. How you work this?

Make an interface with an instance method/member, make a class that implementes the interface an within this instance you can call the static stuff.

Example: The DateTime-object has the member Now, so if you call DateTime.Now, you will get the current time. Unfortunately, can’t test time relevant actions like how long took a print job this way.

First: Declare the interface (VB.NET Code):

   1: Public Interface IClock

   2:     ReadOnly Property Now As DateTime

   3: End Interface

 

Second: Implement it (C# Code):

   1: public class SystemClock : IClock

   2: {

   3:     public DateTime Now

   4:     {

   5:         get { return DateTime.Now; }

   6:     }

   7: }

 

With Fake It Easy you can create a mock like this:

   1: A.Fake<IClock>();

 

Important: Don’t forget to register this in your IoC-Container respectively inject the SystemClock-Object into your object/method that used the DateTime.Now earlier.

Fake It Easy–don’t forget about limiting factors

Today i want to talk about a feature of Fake It Easy that you can need in many cases:

If you have a method that retrieves you job ids that can be processed by the calling method (e.g. a print service with a job id provider that observes the database for new jobs). In my case, if there is no job, the method returns –1. But there are some circumstances in which my providers also returns –1 for example when i stopped my provider (in case of multithreading) or when an expected exception occurs (when the database is down).

So i don’t want to talk about how clean this code is if it acts like the way i told you, instead i want to make clear that this are two kinds of tests you’ve got to write:

  • Case 1: There is no next job in the database
  • Case 2: Any other cases with the same return value but another meaning/behaviour inside your method

The extension methods ‘MustHaveHappened’ and ‘MustNotHaveHappened’ are your friends:

   1: It should_not_query_for_the_next_print_job_id =

   2:     () => A

   3:             .CallTo( () => _dbConversation.Query(A<NextWaitingPrintJob>

   4:             .That.Not.IsNull()))

   5:             .MustNotHaveHappened();

   6:  

   7: It should_return_the_default_job_id = () => _jobId.ShouldEqual(_fakeId);

If you’re wondering about the syntax (It should_…), so take a look here: MSpec

To quote ‘Barney’ from ‘How i met your mother’: “It’s awesome!”

The code above shows the solution for case 2: Though the second assert ensures that the return value is the default value, the first assert is the important one as it guarantees me that there was no read operation on the database

If i want to indicate case 1, i just have to rewrite line 5 into “.MustHaveHappened()”.

Fake It Easy – WithArgumentsForConstructor

Dieser Tage stand ich vor dem Problem, dass ich bei einem Test der zu testenden Klasse prüfen musst, ob ein Aufruf innerhalb der Klasse stattgefunden hat.

Beispiel: Die Klasse Vb6Process enthält die 2 Methoden Monitor() und Kill(). Die Monitormethode ruft unter bestimmten Umständen, welche ich in meinem Arrange bzw. Act des Unit Tests provoziert habe, die Kill() Methode auf. Und genau das wollte ich prüfen. Nun ist es natürlich sinnfrei das zu mocken, was man testen will.

Fake It Easy bietet uns dafür eine Lösung, nämlich die Methode WithArgumentsForConstructor. Bei meinem Code sieht das wie folgt aus:

   1: _vb6Process = A.Fake<Vb6Process>(

   2:     x => x.WithArgumentsForConstructor(

   3:         () => new Vb6Process(

   4:               jobController,

   5:               token,

   6:               printServiceConfig)

   7:           )

   8:       );

 

Folgendes ist zu beachten:

  • In Zeile 1 wird nicht wie üblich ein Interface, sondern die konkrete Klasse angegeben
  • Über die Methode WithArgumentsForConstructor wird dann die konkrete zu testende Instanz reingereicht. Wichtig ist hierbei, dass eine Expression verwendet wird. Ein Delegate ist nicht möglich!

Damit kriegt Fake It Easy alle Aufrufe innerhalb einer Klasse mit, sodass ich im Assert über

   1: () => A.CallTo(() => _vb6Process.Kill()).MustHaveHappened()

 

prüfen kann, ob die Kill-Methode aufgerufen wurde.

Wichtig: Folgender Aufruf hätte mich nicht zum Ziel geführt

   1: _vb6Process = A.Fake<IVb6Process>(

   2:     x => x.Wrapping(

   3:         new Vb6Process(

   4:             jobController,

   5:             token,

   6:             printServiceConfig

   7:         )

   8:     )

   9: );

 

In diesem Fall hätte ich nur einen Wrapper um die Klasse herum, d.h. ich würde nur Aufrufe von außerhalb auf die Instanz mitbekommen, nicht aber Aufrufe innerhalb der Klasse! Ein riesiges Dankeschön an dieser Stelle an Alex Groß für seine Hilfe dabei.

%d Bloggern gefällt das: