Dojos für Entwickler. Stefan Lieser

Чтение книги онлайн.

Читать онлайн книгу Dojos für Entwickler - Stefan Lieser страница 7

Dojos für Entwickler - Stefan Lieser

Скачать книгу

}

       }

       ...

       var result = Input().A().B().C();

      Lösungsansatz

      Der erste Schritt des INotifyProperty-Changed-Testers besteht darin, die zu testenden Properties des Typs zu ermitteln. Anschließend muss er jedem dieser Properties einen Wert zuweisen, um zu prüfen, ob der Event korrekt ausgelöst wird. Zum Zuweisen eines Wertes benötigen Sie zur Laufzeit einen Wert vom Typ der Property. Wenn Sie auf eine string-Property stoßen, müssen Sie einen string-Wert instanzieren, das ist einfach.

      Komplizierter wird die Sache, wenn der Typ der Property ein komplexer Typ ist. Denken Sie etwa an eine Liste von Points oder Ähnliches. Richtig knifflig wird es, wenn der Typ der Property ein Interfacetyp ist. Dann ist eine unmittelbare Instanzie-rung nicht möglich. Das Instanzieren der Werte scheint eine eigenständige Funktionseinheit zu sein, denn die Aufgabe ist recht umfangreich.

      Wenn Sie die Properties und ihren jeweiligen Typ gefunden haben, müssen Sie für jede Property einen Test ausführen. Jeder dieser Tests ist eine Action<object>, die auf einer Instanz der Klasse ausgeführt wird, die zu testen ist. Wenn also die Klasse KundeViewModel überprüft werden soll, wird für jede Property eine Action<KundeView-Model> erzeugt. Sind die Actions erzeugt, müssen sie nur nacheinander ausgeführt werden. Dabei soll jede Action eine neue Instanz der zu testenden Klasse erhalten. Andernfalls könnte es zu Seiteneffekten beim Testen der Properties kommen.

      Funktionseinheiten identifizieren

      Die erste Aufgabe ist also das Ermitteln der zu testenden Properties. Eingangsparameter in diese Funktionseinheit ist der Typ, für den die INotifyPropertyChanged-Implementierung überprüft werden soll. Das Ergebnis der Flowstage ist eine Aufzählung der Property-Namen.

      static IEnumerable<string>

       FindPropertyNames(this Type type)

      An dieser Stelle fragen Sie sich möglicherweise, warum ich die Property-Namen als Strings zurückgebe und nicht etwa eine Liste von PropertyInfo-Objekten. Schließlich stecken in PropertyInfo mehr Informationen, insbesondere der Typ der Property, den ich später ebenfalls benötige. Ich habe mich dagegen entschieden, weil dies das Testen der nächsten Flowstage deutlich erschwert hätte. Denn diese hätte dann auf einer Liste von PropertyInfo-Objekten arbeiten müssen. Und da PropertyInfo-Instanzen nicht einfach mit new hergestellt werden können, wären die Tests recht mühsam geworden.

      Nachdem die Property-Namen bekannt sind, kann die nächste Flowstage dazu den jeweiligen Typ ermitteln. Die Flowstage erhält also eine Liste von Property-Namen sowie den Typ und liefert eine Aufzählung von Typen.

      static IEnumerable<Type> FindPropertyTypes(

       this IEnumerable<string> propertyNames,

       Type type)

      Im Anschluss muss für jeden Typ ein Objekt instanziert werden. Diese Objekte werden später im Test den Properties zugewiesen. Die Flowstage erhält also eine Liste von Typen und liefert für jeden dieser Typen eine Instanz des entsprechenden Typs.

      static IEnumerable<object> GenerateValues(

       this IEnumerable<Type> types)

      Dann wird es spannend: Die Actions müssen erzeugt werden. Dabei lässt es sich leider nicht vermeiden, die Property-Namen aus der ersten Stage nochmals zu verwenden. Die Ergebnisse der ersten Stage fließen also nicht nur in die unmittelbar nächste Stage, sondern zusätzlich auch noch in die Stage, welche die Actions erzeugt. Die Namen der Properties werden benötigt, um mittels Reflection die jeweiligen Setter aufrufen zu können.

      static IEnumerable<Action<object>>

       GenerateTestMethods(this IEnumerable<object>

       values, IEnumerable<string> propertyNames,

       Type type)

      Der letzte Schritt besteht darin, die gelieferten Actions auszuführen. Dazu muss jeweils eine Instanz der zu testenden Klasse erzeugt und an die Action übergeben werden.

      Abbildung 2 zeigt den gesamten Flow. Die einzelnen Flowstages sind als Extension Method implementiert. Der Flow selbst wird in der öffentlichen Methode Notifica-tionTester.Verify zusammengesteckt. Testen möchte ich die einzelnen Stages aber isoliert. Denn nur so kann ich die Implementierung Schritt für Schritt vorantreiben und muss nicht gleich einen Integrationstest für den gesamten Flow schreiben. Einige Integrationstests sollten am Ende aber auch nicht fehlen.

      Diese Vorgehensweise hat einen weiteren Vorteil: Um den NotificationTester testen zu können, müssen Testdaten her. Da er auf Typen arbeitet, müssen also Testdaten in Form von Klassen erstellt werden. Das ist nicht nur aufwendig, sondern wird auch schnell unübersichtlich. Ganz kommt man zwar am Erstellen solcher Testklassen auch nicht vorbei, aber der Aufwand ist doch reduziert.

      [Abb. 2] Die zu testenden Properties ermitteln.

      Interna testbar machen

      Um die einzelnen Flowstages isoliert testen zu können, habe ich ihre Sichtbarkeit auf internal gesetzt. Damit sind die Methoden zunächst nur innerhalb der Assembly, in der sie implementiert sind, sichtbar. Um auch in der Test-Assembly darauf zugreifen zu können, muss diese zusätzliche Sichtbarkeit über das Attribut InternalsVisibleTo hergestellt werden:

      [assembly:InternalsVisibleTo(

       "INotifyTester.Tests")]

      Das Attribut kann prinzipiell in einer beliebigen Quellcodedatei in der Assembly untergebracht werden. Üblicherweise werden Attribute, die sich auf die Assembly beziehen, in der Datei AssemblyInfo.cs untergebracht. Diese finden Sie im Visual Studio Solution Explorer innerhalb des Ordners Properties.

      Das Sichtbarmachen der internen Methoden nur zum Zwecke des Testens halte ich auf diese Weise für vertretbar. UnitTests sind Whitebox-Tests, das heißt, die Art und Weise der Implementierung ist bekannt. Im Gegensatz dazu stehen Blackbox-Tests, die ganz bewusst keine Annahmen über den inneren Aufbau der zu testenden Funktionseinheiten machen. Durch Verwendung von internal ist die Sichtbarkeit nur so weit erhöht, dass die Methoden in Tests angesprochen werden können. Eine vollständige Offenlegung mit public wäre mir zu viel des Guten. Übrigens halte ich es für keine gute Idee, auf die Interna einer zu testenden Klasse mittels Reflection zuzugreifen. Dabei entziehen sich nämlich die Interna, die über Reflection angesprochen werden, den Refaktori-sierungswerkzeugen. Und wie man sieht, ist internal in Verbindung mit dem InternalsVisibleTo-Attribut völlig ausreichend.

      FindPropertyNames

      Die Namen der Properties werden durch die Flowstage FindPropertyNames geliefert. Dabei entscheidet diese Funktion bereits, welche Properties geprüft werden sollen. Es werden nur Properties berücksichtigt, die über öffentliche Getter und Setter verfügen.

      Um diese Funktion testen zu können, müssen Testklassen angelegt werden. Das lässt sich leider nicht

Скачать книгу