Foundations for Professionals .NET Professionals im Profil guide to C# guidgen.de

Blog

Nutze den Augenblick
und teile der Welt mit, was Du zu sagen hast.

Privaten Code testen

Samstag, 7. August 2010, 00:40 Uhr
Permalink | Kommentare (9) | Kommentare als RSSRSS

Vor einigen Tagen habe ich den Blogeintrag Wie viel kosten Unittests? geschrieben, um zu zeigen, dass der Einsatz von Unittests nicht aufwändiger und teurer ist als der Verzicht auf diese. Mein Fazit lautete:

Da ich – bevor ich die Entwicklung einer Komponente beginne – auf Grund von EBCs und TDD ohnehin detailliert plane, wie deren API aussehen und wie sich diese API für einen Verwender anfühlen wird, erfordert die Planung häufig deutlich mehr Zeit als die eigentliche Implementierung.

Der Aufwand, die Implementierung im Sinne von TDD noch mit Unittests auszustatten, geht daher gegen Null.

Auf die Idee für diesen Blogeintrag haben mich der Blogeintrag TDD in der (meiner) Praxis – Wunsch und Wirklichkeit von Thomas Bandt und der Thread Tatsächlicher Nutzen von Unit-Tests? auf myCSharp.de gebracht, die beide ebendiesen Aspekt in Frage gestellt haben.

In besagtem Thread auf myCSharp.de hat sich zwischenzeitlich noch eine weitere interessante Diskussion ergeben: Die aktuell dort diskutierte Frage lautet, inwiefern privater Code mit eigenen Unittests ausgestattet werden sollte.

Hierzu empfiehlt es sich, zunächst die verschiedenen Varianten zu evaluieren, wie dies überhaupt geschehen kann – schließlich ist der Zugriff auf privaten Code von außen per se zunächst nicht möglich.

  • Reflection: Die einfachste Variante, privaten Code von außen zugreifbar zu machen, liegt darin, per Reflection auf ihn zuzugreifen. Dies ist auch die Variante, die Visual Studio von Haus aus im Rahmen der automatischen generierten Zugriffsklassen für Tests bietet. Die Nachteile liegen auf der Hand: Da der Zugriff mit Hilfe von Reflection naturgemäß nicht typisiert erfolgen kann, muss auf Magic Strings zurückgegriffen werden – die spätestens bei umfangreichen Refaktorisierungen für Probleme und Unannehmlichkeiten sorgen.
  • Vererbung: Als zweite Möglichkeit bietet sich an, den Zugriffsmodifizierer private durch den Zugriffsmodifizierer protected zu ersetzen – auf diese Art wird privater Code zumindest für abgeleitete Klassen zugreifbar. Da prinzipiell nichts dagegen spricht, auch eine Testklasse von der zu testenden Klasse abzuleiten, kann der Zugriff auf diese Art hergestellt werden. Doch private und protected weisen eine unterschiedliche Semantik auf und sind daher nicht bedingungslos gegeneinander austauschbar. Zudem funktioniert dieser Trick nicht bei versiegelten Klassen, von denen nicht abgeleitet werden kann.
  • InternalsVisibleTo: Als weitere Möglichkeit kann der Zugriffsmodifizierer private auch durch den Zugriffsmodifizierer internal ersetzt werden. Wird zusätzlich das Attribut InternalsVisibleTo verwendet, fühlt sich der Zugriff auf privaten Code aus Unittests de facto so an wie der Zugriff auf nicht-privaten Code. Allerdings trifft der Nachteil der zweiten Variante hier ebenfalls zu – sogar in gravierenderem Ausmaß, da internal noch freigiebiger ist als protected.
  • Zustand als Abhängigkeit: Die vierte Variante schließlich wurde von Ralf Westphal in seinem sehr lesenswerten Blogeintrag Zustand als Abhängigkeit – IoC konsequent gedacht beschrieben. Diese Variante ist deutlich aufwändiger, packt das Problem jedoch an der Wurzel an und stellt – zumindest in meinen Augen – die beste der hier vorgestellten vier Varianten dar. Allerdings verfügt sie neben dem hohen Aufwand noch über einen weiteren Nachteil: Sie erfordert die Einführung eines zusätzlichen, nur für Tests gedachten Konstruktors – die Tests verwenden also nicht mehr den gleichen Konstruktor, der auch im Produktivcode zur Anwendung kommt.

Alles in allem ist das Testen von privatem Code also nicht einfach und wirft etliche Probleme auf. In TDD gilt die Regel, dass schwer testbarer Code in der Regel eine Schwäche in seiner Architektur aufweist. Nimmt man beides zusammen, folgt aus dem Bedürfnis, privaten Code testen zu wollen, dass die zu Grunde liegende Architektur des zu testenden Codes eine Schwachstelle aufweist.

Bernd Hengelein schreibt dazu in seinem Blogeintrag TDD – ein Garant für heiße Diskussionen:

Wenn man den Drang verspürt eine private Methode testen zu wollen, ist das ein Code Smell der auf eine Umstrukturierung der Klasse hindeutet.

Auf stackoverflow.com wird in How do you unit test private methods? ähnlich argumentiert:

If you want to unit test a private method, something may be wrong. Unit tests are (generally speaking) meant to test the interface of a class, meaning its public (and protected) methods.

Auch Wikipedia geht in dem Abschnitt Eigenschaften von Modultests auf ähnliche Art auf die Frage ein:

Modultests sollen gemäß dem Design by contract Prinzip möglichst nicht die Interna einer Methode testen, sondern nur ihre externen Auswirkungen (Rückgabewerte, Ausgaben, Zustandsänderungen, Zusicherungen). Werden auch interne Details der Methode geprüft (dies wird als White-Box-Testing bezeichnet), droht der Test fragil zu werden, er könnte auch fehlschlagen, obwohl sich die externen Auswirkungen nicht geändert haben. Daher wird in der Regel das sogenannte Black-Box-Testing empfohlen, bei dem man sich auf das Prüfen der externen Auswirkungen beschränkt.

Das Buch Pragmatic Unit Testing äußert sich ebenfalls ähnlich:

In general, we don’t want to break any encapsulation for the sake of testing (or as Mom used to say, “Don’t expose your privates!”).

Kurzum: Die Fachwelt ist sich einig, dass man privaten Code nicht testen sollte. Doch warum ist dies so? Wie liegt die architektonische Schwachstelle, deren Existenz man aus der bloßen Schwierigkeit, einen Test zu formulieren, ableiten kann?

Die Schwachstelle liegt im Kontrakt der zu testenden Klasse. Jeder Code weist einen Kontrakt auf – implizit oder explizit als dedizierte Schnittstelle – der für die Verwender dieses Codes gedacht ist: Auf das, was mir öffentlich zugesichert wird, kann ich mich verlassen. Dies gilt dabei nicht nur im Hinblick auf die Syntax, sondern explizit auch im Hinblick auf die Semantik.

Alles, was nicht im Kontrakt enthalten ist, wird mir als Verwender auch nicht explizit zugesichert. Anders formuliert: Alles, was nicht im Kontrakt enthalten, sondern private ist, zählt zu den Interna – die mich als Verwender nichts angehen. Das ist das Prinzip einer Blackbox.

Was ist nun der Sinn eines Unittests? Der Unittest soll dem Entwickler einer Klasse garantieren, dass er diese derart refaktorisieren kann, dass er ihre innere Struktur ändert, ohne zugleich den nach außen zugesicherten Kontrakt zu ändern. Ein Unittest ermöglicht dem Entwickler, das Innere der Blackbox nach Belieben zu verändern, sofern ihre Funktionsweise nach außen erhalten bleibt.

Würde man nun beginnen, auch das innere der Blackbox mit Unittests abzusichern, so würde man dem Entwickler diese Freiheit nehmen und in seinem Revier wildern. Eine interne, durchaus sinnvolle, aber ausgesprochen große Refaktorisierung würde hiermit unmöglich gemacht – schließlich könnten potenziell alle Unittests, die auf das Innere der Blackbox abzielen, fehlschlagen.

Die Unittests müssten also angepasst werden – was aber der ursprünglichen Absicht widerspricht, mit den Unittests sicherzustellen, dass das Verhalten vor und nach der Refaktorisierung identisch ist. Kurzum – das Testen von privatem Code ist nicht nur umständlich, sondern geht auch noch an der eigentlichen Absicht der Tests vorbei.

Die einzig verbleibende Frage lautet nun noch, wie mit privatem Code umgegangen werden sollte – wenn er sich schon als ungeeignet für Unittests erweist?

Die Antwort auf diese Frage ist einfach: Zunächst einmal gilt, dass auch privater Code getestet wird – nur eben indirekt. Ruft eine öffentliche Methode privaten Code auf, so wird dieser implizit ebenfalls getestet – ist dies nicht der Fall, handelt es sich eventuell um Code, der gar nicht benötigt wird, oder um einen Hinweis darauf, dass noch nicht alle potenziellen Fälle per Test überprüft werden.

Für private Methoden gibt es jedoch noch einen weiteren Ansatz, wie mit ihnen umgegangen werden kann: Häufig enthalten private Methoden nämlich Code, der gar nichts mit dem eigentlichen Zweck der sie enthaltenden Klasse gemeinsam hat – streng genommen handelt es sich also sogar um eine Verletzung des Single Resposibility-Prinzips (SRP).

Die Lösung hierfür lautet: Auslagern des privaten Codes in eine eigene Klasse mit dedizierter Verantwortlichkeit. Diese weitere Einführung zusätzlicher Klassen oder Komponenten mit eigener Verantwortlichkeit führt damit nicht nur zu besserer Testbarkeit, sondern automatisch auch zu einer Verbesserung der Architektur – und kann in nahezu allen Fällen dieser Art angewandt werden.

Am Anfang mag sich dies ein wenig ungewohnt anfühlen – doch wenn man darüber nachdenkt, findet man beinahe in jedem Fall entsprechende Verantwortlichkeiten, die vermischt wurden. Diese aufzulösen, ist dann ein Leichtes.

Abschließend kann noch festgestellt werden, dass das Problem des Testens von privatem Code hauptsächlich Entwickler betrifft, die nicht nach TDD vorgehen. Dies ist jedoch nicht verwunderlich, denn TDD-affine Entwickler schreiben zuerst den Test und dann erst den Code: Privater Code kann hier also nur durch Refaktorisieren von bereits bestehendem und getestetem Code entstehen.

TDD sorgt also auch in diesem Fall für eine bessere Architektur – für mich persönlich, ein weiterer Grund, den mit TDD eingeschlagenen Weg weiter zu verfolgen.

.NET Day Franken

Dienstag, 13. Juli 2010, 17:04 Uhr
Permalink | Kommentare (0) | Kommentare als RSSRSS

Wie in meinem gestrigen Blogeintrag berichtet, war ich am 22. und 23. Juni 2010 in München auf den dotnetpro.powerdays, wobei ich für den Tag C# für Profis nicht nur als Referent, sondern auch als Content Manager tätig war.

Im Anschluss an diese beiden Tage habe ich noch einen Tag in München verbracht –unter anderem mit meinem neuen Beratungsformat, der tech:lounge – bevor es dann weiter nach Nürnberg ging, wo am 26. Juni 2010 der .NET Day Franken erstmalig stattgefunden hat.

Organisiert wurde diese eintägige Community-Konferenz von Thomas Müller und Bernd Hengelein, die als Themen .NET 4.0, Visual Studio 2010 und verteilte wie auch agile Entwicklung ausgewählt hatten.

Ich selbst war mit einer Session zu den Neuerungen in C# 4.0 unter dem Titel

C# 4.0 als Fliegenfalle

dabei, wobei mein Fokus nicht auf der Vorstellung der neuen Features lag, sondern auf der Frage, wann und in welchem Kontext diese sinnvoll eingesetzt werden sollten – im Prinzip angelehnt an meinen Blogeintrag Für und wider C# 4.0, allerdings um einiges ausführlicher.

Von den rund 100 Teilnehmern des .NET Day Franken durfte ich in meiner Session rund 60 begrüßen. Das Feedback war fast ausnahmslos sehr positiv, was mich natürlich sehr gefreut hat – erfreulich war auch, dass unter den von mir angesprochenen Kritikpunkten für die meisten Teilnehmer doch noch neue Aspekte enthalten waren, so dass das Ziel der Session erreicht wurde.

Alles in allem hat der .NET Day Franken sehr viel Spaß gemacht, es gab sehr viele interessante Sessions, ein hervorragendes Mittagsbuffet, eine schöne Lokation – was will man mehr.