Wohin mit den Tests?
Bislang habe ich immer dafür plädiert, Tests und Implementierung in getrennten Projekten unterzubringen. Im Sinne von Separation of Concerns sind Tests und Implementierung nun mal zwei unterschiedliche Belange, die daher nicht im selben Projekt untergebracht werden sollten. Im Kern scheint mir das Problem darin zu liegen, die Dateien mit den Tests sauber von den Dateien mit der Implementierung zu trennen.
Auf der anderen Seite gehören Test und Implementierung zusammen. Schon bei der ersten Implementierung arbeitet man ständig an beidem und auch bei Änderungen sind in vielen Fällen Änderungen/Ergänzungen sowohl an den Tests als auch der Implementierung erforderlich. Um diese Zusammengehörigkeit auszudrücken, verwende ich in den Projekten die gleiche Verzeichnisstruktur. So weiß ich, wo die zu einer Implementierung gehörenden Tests zu suchen sind. Aber mir fällt schon auf, dass dies mit Disziplin und Pflege verbunden ist. Ordnet man den Code im Implementierungsprojekt anders an, muss man dies im Testprojekt nachziehen. Speziell das Umbenennen von Klassen führt häufig dazu, dass die Tests nicht umbenannt werden. Würden diese näher bei der Klasse stehen, würde einem das Problem eher auffallen.
Nun ist das Problem bei der Komponentenorientierten Architektur nicht wirklich groß, denn da sind die einzelnen Projekte überschaubar klein. Jede Komponente besteht in der Regel aus wenigen Klassen, meist eine Hand voll, selten deutlich mehr als 10. Da führt die Unterbringung von Tests und Implementierung in getrennten Projekten nicht so schnell zum Chaos.
Dennoch, angeregt durch Gespräche mit Teilnehmern des Open Space in Karlsruhe, an dem ich selbst leider nicht teilnehmen konnte, und angeregt durch die folgende Bemerkung von Ilker, möchte ich es mal anders ausprobieren:
weil es schlichtweg nicht mehr zeitgemäß ist, Tests vom SUT zu trennen. Je näher sie zueinander stehen, um so besser.
Die Frage ist also, wie schafft man es, dass die Tests möglich nahe bei der Implementierung stehen, andererseits aber eine klare Abgrenzung möglich ist. Eine mögliche Lösung heißt: Hierarchien bilden. Ralf hat in einem Blogpost in einem anderen Kontext schon auf vsCommands und die Möglichkeiten der Gruppierung hingewiesen. Mit vsCommands ist es möglich, Dateien zu gruppieren, so wie Visual Studio es mit partiellen Klassen macht. Auf diese Weise habe ich gerade mal die Datenstruktur Queue<T> implementiert und muss sagen, dass mir die dadurch entstandene Projektstruktur gut gefällt:
Jetzt stehen die Tests zur Klasse Queue unmittelbar darunter. Wenn ich sie nicht sehen möchte, kann ich sie zuklappen. Sehr schön.
Eine Frage bleibt allerdings noch: die Tests landen jetzt in der Assembly, die eigentlich nur die Implementierung enthalten sollte. Ist das egal? Kriegt man das im Buildprozess mit einfachen Mitteln behoben?
July 2nd, 2010 at 9:48
Schau mal hier: http://github.com/forki/FAKE/issues#issue/4
Ich denke ich schaffe das noch am WE.
Grüße Steffen
July 2nd, 2010 at 9:50
Hallo Stefan, das Problem hatten wir beim Open Space in Karlsruhe diskutiert und ich hatte meine Ideen zum Gruppieren hier zur Diskussion gestellt: http://gist.github.com/457248
Wenn man beim Gruppieren nach einem reproduzierbaren Schema vorgeht, scheint es laut Alex Groß kein größeres Problem zu sein, das beim Build auszufiltern, indem man die Test-Files einfach aus dem Projekt-Files ausschließt. Im Ansatz ist das ja hier beschrieben: http://msdn.microsoft.com/en-us/library/ms171455.aspx
July 2nd, 2010 at 9:51
>> Kriegt man das im Buildprozess mit einfachen Mitteln behoben?
kann man da nicht excludierungen machen im build prozess ueber einen eigenen build task der die test dateien nicht inkludiert?
July 2nd, 2010 at 9:51
p.s. ansonsten muss ich sagen gefaellt mir der ansatz sehr gut
July 2nd, 2010 at 9:52
Kannst du mir dein Projekt vielleicht gleich mal als weiteren Testcase geben?
Allerdings brauche ich schon irgendwie eine (leicht erkennbare) Konvention um Tests von Implementierung zu unterscheiden.
Grüße Steffen
July 2nd, 2010 at 9:53
Hi Stefan,
Vorschlag für die Testdatei:
#if DEBUG
// Inhalt der Datei
#endif
Somit hättest Du die Tests nur im Debug-Build.
July 2nd, 2010 at 10:20
Tests haben nicht nur eine spezielle Verantwortlichkeit (SRP), sondern gehören auch einer ganz anderen Kategorie von Code (Concern) an (SoC). So recht verstehe ich daher nicht, dass sich viel Widerstand gegen eine Trennung von Implementation und Tests regt.
Mein Gefühl: Hier soll irgendwas gespart werden. Tastendrücke beim Anlegen eines zweiten Projektes? Oder gar an der Menge von Projekten, weil es nicht mehr als soundsoviele geben sollte? Da frage ich mich, ob nicht ein weiteres CCD-Prinzip verletzt wird: Beware of Premature Optimization (BoPO).
Bringt dann aber womöglich KISS auf der anderen Seite der Waage mehr Gewicht ins Spiel als SRP, SoC, BoPO? Nein. Der Aufwand für ein zweites Projekt für Tests ist marginal. Die Verständlichkeit des Codes insgesamt sinkt nicht (sondern steigt eher).
Und was ist mit der Änderungsfreundlichkeit? Wird irgendwas einfacher, wenn Testklassen und Implklassen in einem Projekt liegen? Funktionieren dann Refaktorisierungen besser? Nein. R# ist das egal, solange bei in derselben Sln liegen.
Es bleibt ein einziger Grund für eine Kolokation: Irgendwie gehören doch Impl und Test zusammen.
Klar, die Kohäsion zw Impl und Test ist höher als zwischen Impl der einen Verantwortlichkeit (z.B. Frontend) und der anderen (z.B. Geschäftslogik).
Aber wiegt diese Kohäsion schwerer als SRP, SoC, BoPO? Vielleicht sind wir hier im Land der Geschmäcker angekommen. Aber ich würde sagen: Nein. Diese Kohäsion drückt sich immer noch darin aus, dass Impl-Projekt und Test-Projekt in derselben Sln (Komponentenwerkbank) liegen. (Oder falls die Sln größer ist, können beide zusammen in einem Sln-Folder liegen.)
Und noch ein weiterer Grund gegen dasselbe Projekt: Tests haben andere Anforderungen an Assembly-Referenzen (z.B. für NUnit, Mock Fx, BDD Extensions). Tests haben jenseits der Trivialität auch andere Anforderungen an Projektdateien. Da sind Dateien mit Testdaten zu verwalten, Goldmaster, eigene App.Config. Und all das soll im Implementationsprojekt mit herumliegen und ins Zielverzeichnis der Impl-Assembly kopiert werden?
Testprojekte definieren ganz pragmatisch nicht nur einen Ort für Code mit eigener Verantwortlichkeit und speziellem Concern (nahe am SUT), sondern darüber hinaus ein ganzes Testbett. Das Zielverzeichnis des Tests ist eine Spielwiese. Sie wird autom. von VS erzeugt. Sie ist getrennt vom Zielverzeichnis der Implementationen. Wenn das keine weitere saubere Trennung ist.
Und schließlich: Was ist mit dem Testcode in der Produktionsassembly? Der könnte natürlich mit #If DEBUG für die Release-Produktion herausgefilter werden. Was dennoch bleibt, ist die Kontamination des Zielverzeichnisses mit allen möglichen anderen Testabhängigkeiten.
Bottom line: Ja, irgendwie kann man Testcode und andere testrelevante Artefakte in einem Produktionsprojekt schon isolieren (untergeordnete Dateien, Projekt-Folder). Aber das scheint mir eine Diskussion darum, was geht und nicht darum, was sinnvoll ist.
Denn für sinnvoll halte ich es, Grundprinzipien der Softwareentwicklung zu beachten. Und die sagen mit SRP, SoC, BoPO und auch KISS: Trenne den Code. Denn dann sind die Verantwortlichkeiten und die Concerns getrennt. Und dann sind auch die Artefakte getrennt. Das ist KISS, weil Testartefakte nicht später mit Zusatzaufwand aus der Produktion herausgefiltert werden müssen.
Ich bleibe also dabei und trenne meinen Testcode weiterhin. Ob das zeitgemäß ist, ist mir egal. Es ist prinzipiengemäß.
Wenn mir allerdings jmd zeigt, dass andere Prinzipien wichtiger sind… dann überdenke ich das gern.
-Ralf
PS: Mir mag man nun “Prinzipienreiterrei” unterstellen. Ich halte aber dagegen: In einer Branche, die zwar Prinzipien kennt, sie aber weitgehend nicht einhält, sondern immer wieder “Kommt drauf an” ruft und anfängt, selbst Wege zu suchen, statt sich an Prinzipien anzulehnen, da ist es wichtig, Prinzipien erstmal hoch zu halten. Zumindest solange wir nicht auf einer allgemeinen Ebene der Diskussionen mit und über Prinzipien angekommen sind. Den Zeitgeist halte ich dagegen für keinen guten Ratgeber. Der ist nämlich wetterwendisch.
July 2nd, 2010 at 10:21
Hi Stefan,
auf dem Open Space in Karlsruhe haben darüber gesprochen wie wir in Produktions-Builds das Einkompilieren von Tests verhindern können.
Meiner Ansicht nach bietet es sich an bei den CS-Dateien die Tests bzw. Spezifikationen beinhalten auf eine Namenskonvention zurückzugreifen (auch dazu hatten wir eine Session
. Eine Konvention die ich bspw. bei der Benutzung von MSpec verfolge ist den Datei mit dem Spezifikationen auf “Specs” enden zu lassen. In deinem Beispiel EmptyQueueSpecs.cs, oder einfach eine QueueSpecs.cs welche die Kontexte für leere und nicht leere Queues etc. enthält.
Für Produktions-Builds wird ein weiterer Task in den Buildprozess eingehangen der “**/*Specs.cs”-Dateien auf 0 Bytes verkürzt und damit das Einkompilieren der Spezifikationen verhindert.
Aus meiner Sicht sollte man nicht nur zwischen Debug- oder Release-Builds differenzieren und durchaus auch Unit Tests und Integrationstests im Release-Modus kompilieren.
Es ist bspw. vorstellbar dass ein Bug nur im Release-Modus auftritt. In diesem Beispiel verhindert die Benutzung von Präprozessordirektiven (#if DEBUG) die Reproduktion des Bugs in Form eines Tests. Außerdem muss sich der Entwickler daran erinnern die Direktive einzufügen was zu mehr Zeremonie im Testcode führt.
Alex
July 2nd, 2010 at 10:23
Ich kanns mir nicht verkneifen:
Testfiles herausfiltern, Tests herausfiltern, Testartefakte herausfiltern… Leute, wo ist denn das Wurzelproblem hinter all diesen Symptomkuren?
Das Wurzelproblem ist, dass irgendwer keine Lust hat, ein zweites mieseliges Projekt für Tests anzulegen. 2-3 gesparte Mausklicks und Eingaben sollen dann aber dazu führen, dass ich Buildprozesse ausblähe und sonstigen Filter/Organisationsaufwand treibe?
Ne, ne, ne, dat versteh ich nich.
-Ralf
July 2nd, 2010 at 10:27
@Steffen Eine Konvention… alle Klassen die mit Test o.ä. attributiert sind ausschließen? Aber ehrlich gesagt möchte ich eher nicht mit einem Script für’s Build anfangen. Bin mit FinalBuilder ganz glücklich.
@Mathias Wenn ich RELEASE ausliefere, teste ich auch RELEASE. Da ich immer DEBUG ausliefere, teste ich logischerweise DEBUG. Und selbst mit einem anderen nur dafür vorgesehenen Define fände ich das nicht toll. Die Präprozessorzeiten sind zum Glück vorbei. Es sollte im Buildprozess gefiltert werden.
July 2nd, 2010 at 10:28
Eine weitere Anmerkung zu Integrationstests: Wie Ralf erwähnt sind hierfür u. U. gesonderte Konfigurationsdateien erforderlich. Diese gehören aus meiner Sicht nicht zum Produktivcode und sollten deshalb in ein eigenes Projekt. Unit Tests die auf App.config etc. zugreifen sind smelly.
Meine Konvention:
* Unit Tests gemeinsam mit dem Produktivcode verwalten (wie von dir gezeigt als gruppierte Elemente),
* Integrationstests “klassisch” mit der zugehörigen App.config in eigene Class Libraries auslagern.
July 2nd, 2010 at 10:31
@Ralf: Aus meiner Sicht geht es nicht um “keine Lust” und “wenige Mausklicks”. Es geht darum, dass man Strukturen doppelt verwaltet und deshalb “Friction” erzeugt.
Weiterer Nebeneffekt: VS ächzt bei weniger Projekten in der sln deutlich weniger.
July 2nd, 2010 at 10:33
@Alex Das Argument, VS würde mit weniger Projekten schneller greift nicht. Meine Solutions haben meist 2 Projekte. In Worten ZWEI
@Ralf An die Testdaten habe ich nicht gedacht, ein sehr guter Einwand.
Ich glaube… ich bleibe bei getrennten Projekten. Danke für euren Input!!
July 2nd, 2010 at 10:36
Ich sehe das auch so wie Ralf (auch wenn ich dennoch gern Ratschläge wie oben dazu abgebe, wie man es denn anders machen könnte wenn man es anders machen will).
Insbesondere stören mich die zusätzlich erforderlichen Referenzen auf die Testframework-Assemblies, die so im Projekt landen, und der zusätzliche Aufwand, um alles was zum Test gehört wieder herauszufiltern.
Vielleicht sollten wir uns stattdessen fragen: können wir die Vorteile, die sich aus dem “ein-Projekt-Ansatz” ergeben, auch mit dem “klassichen” Test-im-Testprojekt-Ansatz erreichen?
Wenn man z.B. zu jeder Datei sofort die Tests sehen möchte, könnte ich mir dazu ein kleines Visual Studio Plugin vorstellen, dass die Tests zur gerade gewählten Klasse in einem Toolwindow oder Editor-Margin anzeigt. Dann könnte man die Tests ebenfalls sofort direkt bei der Implementierung sehen, hätte aber nicht die Nachteile aus dem “ein-Projekt-Ansatz”.
July 2nd, 2010 at 10:39
Hier bin ich ganz klar auf Ralf’s Seite. Produktivcode und Tests gehören getrennt. Eine Referenz auf NUnit o.ä. gehört nicht in die Produktivassembly und der Code schonmal gar nicht dort hinien kompiliert. Und das herausfiltern u.a. Kniffe sind mir persönlich zuviel Gefriggel. Ich will coden und nicht an solchen Dingen rumbasteln.
July 2nd, 2010 at 11:04
@Alexander: Welche Strukturen muss ich doppelt verwalten, wenn Tests in zwei Projekten stehen? Nenn doch mal bitte 2-3 solche Strukturen. Mir wollen keine einfallen.
Einfallen tun mir allerdings ne Menge Dinge, die ich plötzlich machen muss, wenn ich Tests und Impl im selben Projekt habe. Woran man dann plötzlich denken muss…
Und das Argument mit der Projektanzahl in VS: Wer immer noch Dutzende Projekte in Sln stecken hat, der sollte mal überlegen, ob diese Praktik nicht eines der Wurzelprobleme für die ganze Diskussion ist.
-Ralf
July 2nd, 2010 at 11:35
@Ralf: Guckst du z. B. hier: http://github.com/agross/netopenspace/tree/master/source/test/
Ich “muss” auch mit zwei getrennten Projekten an meine Konventionen denken. Nenn es von mir aus Disziplin, damit hab ich kein Problem.
Den zusätzlichen Schnipsel im Build Skript zur “Nullifizierung” der Specs füge ich durchaus gern ein, denn er löst mein Problem ein für alle Mal.
July 2nd, 2010 at 11:51
@Alexander: Ich versteh dich grad nicht. Ich bin für 2 Projekte. Du zeigst mir Beispiele von Tests, die getrennt von der Impl sind. Das find ich gut.
Und was ist mit deinen Strukturen, die du nicht doppelt halten willst und die dir nahelegen, Tests und Impl im selben Projekt zu halten? Du sagst ja, für dich entstünden Friktionen.
Und was die “Nullifizierung” von Specs angeht: Wenn du über ganze Projekte mit reinen Specs redest, dann reden wir aneinander vorbei. Wenn du über Specs in einem Impl-Projekt redest, dann gibt es für mich keine Automatik, deren Artefakte zu “nullifizieren”. Denn das kann alles mögliche sein.
July 2nd, 2010 at 11:57
@Ralf: Du fragtest nach einem Beispiel welches die Reibung enthält von der ich weiter oben sprach. Das NOS-Repository enthält diese Reibungspunkte (2 Projekte, duplizierte Struktur, keine Lokalität von Spec und Implementierung).
Das Auf-0-Bytes-Setzen von **/*Specs.cs betrifft in dem in Stefans Post gemachten Vorschlag Test und Implementierung in einem Projekt vorzuhalten. Wir stimmen sicher darin überein, dass wir dem Kunden keine Tests ausliefern. Deshalb müssen diese vor der Kompilation raus – indem die Files mit Specs einfach geleert werden.
July 2nd, 2010 at 12:08
@Alexander: Welche Struktur duplizieren deine Tests, die bei Kolokation nicht dupliziert werden müsste? Das (!) verstehe ich nicht. Bennen die doppelten Strukturen mal.
Dass deine Tests irgendwo in der Gegend stehen (“keine Lokalität”) hat ja damit zu tun, wie du deinen Code ansonsten anordnest. In meinen Lösungen stehen Impl und Test Projekt direkt nebeneinander – sogar freigestellt von anderen. Ich fokussiere mich in einer Komponentenwerkbank voll auf eine Implementation – mit all ihren Tests, z.B. Unit, Integration, Performance…
Das 0-Setzen von Testklassen mag für dich technisch einfach sein. Aber bitte realisiere: Auch dieser kleine Aufwand ist ein Folgeaufwand. Er ist nur zu treiben, wenn überhaupt Tests und Impl im selben Projekt liegen.
Wenn ich Letzteres nicht tue, dann spare ich mir autom. Ersteres. (Davon mal abgesehen, dass es eben nicht mit 0-Setzen von ein paar Testdateien getan ist.)
Die ganze Diskussion ist für mich schlicht überflüssig. Und auch wenn ich mich wiederhole: Wo (!) sind die großartigen zu duplizierenden Strukturen?
-Ralf
PS: Für Leute, die gern so handeln wie die “großen Weisen” aus dem Abendland es empfehlen, sei angemerkt, dass selbst Neal Ford sagt, dass Tests “moist” sein dürfen (nein, sogar sein müssen).
July 2nd, 2010 at 12:17
Ich kann beide Seiten verstehen. Vielleicht gibt es Projekte bei denen es Berechtigt ist, Tests bei der Implementierung zu halten.
Ich persöhnlich habe es bisher als hinderlich empfunden Test-Assemblys direkt im Projekt zu referenzieren.
Die Physikalische Verbundenheit zwischen Produktivcode und Tests ist mir zu hoch (nicht alleine wegen den Prinzipien), sonder weil es für mich hinderlich ist, Test bei reimplementierung, bzw. Portierung erneut zu schreiben.
Ich experimentiere gerne mal herum ohne mich gleich mal ans Planboard zu setzen um zu sehen, wie sich Code anfühlt. Einige nutzen Pythen, Ruby für solches Prototyping, ich nutze auch gerne UnitTests, da sich dadurch meine Spec gleich mitzeichnet.
Der Vorteil der physikalischen Trennung ist für mich auch, dass ich die Tests an einen Spezialisten weiter geben kann der mir eine Optimierte Version meines Moduls erstellt. Das mag bei Open Source kein Problem sein, aber Closed Source schon eher.
Für mich hat das Trennen der Test neben SoC ganz klar einen praktischen nutzen. Wo ich jedem meine voll Zustimmung gebe, ist die Zuordnung von / physikalische nähe von Produktiv- und Testcode. Aber hier könnte ich mir vorstellen, durch eni Plugin per Konvetion (Attribut) Abhilfe zu schaffen. Ähnlich wie es das schon mit dem gehe zu Definition gibt, oder “finden Referenzen” gibt, nur eben geschmeidiger per shortcut.
Ich bin sicher: Keiner von euch würde gerne seine Bratwurst im Bierkrug serviert bekommen, sonder beides lieber getrennt genießen (auch wenn es auch im Mund zusammen kommt).
July 2nd, 2010 at 13:32
@Ralf: Warum heißt denn SoC das man in 2 Projekte separieren sollte?
Selbst in einem Projekt sind die Dinger getrennt? Wo zieht man die Grenze? Warum nicht in gleich in Solutions trennen oder 2 Repositories?
@Stefan: Mich würde interessieren, wie du über die Sache denkst wenn du Side-by-Side Specification etwas länger in der Praxis benutzt hast. Immerhin hast du dir die Mühe des Blogartikels gemacht und jetzt schwenkst du so schnell wieder zurück? Selbst für FinalBuilder sollte sich doch ein Pre-Compile-Task bauen und dann in der Community sharen lassen.
Die Lokalität von Testdaten, Tests und Implementierung sind aus meiner Sicht echt etwas sehr hilfreiches. Schließlich machen wir die Tests ja auch zur Dokumentation und nicht nur zum Testen.
July 2nd, 2010 at 13:51
@Steffen: Natürlich gibt es keine Pflicht, wg SoC auch zwei Projekte zu benutzen. Aber wenn ich schon normalerweise Concerns in unterschiedlichen Projekten/Assemblies realisiere, dann liegt das für Tests nahe.
Aber da kommen wir auf Grundsätzlicheres. Ich denke halt komponentenorientiert. Da habe ich auch nie den Fall, dass ich Sln mit 20, 30, 80 Projekte habe. Ich nutze halt die physischen Strukturierungsmöglichkeiten von .NET Fx und VS: Methode, Klasse, Projekt, Solution Folder, Solution.
Wer das nicht mag und lieber alles in eine Sln packt und da möglichst alles in ein Projekt… nun, der kann das machen. Die Konsequenz: Beim Buildprozess Artefakte rausfiltern, die nicht zur Impl gehören.
Wo das Verständnisproblem liegen soll oder das Strukturdopplungsproblem, wenn ich 2 Prj in 1 Sln habe, die Impl und Test trenne, verstehe ich aber immer noch nicht.
Mein Gefühl: Wir sind im Land der Geschmäcker und der Schmerzempfindlichkeit angekommen. Der eine mag es so, der andere anders. Der eine hält seinen Schreibtisch übersichtlich, der andere bildet wilde Haufen.
Wenn man meinen Argumenten auf der Basis von SoC, SRP, BoPO und KISS nicht folgen mag, dann ist das ok. Aber bitte, bitte rede man mir nicht ein, dass die Zusammenfassung “zeitgemäß” sei oder “selbstverständlich ist, weil irgendwie Test und Impl ja zusammen gehören”.
“Evident”, “zeitgemäß”, “selbstverständlich” etc. sind keine Argumente. Das sind Geschmacksurteile. Ich warte daher noch auf die Argumente der Freunde der Zusammenlegung. Zeigt mir die Strukturen, die sich sonst verdoppeln. Zeigt mir den Mehraufwand, den man ständig treiben muss, wenn man zwischen Impl und Test Projekt hin und her springt während TDD.
Exzessive Feuchtigkeit (anti-DRYness) oder echten Mehraufwand z.B. bei der Navigation oder Refaktorisierung aufgrund (!) von zwei Projekten nehme ich gern als Argumente gegen meine Position, die prinzipienorientiert ist. Wer macht den Anfang in der Demonstration?
-Ralf
July 2nd, 2010 at 14:36
@Steffen Was ich bei meinem ersten Versuch übersehen habe:
- Überflüssige Referenzen auf NUnit/RhinoMock müssen entfernt werden
- Wohin mit Testdaten? Eingebettete Dateien kommen da schon ab und zu vor.
- So landet alles in ein und demselben bin Verzeichnis
- Für das Entfernen der Testklassen habe ich, außer Dateinamenskonvention, keinen konkreten Vorschlag gehört. Und eine solche Konvention engt mich zu sehr ein.
Das sind gewichtige Argumente _gegen_ side-by-side Tests. Deshalb werde ich die Idee nicht aktiv weiter verfolgen.
Und nochmals: ich entwickle wie Ralf komponentenorientiert. Jede der Solutions ist da seeeehr überschaubar, weil sie nur aus 2 Projekten besteht, die dazu noch wenige Klassen enthalten. Dadurch liegen Test und Implementierung schon sehr eng beisammen.
Der Aufwand für den Blogpost hat sich gelohnt. So schnell zu Erkenntnis kommen ist doch toll! Also nochmals Danke an alle für den Input!
Grüße
Stefan
July 2nd, 2010 at 15:20
Hi Stefan,
mal ehrlich das Entfernen von Referenzen usw. sind doch technische Kleinigkeiten, die “irgendwer” einmalig in das Buildframework deiner Wahl einbauen muss. Damit will man sich nicht rumschlagen, das ist klar – aber das ist doch kein Argument gegen Side-By-Side Specification.
Und wieso schränken dich Dateinamenskonventionen ein?
Laut eurem CCD-Orange erkennt ihr Konventionen doch auch schon in “niedrigen” Graden als wichtig an. http://clean-code-developer.de/wiki/CcdOrangerGrad#SourceCodeKonventionen
Warum also nicht auch Konventionen für TestDaten und Testklassen verwenden?
Grüße Steffen
July 2nd, 2010 at 15:31
@Steffen: Im Sinne von KISS halte ich dagegen: Warum überhaupt Zusatzaufwand treiben beim Build-Prozess? Warum überhaupt Konventionen zur Herausfilterung von Tests?
Was technisch machbar ist, muss nicht unbedingt getan werden.
Und den Vorteil von Side-by-Side-Specs im selben (!) Projekt hab ich immer noch nicht verstanden. Bzw. bist du bisher schuldig geblieben deutlich zu machen, wo der verwirrende, lästige, prinzipienwidersprechende Mehraufwand liegt bei getrennten Projekten.
Wenn du dich drauf zurückziehst, dass es dir nur Side-by-Side schmeckt – dann ok. Dann sag ich Mahlzeit
Falls du jedoch überzeugen möchtest, dann versuch es nochmal mit Argumenten für die bisher von dir und Alex genannten Vorteile. Technisch Machbares ist leider kein Argument.
-Ralf
July 2nd, 2010 at 20:44
Um nochmal auf meine Bier/Bratwurst Metapher zu kommen. Also ich programmiere ja zur Zeit bekannter Maßen in Python. Hier programmieren auch viele ihre Tests in die GLEICHE! Datei. In Python entspricht eine Datei einem Modul, welches in etwa einem Namespace quasi also einem Import in C# gleicht. Das halte ich für unsinnig. Eine Assembly entspricht hier einem Package (also einem Ordner). Meine Specs kommen Momentan in ein separates Modul (Sollte mal überlegen sie evtl. in ein separates Package zu schmeissen. Naja, das ist hier aber nicht so wild.
Die Lösung Specs side-by-side zu lokalisieren finde ich gut, aber ich halte es für Unglücklich sie durch Kompilertasks physikalisch herausoperieren zu müssen. Ich fände es auch schön, in dem Projektexplorer zu den Tests navigieren zu können (diese Funktion gefällt mir übrigens am R# so gut, das man so gut navigieren kann. Das Problem ansich ist erst einmal, dass die Exploreransicht ein Abbild des Projekt und Solutionfiles ist. Es wäre von Vorteil, wenn die Exploreransicht eine Projektion wäre, die man mit verschiedenen Datenquellen ausstatten kann. Das wäre zum einen das Solutionfile, zum anderen eben n zusätzliche Date / Meta-daten, die nicht den Kompiler instrumentieren und in das Projekt einfliessen.
Dadurch hätten wir eine Kolokation mit physikalischer Trennung. Im Endeffekt ist das das gleiche Problem, wie bei Sourcecode. Wir haben Dokumentation und Kommentare im Quellcode. Meiner Meinung nach sind das Concerns, die zwar an Ort und Position sinnvoll sind, aber physikalisch nichts damit zu tun haben. Also es ist das Gleiche, wie mit den Test. Nehmen wir als Beispiel D von Digital Mars. Die Jungs haben hier die Testsyntax zum Bestandteil der Sprache gemacht. Ist ok, gehört aber nicht ins Binary zum ausliefern. Kann mir gut vorstellen das herausoperieren zum Compilertask zu machen (in unserem Fall ein PreCompile Task, weil es der Compiler ja selbst nicht kann).
Meiner Meinung nach ist der Schlüssel für unsere Probleme und der SoC eine andere Strukturierung und Instrumentierung. Bis wir dort angekommen sind, sind wir eben auf andere “Tricks” angewiesen. Die Lösung heißt für mich Abstract Syntax Tree, oder Intentional Tree oder wie auch immer ihr es nennen wollt. Compiler as a Service. Quellcode wird nicht als Textdatei gespeichert, sonder als Bamstruktur. Wir bestimmen was die zutaten für ein Projekt sind und was nicht. Das wäre für mich mal AOP bzw. SoC auf Sourcecode angewendet.
Code Bubbles finde ich eine spannende Projektions- und Stukturierungsmöglichkeit für Code. Das Symptom ist meiner Meinung nach die Art und Weise wie wir unseren Code persistieren und Verwalten.
Bei Business Applikation machen wir SoC nach Model View Controller und Konsorten. Da müssen die IDE und Compilerhersteller noch nacharbeiten. Vermischung von Persistenz und Ansicht. Pfui, das stinkt.
Was haben wir momentan? Erst vermischen, dann auseinander fummeln. Hmm hört sich unnötig an. Stimme da Ralf zu! Wir haben aber aus meiner Sicht ein anderes Problem, wo ich Alex und Steffen zustimme. Das Stichwort heißt hier für mich Traceability. Leichte Nachvollziehbar- und -verfolgbarkeit.
Jetzt habe ich mal jedem Honig um den Mund geschmiert. Ich verstehe wie gesagt beide Argumentationen, aber das Symptom ist wie ich hoffentlich schildern konnte aus meiner Sicht ein anderes. Und das gilt es hoffentlich anzupacken.
July 3rd, 2010 at 7:29
Ist heute wieder heiß. Vielleicht liegt es daran. Aber verstehen tun ich die ganze Diskussion nicht mehr so recht. Auf neue Tools warten, intentional programming…
Ich möchte nochmal drum bitten mir möglichst lebhaft zu beschreiben, was an dieser Codeorganisation hier (http://bit.ly/bxaK5w) nicht Side-by-Side ist. Das sind die einzigen Projekte in der Solution. Und wenn ich noch andere Tests bräuchte (z.B. Performancetests), dann würde ich ein weiteres Projekt in der Sln anlegen.
Mir scheint die Diskussion getrieben von zweierlei:
1. Brownfieldcode: Schon 30 Projekte in der Sln. Da macht es keinen Spaß, nur den Testcode für irgendeine Klasse in ein 31. Projekt zu stecken.
2. Technikverliebtheit: Es macht mehr Spaß, darüber nachzudenken, ob wir den Buildprozess verändern können, als die Architektur/Codeorganisation zu verbessern.
Wenn ich damit auch nur halbwegs richtig liege, dann kann ich nur sagen: Get real! Packt das Wurzelproblem an.
Testcode im Projekt des SUT oder nicht? Das ist egal. Ihr habt ein anderes, ein viel größeres Problem. Und der Widerstand gegen SoC und SRP und BoPO ist ein Symptom dieses größeren Problems.
July 3rd, 2010 at 14:25
Deine Organisation ist Side-By-Side, aber super unprakisch, wenn du 300-500 solcher Solutions pflegen und refaktorisieren musst. Willst du 300 mal Visual Studio parallel aufmachen. (Ich weiß das ist jetzt leicht überspitzt).
Das Problem ist wie du richtig erkennst, nicht die Side-By-Side lokation, sondern die Art und Weise, wie du uns sagen willst, dass wir Komponenten prorgammieren sollen. Nämlich jede in einer Solution. Halte ich für nicht praktikabel, da zu aufwendig. Mir ist das persöhnlich zu umständlich. Die Trennung ist schön, aber der Wartungsaufwand ist es nicht.
Bitte, bitte, das Problem ist die Art und weise, wie mit Quellen und ihren Abhängigkeiten umgegangen wird. Dein Ansatz ist hinsichtlich der Organisation gut, ich persönlich werde davon nie ein Freund werden, weil er im Alltag ein Hemmnis ist. Läßtige Mehrarbeit. Damit meine ich:
Referenz von 10, 20, 30, 40, 100, 200 Komponenten in einem Projekt setzen. Oh, da ist ein Fehler, Signatur von einem Interface muss geändert werden. Mißt, ich brauch eine neue Version vom Interface, wieder 10, 20, 30 Projekte ändern.
Der FizzBuzz Micro-Organismus ist schön für deine Modellwelt. Aber wie war das. Get Real! Pack das Problem an der Wurzel an. Wo bleiben denn die Business Apllikationen, mit 300 Komponenten und aufwärts. Bitte jetzt nicht ernst nehmen, ich wollte dir nur deine Provokation spiegel. Die ist unnötig.
Vielleicht fehlt mir einfach die notwendige Disziplin mir eine solche Struktur anzueignen. Denn die ist hier wirklich notwendig.
Ich bin überzeugt, dass SoC and Friends besser von der Hand gehen würden, wenn das Problem (aus meiner Sicht die Wurzel), nämlich wie Quellcode dargestellt und gespeichert wird, angepackt werden. Das sind keine ausreden, dass sind meine Erfahrungswerte.
Kommentare im Quellcode sind so ziehlich das dämlichste, was es gibt. Denn sie machen den Code nicht funktionaler. Ich weiß, selbsprechender Code mit Variablen. Das mache ich konsequent. Aber manchmal und das wissen wir, sind Kommentare ein Segen, weil notwendiges Hintergrundwissen transportiert werden muss ,z.B. warum das gerade so gemacht wird – das wie sehen wir ja offensichtlich am Code.
Kommentare, Tests, Konventionen. Alles Belange, die den Code nicht funktionaler machen. Den wichtigsten habe ich noch vergessen. Traceability. Wenn ich nicht nachvollziehen kann wo die ganze FizzBuzz magic herkomt und was ich damit anfangen kann, dann fühle ich mich ziehmlich als Einzelkämpfer im Urwald.
Du machst Solutions pro Kompontene um den lesbaren Inhalt und damit dei Komplexität niedrig zu halten. Die wird aber nicht besser, wenn du 500 solcher Asemblies zusammen schraubst. Damit hast du das Problem nur verlagert. Was hast du gewonnen? Physikalische Trennung. Das ist super. Aber nicht die Lösung, nur eine Annäherung.
Steffen und Alex haben es schon gut gemacht, wenn sie die Tests dort unterbringen, genau wie du. Du hast sie auch Side-By-Side untergebracht. Ich glaube ihr Weg ist in der Realworld weniger aufwendiger und geht dem Entwickler leichter von der Hand, denn im Compilertask muss das einmal gemacht werden. Die Trennung von 500 Komponenten musst du 500 mal machen. Ich persöhnlich mache im Moment wie du Side-By-Side in einzelnen Projekten, aber nur, weil es noch nicht besser geht. Dabei kommt aber bei mir nicht jede Komponenten in eine Solution, da sind dann gut und gerne auch 20-30 drinnen. Und dann ist die Kolokation nich mehr gegeben. Dann muss umständlich (und mit umständlich meine ich 3 Klicks 5 mal Scrollen, das ist schon umständlich genug um wirklich nach ein paar Wochen so genervt zu sein um lieber eine Unterbringung direkt in der Nähe zu haben.
Vielleicht sollte ich jetzt mal aus der Sonne gehen um meinen Sonnenstich zu Kühlen um nicht so viel wirres Zeug zu reden. Vielleicht gefällt mir die Lösung mit Kühlem Kopf dann besser.
July 3rd, 2010 at 17:29
@Rainer: 300-500 Solutions? Wo ist das Problem? Vor allem: Was ist die Alternative? 1 Solution mit 300-500 Projekten? Oder 5 Solutions mit jeweils 50 Projekten? Wie du es drehst und wendest: Wenn du von 300-500 Solutions ausgehst, dann musst du eine Anwendung von, hm, mehreren Tausend Klassen im Sinn haben.
Und deshalb frage ich wieder: Du hälst es aus Gründen der Refaktorisierung für am besten, die in sowenigen Solutions und Projekten wie möglich zu pflegen?
Jetzt komm ich mal mit Automatisierung: Wenn du denn echt massiv refaktorisieren musst, dann ist es kein Problem, genau dafür dir automatisch die eine riesige Solution basteln zu lassen. Darin refaktorisierst du, was das Zeug hält. Und dann schmeißt du sie wieder weg.
Weitere Arbeit findet dann wieder in kleinen, überschaubaren, fokussierten Komponentensolutions statt. Die darfst du sogar in einer Folderhierarchie verwalten, wenn du magst
Ich behaupte bis zum Beweis des Gegenteils: Du hälst eine große Zahl für Solutions für impraktikabel, ohne es wirklich ausprobiert zu haben. Schade. Du hättest dich fürs CCD Praktikum bewerben können, um mal eine andere Sichtweise zu bekommen. Denn dein Erlebnis:
“Referenz von 10, 20, 30, 40, 100, 200 Komponenten in einem Projekt setzen. Oh, da ist ein Fehler, Signatur von einem Interface muss geändert werden. Mißt, ich brauch eine neue Version vom Interface, wieder 10, 20, 30 Projekte ändern.”
ist symptomatisch für ein bestimmtes Vorgehen. Das beißt die Maus keinen Faden ab. Ich referenziere nie 200 Komponenten. Und neue Versionen von Interfaces werden autom. von CI eingebunden. Wenn dann natürlich etwas bricht… nun, dass hast du Exzessive Abhängigkeiten. Wieder ein Wurzelproblem.
Du vermutest: “Vielleicht fehlt mir einfach die notwendige Disziplin mir eine solche Struktur anzueignen” Und ich sag mal: Ja, könnte sein. Warum versuchst du dann nicht mal, eine andere Erfahrung zu machen. Vielleicht ist es gar nicht so schlimm?
Über welches Problem sprichst du hier? “Damit hast du das Problem nur verlagert. Was hast du gewonnen?”
Was ich gewinne, wenn ich 500 Assemblies “zusammenschraube”, das kann ich dir sagen: Ich habe erstens Werkbänke, bei denen ich mich auf 1 Werkstück konzentriere, wenn ich dran sitze. Stichwort: Fokus. Die Literatur ist da eindeutig, dass Fokus wichtig ist. Und das zeigt sich nicht nur, wenn wir dauernd unterbrochen werden. Es zeigt sich auch bei dem Rauschen, dass wir uns selbst basteln mit Kommentaren oder riesigen Solutions.
Zweitens habe ich durch die Form erzwungen isolierte Einheiten. Ich kann nicht anders entwickeln als entkoppelt. Das trägt enorm zur Evolvierbarkeit bei.
Drittens habe ich durch die Isolierung in Werkbänke quasi einen Zwang, automatisiert zu testen. Mir steht nämlich mit F5 kein Programm zur Verfügung, durch das ich mich durchklicken kann. Das trägt zur Korrektheit bei.
Viertens habe ich durch eine Organisation der Arbeit um Werkbänke herum weniger Kollisionen beim Mergen von Entwicklungsästen im Repo. An einer Sln sitzt ein Entwickler. Wenn er fertig ist, pusht er sein Ergebnis ins zentrale Repo. Konflikte sind nicht zu erwarten. Das steigert die Produktionseffizienz.
Fünftens bekomme ich mit Werkbänken autom. Einheiten, an denen echt parallel gearbeitet werden kann. Das steigert die Produktionseffizienz.
Noch Fragen?
Und was steht dagegen, wenn ich nur 1 Sln für alles habe? Traceability? Get real, die ist nicht besser, wenn alles in einem Klumpen vermatscht ist. Allemal nicht, weil da wahrscheinlich viel weniger SRP und SoC eingehalten sind.
Ich habe 10 Jahre lang Branchensoftware geschrieben. Genauso, wie du es darstellst. Alles hübsch zusammengepappt. Das hat – sorry to say – keinen Vorteil. Keinen. Heute verstehe ich nicht mehr, wie ich das ausgehalten habe.
Leider fällt mir nicht mehr dazu ein, als das diejenigen, die sich die riesigen Sln schönreden eben nur das tun: sie sich schönreden. Das ist Rationalisierung. Man versucht, einem Problem (dessen man sich bewusst ist oder auch nicht) eine rationale Erklärung zu geben. Es ist gut so oder zumindest kann es leider nicht anders sein weil…
Ich sehe vor allem ein Problem: Die vorhandene Codebasis ist vielfach einfach grottig. Da ist Side-by-Side Impl/Test in zwei Projekten in einer extra Sln gar nicht möglich. Das verstehe ich. Da kann ich mitfühlen.
Aber, bitte, aus diesem So-Sein leite man bitte kein Sollen ab. Und auch das Warten auf das bessere Tool halte ich für keine gute Lösung. Heilsbringer lassen gewöhnlich auf sich warten, derweil man hienieden weiter leidet.
Wir können bei nem Bierchen über Intentional Programming, DSLs, andere Repräsentationen von Software gern plaudern. Ich bin auch der Meinung, dass das Speichern von Code als Text anachronistisch ist. Aber so isses halt grad mal. Wir müssen´s Beste draus machen. Und das ist – so denke ich – eben nicht, alles in einer Sln zusammenzuwürfeln. Das ist nur der default. Mehr nicht.
Aber nun ist das Thema wohl ausgelutscht. Jeder schreibt weiter seine Tests, wo er mag. Schön, dass wir drüber gesprochen haben
July 5th, 2010 at 6:05
[...] Wohin mit den Tests? unbedingt auch die comments lesen var flattr_wp_ver = '0.9.11'; var flattr_uid = '20870'; var flattr_url = 'http://www.kreiseder.at/2010/07/random-links-218/'; var flattr_btn = 'compact'; var flattr_hide = 0; var flattr_lng = 'de_DE'; var flattr_cat = 'text'; var flattr_tle = 'Random Links #218'; var flattr_dsc = '5 Tips for Starting a Fresh Work Week Not only for Mondays Agile Team Meets a Fixed Price Contract or being agile in a non agile environment? Wohin mit den Tests? unbedingt auch die comments lesen'; var flattr_tag = 'agile,gtd,scrum,tdd'; [...]
July 5th, 2010 at 7:43
Meine Antwort kennst du… Es ist Aufgabe des eingesetzten Tools zum Build-Management (zB Maven), die Tests entsprechend zu verwenden:
- Ausgeliefert wird ohne Tests
- Für das Ausführen der Tests wird beides zusammen kompiliert (aka “Test-Artefakt”)
July 5th, 2010 at 11:33
@Ralf: Was heißt hier Zusatzaufwand im Build-Prozess? Momentan habe ich ja schon eine Konvention (auf Folder-Ebene) die dafür sorgt, dass die Testprojekte nicht deployed werden. Die tausche ich gegen eine Konvention die Test-Dateien nicht für das Release kompiliert. Aus meiner Sicht kein Komplexitätszuwachs fürs Buildscript – im Gegenteil.
Und “überzeugen” will ich dich nicht. Wenn die genannten Sachen für dich keine Vorteile sind, dann ist das OK. Dann denkst du halt dass wir “wilde Haufen” auf unserem Schreibtisch machen.
Grüße Steffen
July 12th, 2010 at 0:16
[...] been something like a week since Stefan asked a provoking question in his blog (german): „Where to put the Tests?” He wrote that he’s strictly separating tests from their implementation in separate projects. [...]
July 12th, 2010 at 0:20
[...] knapp einer Woche stellte Stefan die Frage „Wohin mit den Tests?“ gestellt. Er selbst schreibt, er trenne die Implementierung und deren Tests strikt, ließ sich [...]