Follow my new blog

Dienstag, 13. Juli 2010

Testnähe aus der Distanz betrachtet

Die Diskussion um den besten Platz für Tests reißt nicht ab. Nach meiner Gegenüberstellung zweier Testplatzierungsstile hat Stefan nachgelegt und nun Ilker gekontert. Die WM ist vorbei, der Testball aber noch im Spiel… :-)

Was für ein Spiel ist das aber? Ist es ein Nullsummenspiel? Und worum dreht es sich? Die erste Frage ist, was “nah am zu testenden Code” bedeutet. die zweite Frage, ob Nähe das wichtigste oder gar einzige Kriterium für die Platzierung von Testcode ist.

Nah und fern

Wie nah ist nah? Ist “nah am Code” nur dies? Test und System-under-Test (SUT) liegen im selben Projekt.

image

Oder wäre es sogar so noch besser? Der Test ist dem SUT visuell untergeordnet, um den Blick auf die Hauptfunktionalität nicht zu verstellen:

image

Oder ist Nähe noch gegeben, wenn Tests in eigenen Unterverzeichnissen stehen?

image

Oder darf ich mit Recht behaupten, dass Tests platziert in einem eigenen Projekt noch nah sein können?

image

Hm… verwirrend diese Optionen. Oder sind sie gar nicht so verschieden, denn wie sieht es während der Arbeit an SUT und Tests in Visual Studio aus? Ist aus diesen Bildern die Platzierung der Tests abzulesen?

image 

image

Ich sehe da keinen Unterschied, obwohl die Tests sehr unterschiedlich platziert sind. Für die Arbeit am Code, also den ständigen Wechsel zwischen Test und SUT und Test und SUT, scheint es mir unbedeutend zu sein, ob die Tests als “near spec” im Projekt des SUT liegen oder in einem anderen Projekt. Solange sie in derselben Solution zu finden sind, können Tests und SUT ganz einfach so dargestellt werden.

Wer es noch verwegener mag und einen großen Bildschirm hat, der legt sich Tests und SUT sogar nebeneinander. Solche Darstellung überwindet jede Distanz. Programmieren wird da zur Kuschelstunde ;-)

image

Angesichts dessen würde ich sagen, dass es während der Arbeit an Tests und SUT einerlei ist, ob die Tests im SUT-Projekt liegen oder in verschiedenen. Solange sie sich in derselben Projektmappe befinden, ist der Wechsel zwischen beiden einfach möglich. Im Editor kann man sie immer in die Nähe rücken. Dasselbe gilt für Testdaten.

Kohäsion

Für die Arbeitsgeschwindigkeit an Tests und SUT sollte es keinen Unterschied machen, in welchem Projekt Tests platziert sind. Wie sieht es aber mit der Verständlichkeit aus? Ist Code besser zu verstehen, wenn Tests inkl. Testdaten und SUT in einem Projekt liegen? Ist nicht die Kohäsion zwischen Tests und SUT höher als die zwischen zwei unterschiedlichen SUTs? Müssten daher nicht Tests sogar eher in einem Projekt zusammen mit ihrem SUT liegen als zwei verschiedene SUTs?

Hier ein Blick in die Sourcen von RavenDB, der dokumentenorientierten NoSql-Datenbank von Ayende Rahien:

 image

Die Tests für den Client liegen in einem eigenen Projekt. Ob sie sich nur auf das markierte SUT-Projekt beziehen oder auch die anderen Client-Projekte, ist nicht ersichtlich. Das widerspricht der hohen Kohäsion von Test und SUT, würde ich sagen. Die Zusammenhänge zwischen beiden zu erkennen, ist schwierig.

Wer den Code grundsätzlich so strukturiert, d.h. viele Projekte in einer Projektmappe führt, der beklagt zurecht, dass die natürliche Kohäsion durch getrennte Projekte für Tests und SUT schwer auszudrücken ist.

Wie gesagt, dem Wechsel zwischen SUT und Tests tut das keinen Abbruch. Doch die Übersichtlichkeit leidet zunächst.

Was aber, wenn Software besser strukturiert ist? Wenn ihr ein Plan zugrunde liegt, der auch noch umgesetzt wird? Was wenn Plan und Code gar übereinstimmen? Hier ein Beispiel aus dem CCD Praktikum:

image

Das ist der Plan für ein Feature eines Softwaresystems für Kinos, das wir im Praktikum begonnen haben. Der Plan zeigt die Prozessschritte für ein Feature. Er ist als EBC-Schaltplan ausgelegt. Die Kästen werden in Klassen übersetzt. Die Pfeile in Event-Eventhandler-Verbindungen. Darüber hinaus gibt es aber auch noch Zusammenfassungen zu Komponenten. Die habe ich farblich hervorgehoben. Sie werden in Projekte übersetzt.

Im Repository sieht das dann so aus:

image

Die Komponenten finden sich dort als Projektmappen in eigenen Verzeichnissen wieder. (Wer mag, kann die natürlich auch wieder in Unterverzeichnissen gruppieren.)

Und innerhalb der Komponenten sind jeweils mindestens zwei Projekte zu finden, eines für das SUT sowie eines für dessen Tests:

image

Die hohe Kohäsion zwischen Tests und SUT findet dadurch klaren Ausdruck, würde ich sagen. Wer Tests zu einem SUT sucht, weiß genau, wo er sie findet.

Voraussetzung dafür ist natürlich der explizite Wille zur Architektur. Wo es den nicht gibt oder wo der nicht walten kann, weil der Brownfield-Matsch so tief ist… da mag es tatsächlich angezeigt sein, Tests und SUT im selben Projekt zu pflegen. Doch man sei sich bewusst, was dafür der Grund ist. Tests und SUT im selben Projekt ist dann ein Symptom der Grundproblems “Brownfield”. Tests und SUT im selben Projekt sind aus meiner Sicht daher kein Ziel, sondern nur eine Übergangslösung.

Trennung

Worin sich alle Diskutanten auch einig sind, das ist die Notwendigkeit der Trennung von Tests und SUT bei der Auslieferung. Tests und ihre Artefakte sollen nicht zum Kunden.

Wenn Tests und SUT in verschiedenen Projekten liegen, ist diese Trennung trivial. Testsprojekte haben ein anderes Output-Verzeichnis als SUT-Projekte, so dass Visual Studio beim Bau einer Solution automatisch die Trennung vornimmt. Im SUT-Output-Verzeichnis kommt immer nur das an, was auslieferungsrelevant ist.

Anders wenn Tests und SUT zusammen in einem Projekt stehen. Dann muss der Buildprozess explizit angewiesen werden, Tests und Testartefakte auszufiltern. Das mag eine Zeile Scriptcode sein oder viele. Egal. Wer den Buildprozess aufsetzt, muss daran denken. Und Entwickler müssen womöglich auch daran denken und während der Entwicklung Konventionen einhalten, die das ermöglichen.

So hoch die Kohäsion zwischen Tests und SUT prinzipiell sein mag – bei der Auslieferung hört sich dann doch auf.

Wenn es aufgrund von monolithischem Code nicht anders geht, die Kohäsion während der Entwicklung auszudrücken als durch Zusammenlegung von Tests und SUT in einem Projekt, dann sei es so und man filtere am Ende beim Build die Tests heraus. Aber, bitte, bitte, man verkaufe mir das nicht als Tugend. Zusätzlicher Aufwand ist zusätzlich fehleranfällig. Wenn ich ihn nicht treiben muss, dann möchte ich das auch nicht.

Eine saubere, angemessene Planung mit klarer Übersetzungsregel schenkt mir also nicht nur Evolvierbarkeit, sondern auch übersichtliche Codeverhältnisse und weniger Aufwand für den Buildprozess. Dann bin ich für saubere, angemesse Planung, würd ich sagen. (Die nicht zu verwechseln ist mit BDUF.)

Fazit

Tests im selben Projekt wie ihr SUT? Ist einzig das zeitgemäß? Ich würde sagen, wir sollten nicht nach Geschmack und Mode und Zeitgeist entscheiden. Professionelle Arbeit braucht Werte und Prinzipien. Um welche geht es hier?

Produktionseffizienz: Tests im selben Projekt sollen die Arbeit beschleunigen. Der Wechsel zwischen beiden soll leichter fallen. Wie oben gezeigt, glaube ich daran jedoch nicht. Visual Studio macht es leicht, für die Arbeit an Tests und SUT Nähe im Editor herzustellen. Und mit einem Tool wie dotCover wird es noch einfacher.

image

Als Beispiel die Open Source Software Lounge Repository. Sie ist komponentenorientiert entwickelt, d.h. für jede Komponente gibt es auch eine Werkbank bestehend aus Tests und SUT in je verschiedenen Projekten.

image

Allerdings bin ich bei der Codeorganisation einen Kompromiss eingegangen und habe alle Projekte in einer Projektmappe versammelt. Damit wollte ich es dem unbedarften Open Source Freund ein wenig einfacher machen, in die Quellen einzusteigen.

Ich finde das übersichtlich; viel übersichtlicher als die RavenDB Sourcen trotz Multi-Projekt Solution. Die Tests liegen nahe dem SUT, sie sind damit “near specs”. Aber gerade geht es ja um Produktionseffizienz. Die ist nicht nur hoch, weil ich Tests und SUT side-by-side im Editor liegen können (s.o.), sondern auch, weil dotCover mir erlaubt, aus dem SUT zum Test zu springen:

image

Die Liste auf der rechten Seite zeigt die Tests, die eine bestimmte SUT-Zeile abdecken. Damit muss ich gar nicht mehr wissen, wo in einer Projektmappe Tests überhaupt stehen. Ich lasse schlicht alle Tests mit dotCover ausführen, bekomme einen Eindruck vom Nutzen der Tests aufgrund ihrer Codeabdeckung – und kann dann aus den SUT-Zeilen, an denen ich gerade arbeite, zu relevanten Tests springen. Im selben Projekt oder in anderen. Wenn das nicht effizient ist.

Ebenfalls effizient ist, dass bei Trennung von Tests und SUT kein weiterer Aufwand für die Trennung beider Anteile während der Produktion zu treiben ist. Nicht anfallender Aufwand ist ganz eindeutig der kleinste mögliche Aufwand.

Übersichtlichkeit: Der nächste für die Kolokation von Tests und SUT reklamierte Wert ist die Übersichtlichkeit. Es sei übersichtlicher, wenn beide in einem Projekt lägen. Wie gesagt, das kann ich glauben, wenn es denn nicht anders geht. Wenn die Verteilung auf zwei Projekte ihrer natürlichen Kohäsion zuwiderläuft, weil sie dann sehr weit auseinander stünden in einer Projektmappe, dann mag die Kolokation einen Vorteil haben.

Der Umkehrschluss ist jedoch aus meiner Sicht falsch: Weil Zusammenlegung im Matschfeld einen gewissen Vorteil haben kann, weil damit ein grundlegendes Problem pragmatisch kurzfristig kaschiert wird, deshalb sollten Tests und SUT möglichst immer zusammengelegt werden. Nein, so würde der Bock zum Gärtner.

Darüber hinaus sollte Übersichtlichkeit aber noch weiter gefasst werden. Die zu einem SUT gehörenden Tests leicht finden können, ist eine Sache. Wie stehts aber mit der Identifikation von SUTs? Wie steht es mit Verantwortlichkeiten der Software im Allgemeinen auf unterschiedlichen Abstraktionsebenen?

Übersichtlichkeit entsteht nicht nur durch Zusammenlegung, sondern auch durch Trennung. Mir scheint, dass dieses Prinzip schnell übergangen wird, wenn man im Kolokationsflow ist. Denn wo Tests und SUT zusammengeschnürt werden sollen, dann halte ich die Tendenz für hoch, auch anderes zusammen zu schnüren. Und am Ende entsteht bzw. wird erhalten der Monolith, das Softwaresystem mit hoher Entropie, das Brownfield.

Statt Tests als Anlass zu begreifen, zumindest mal einen Concern aus dem großen Brownfield herausziehen zu können, trägt die Kolokation vielmehr zum Erhalt des status-quo bei. Schade.

Mein persönliches Fazit der Diskussion: Zeitgeist und Geschmack bringen uns nicht weiter. Meinungen sollten auf Prinzipien verweisen. Sie sollten zeigen, wie und warum und wann diese oder jene durch eine Praktik besser oder schlechter umgesetzt werden.

Bei trivialem Code mag es daher ok sein, Tests und SUT zusammenzufassen. Eine Code Kata könnte dazu gehören – wenn man nicht gerade üben möchte, eine andere Codeorganisation einzuhalten.

Jenseits des trivialen Codes bezweifle ich jedoch, dass Tests und SUT im selben Projekt der Produktionseffizienz und Übersichtlichkeit wirklich dienen. Sie sind mir eher Symptom für ein Wurzelproblem und Bequemlichkeitsmaßnahme. Mehr als eine Übergangslösung aus der Not geboren sehe ich in ihnen nicht. (Mann, oh, mann, wie störrisch und uneinsichtig ist das denn eigentlich ;-) Aber es hilft nix: “Hier stehe ich, ich kann nicht anders.” Für die Zukunft gelobe ich jedoch, den Kontext besser zu berücksichtigen. Bei einer Code Kata könnte ich mal von dieser “Prinzipienreiterei” abesehen ;-)

Kommentare:

Carsten hat gesagt…

„Aber es hilft nix: “Hier stehe ich, ich kann nicht anders.” Für die Zukunft gelobe ich jedoch, den Kontext besser zu berücksichtigen. Bei einer Code Kata könnte ich mal von dieser “Prinzipienreiterei” abesehen ;-)“

Ich glaube, das kannst Du nicht. Deine Motivation ist „es besser zu wissen“. Du willst nicht überzeugen, Du suchst nach Bewunderung.

Im vorliegenden Fall ist Dir meine gewiss, denn ich finde Du hast Recht.

Ralf Westphal - One Man Think Tank hat gesagt…

@Carsten: Freut mich, wenn du mit mir übereinstimmst.

Aber das mit der Bewunderung stimmt so nicht. Ich freue mich natürlich, wenn Leute gut finden, was ich so "rauslasse". Viel angenehmer als Bewunderung ist aber das Gefühl, etwas verstanden zu haben. Wenn Puzzleteile an ihre Plätze fallen, wenn etwas verständlich wird, wenn es funktioniert, sich harmonisch anfühlt... dann habe ich für mich ein wichtiges Ziel erreicht.

Schön natürlich, wenn ich mit solchen Aha-Erlebnissen durch Weitersagen auch noch anderen helfen kann.

Will ich "es besser wissen"? Hm... ne, nicht im Sinne von "unbedingt Recht behalten". Am Ende ist mir die richtige Erkenntnis wichtiger als die Frage, woher sie kommt. Allerdings gebe ich meine Meinung nicht "kampflos" drein ;-) Wenn schon andere Meinungen übernehmen, dann sollen die auch mit guten Gründen vorgetragen werden. Ich bemühe mich zumindest auch darum.

-Ralf

Frank Striegel hat gesagt…

@Carsten: Hat dieser Kommentar irgendetwas zum Thema beigetragen? Also bitte, wenn Kritik, dann bitte konstruktiv!

Gruss
Frank

Anonym hat gesagt…

@Frank Striegel: Hat Dein Kommentar irgendetwas zum Thema beigetragen?

Frank Striegel hat gesagt…

@Anonym: Misst, jetzt hat mich jemand erwischt.

Aber ich muss Dich enttäuschen, ich war mir dieser scheinbaren Widersprüchlichkeit bewusst. Warum scheinbar? Nun, weil ich denke, dass es, zwar nicht auf direkter, so doch auf abstraktere Ebene etwas mit dem Thema zu tun hat. Sind die Kommentare mit unnötigen Diskussionen (wie diese hier) überfrachtet, so sind die wertvollen Erkenntnisse aus den anderen Diskussionen nur schwer zu erkennen.

Achja, es wäre schön, wenn hinter einer Kritik/Statement ein konkreter Name stünde...