Eben in dem Blog von Keyvan Nayyeri gelesen:
Unit Testing is Not Always Good
You may be interested to know that how much of unit testing is done for Waegis. Actually I started the development with heavy unit tests for almost everything. Waegis has a core platform that I tried to develop with the best quality and accuracy so I unit tested it carefully. After 1-1.5 months of development I abruptly believed that this project is going to take much longer than my initial estimation. Normally taking longer wasn’t bad but for a self-managed project this could cause to failures so I had to make a new decision.
My decision was to limit my unit tests to some critical parts of code. Based on experience and background these parts are easy to recognize so I chose this approach and this helped me to speed up my development and bring it to the web finally!
Unit testing is great and I’m a big fan of it but this time I had to reduce and limit it in order to get the point. All the proven methodologies, software development processes and practices are there to teach you how to choose the best way so don’t restrict yourself with them!
more...
Ich lese den Blog von Keyvan schon eine Weile. Er ist nicht nur Autor von einigen Büchern im .NET Developmentbereich, sondern auch ein Tool-Developer für verschieden Software. Ich selber nutze seine GraffitiCMS Extras auf diesem Blog.
Keyvan schreibt in seinem Post über seine Erfahrung im konsequenten Einsatz mit Unit-Testing. Er spricht mir aus der Seele. Es es wichtig es zu tun, aber es ist noch wichtiger es zu unterlassen, wenn es sinnvoll und vertretbar ist. Wie beim Designen von Datenbanken, kommt es nicht auf die tausendste Table an, um die vierte Normalvorm zu erreichen. Es ist wichtiger das Ziel vor Augen zu haben und dieses mit vertretbaren Mitteln zu erreichen.
Ich habe zu ".NET 1.1 Zeiten" mit nUnit und Plugins für Visual Studio begonnen. Beim Wechsel auf die Visual Studio Version 2005 war dann schon Unit-Testing direkt mit an Board. Seit der Version 2008 können auch Nutzer, der nicht "Team Editionen" auf die eingebauten Funktionen zurückgreifen. Es wurde also erkannt, das es ein wichtiges Feature ist, dass nicht zwingend an der Größe und Professionalität der Entwicklung gebunden ist.
Wofür nutzt man also Unit-Testing? Meine Antwort gleicht der von Keyvan. Die Core Elemente sollten im permanenten Testing stehen. Am "Core" sollte ab einem gewissen Zeitpunkt so oder so keine gravierenden Änderungen vorgenommen werden. Somit hat man je nach Projektgröße eine überschaubare Anzahl an Test-Items. Für diese Fälle rentiert es sich auch die entsprechenden Aufwände zu betreiben, die Umgebung auf Unittesting vorzubereiten. Die Ausgangssituation solle idealerweise immer dieselbe sein, aber gerade im Fall von Datenbank getriebenen System nicht gerade trivial zu erreichen. Ein weiteres Szenario in dem sich Unit-Testing anbietet, ist das frühe Stadium eine Applikation. Wann wird zum Beispiel das erste Mal der Datenlayer beansprucht? Wie testet man die Login-Methode von die ASP.NET Front noch nicht steht? In solchen Fällen behilft sich der Entwickler in der Regel mit einer WinForm Applikation: WindowsFormsApplication1. Nach drei Tagen hat der Knopf "Button 1" die sechste Funktion und ist von vier weiteren Knöpfen mit ähnlichem Schicksal umgeben. Hoffentlich wird dann nicht im Team entwickelt. Chaos ist vorprogrammiert. An dieser Stelle einfach Unit-Testing angewandt and und dann einfach in Ruhe lassen. Das ist kein Code der gewartet werden muss. Bei Konflikten im Refactoring lieber auf eine Migration verzichten.
Intensives Unit-Testing wird gerade in "schnellen" (kurzen wie auch schnell wechselnde Anforderungen) Projekten eher zum Hindernis. Bei einem kleinen Projekt auf einen Change zu reagieren, kann nicht (darf nicht) bedeuten tausend Zeilen Code fürs Testing anpassen zu müssen. Bei einer vernünftigen Anzahl an Szenarien pro Test sind schnell mehr Zeilen Code im Unit-Test als in der getesteten Klasse gezählt. Die Einfachheit einen Test für eine Klasse mit den mitgelieferten Tools zu erzeugen, ist als trügerisch.
Für was nutzt Unit-Testing? Und wie intensiv?
Ciao Marco

Ich kann dir hier nur beipflichen. Aussagen wie "Code-Coverage" sollte über 90% sein halte ich für absoluten Blödsinn. Die Kunst beim Unit-Testen liegt nicht darin möglichst für alles einen Test zu haben, sondern mit möglichst wenig Aufwand eine möglichst hohe STabilität der Anwendung zu erreichen. Un der Grad an Unit-Tests hängt sicher sehr wesentlich von der Art der Software ab. Produkte die tausendfach verkauft werden werden sicher eine höhere Code-Coverage haben als Individualsoftware, die nur für einen Kunden entwickelt wird, eine Berechtigungsverwaltung sollte sicher intensiver getestet werden als ein Modul zur Ausgabe irgendwelcher log-Daten.
Ich kann dir ebenfalls nur teilweise beipflichten - es hängt natürlich stark vom jeweiligen Team ab. Um direkt mal auf den einen Punkt von dir einzugehen:
Du sagst, "Core" Elemente sollte ständig getestet sein - aber wer weiß denn heute, was morgen auch ein Core-Element ist.
Wenn die Anwendung wächst, kann sich der Fokus verschieben. Wenn man da (an den richtigen Stellen) keine Unit-Tests hat und sich die Projektanforderungen ständig verändern, hat man schnell gar keinen Anhaltspunkt mehr, ob deine Anwendung überhaupt noch irgendwie funktioniert.
Insbesondere habe ich hier im Team gemerkt, dass Unit-Tests noch eine "dokumentierende" Wirkung haben - neue Projektmitglieder kann man schnell an den Unit-Tests erklären wie was technisch abläuft - ohne Stundenlang vor der UI zu hängen. Das macht das Einarbeiten sicherer und die neuen Teammitglieder brauchen keine Angst haben, dass sie was gravierende Beschädigen.
Wie Thomas schon sagte: Man muss an den richtigen Stellen die Unit-Tests haben.
Naja, ich kann da nicht wirklich beipflichten.
Welche Stellen sind den die richtigen Stellen die getestet werden müssen?
Natürlich ist es hinderlich wenn man bei einer Änderung plötzlich tausende Zeilen Unit-Test-Code anpassen muss. Aus meiner Sicht sind dann die Unit-Test einfach zu umfassend und ein Test deckt zuviel ab. So dass sich Änderungen an zu vielen Stellen auswirken.
Gerade bei Änderungen bieten einem die Unit-Test halt Sicherheit.
Wow. Danke für die Anzahl an Comments :-) Also dann will ich nochmal Stellung beziehen und meine Position klar stellen :-)
Meine Aufgabe als Software Architect ist unter anderem die Definition der "Core" Elemente. Ich denke wir sind uns alle igrendwie schon einige. Es geht nicht darum, das Projekt mit Code zu fluten. Es geht darum die wichtigen Stellen zu treffen. Was auch klar ist, das bei vielen Projekte die wichtigen Stellen immer wieder neu bewertet werden müssen. Bei einem Projekt über 1 Jahr mit 4 Codern oder gar einm Produkt muss die Code Coverage deutlich höher sein, als bei einer OneManShow über 2 Monate.
@Robert: Der Hinweis zum Einarbeiten der neuen Developer ist sehr gut. Daran habe ich bisher noch nicht gedacht. Es bleibt aber auch hier immer die Frage offen was zeige ich dem neuen? Wahrscheinlich auf jeden Fall den "Core"... und da sind wir wieder bei meiner Aussage :-)
Es bleibt anzumerken, dass ich den Begriff "Core" absichtlich nicht definiert habe, da er in der Tat ohne Projekt nicht definierbar ist.
Danke für die Reaktionen.
Ciao Marco
Da habe ich ganz andere Erfahrungen gemacht. Wenn die Tests richtig, d.h. schlank, einfach und atomar aufgebaut sind, ist es keine grosse Sache, bei Änderungen (auch bei kleinen), die bestehenden Tests auf ihre Gültigkeit zu prüfen und gegebenfalls anzupassen.
Gerade dies ist doch eine grosse Stärke von TDD. Dass du eben Änderungen implementieren kannst, die dann wirklich auch ins Gesamtgefüge passen. Es schadet auch bei kleineren Änderungen nicht, mal kurz nachzuvollziehen, was das für Auswirkungen hat - wär nicht das erste Mal wo etwas kleines was grosses bewirkt ;-)
@Dani:
Die Frage bleibt eben, wie halte ich meine Unit-Test schlank? Würde das nicht bedeuten, das ich eventuell zu wenige Fälle berücksichtige? Was passiert bei der übergabe von NULL? Was bei der Übergabe von DateTime.MinValue? Was passiert wenn ich DBNull.Value übergebe? Es geht garnicht darum an TDD zu zweifeln. Ich bin ein Fan davon, allerdings habe ich es auch schon zu oft falsch angewendet gesehen... nicht nur in meinem Code ;-)
Es geht wie du selber schreibst darum, dass Änderungen am Code nicht zu Fehlern führen. Ein Beispiel: Ein Wrapper für den Versand von Mails wird aufgebaut. Die Methode wird 3 mal überladen.
public void SendMail(string to, string subject, string body)
public void SendMail(string to, string cc, string subject, string body)
public void SendMail(string to, string cc, string bcc, string subject, string body)
Ich weiß... würde man anders lösen, aber irgendwie muss ich ein Beispiel bringen :-) Für alle Methoden schreibe ich Unit-Tests. In den Parametern für to, cc und bcc können jetzt aber mehrere Adressen mit ";" getrennt erscheinen. Mein Unit-Test muss also noch ein weitere Methode abdecken. Für alle Methoden muss nicht nur ein Unit-Test her. Das Prüfen von Emailadressen ist nun alles andere als trivial... einfach mal nach einem Regex dafür suchen... man findet nicht das eine wahre... also muss ich mit vielen Tests arbeiten.
Wenn ich jetzt auf die Idee komme, doch die Implementierung von Strings auf die Übergabe von System.Net.Mail.MailAddressCollection umzustellen, dann muss ich plützlich ne Menge Code anpassen. Wenn das versenden von Mail nur selten genutzt wird und nicht Kern-Bestandteil der Software ist, habe ich mehere Möglichkeiten:
1.) Ich muss meine Arbeit machen und das Projekt abschließen... also ändere ich die Implementierung, kompiliere, ändere alle angemerkerten Stellen und kommentiere die Tests einfach aus.
2.) Ich ändere die Implementierung und passe vorbildlich alle Tests an und habe 1 Stunde für den Codechange benötigt und 4 Stunden für das Anpassen aller Testcases und einem anschließenden Testlauf
3.) Ich ändere die Implementierung und schreibe nur die rudimentärsten Tests (ein Testcase mit einer gültigen Email)
Meiner Meinung sind alle drei wege in Ordung... wenn es zum Projekt passt... was passent ist muss der Chief Softare Architect der Projektes entscheiden ;-)
Ciao Marco
1.) Wie Unit-Tests schlank halten
Hier helfen ein paar wirklich einfache Regeln: Keep it simpel! Musst du beim Test schreiben zu viel Hirnschmalz rein stecken, ist der Test zu komplex. Steck das Hirnschmalz besser in die Definition der Testfälle :) Zweite Regel: Halte die Testfälle atomar. Also teste pro Testfall wirklich nur etwas. Das endet zwar in einer Flut von Fällen, hält das ganze aber einfach. Regeln 2.5: Benamse die Tests sprechend. Das hilft bei der "Flut" :)
2.) Das mit der Email-Validierung
In so einem Falle wäre die Regex-Validierung ja in der Implementation der Methode - was je nach Anforderung sicher kein Klacks ist, aber das hat ja nichts mit den Tests zu tun. Als Testfälle definierst du einfach einige Email-Adressen die du als korrekt eingestuft haben willst, und einige die eben krachen sollen. Das ist Arbeit die du doch so oder so machen musst, wenn du auf der Suche (oder an der Entwicklung) eines entsprechenden RegEx-Ausdruckes bist. So ist's dann aber auch gleich noch dokumentiert.
3.) Und das mit der Schnittstellenänderung
Eine Schnittstellenänderung bedeutet doch immer Aufwand. Auch die Calls müssen ja entsprechend angepasst werden.
Aber wenn Du wirklich 4h brauchst, um die Tests anzupassen, deutet das erstens mal daraufhin, dass deine Tests viel zu kompliziert sind und zweitens, dass du spätestens dann gleich lieber die Tests neu schreibst. Das Schreiben von Tests für eine solche Methode dauert keine 15 Minuten.
Komplizierter ist es natürlich, wenn man entsprechend komplexe Algorithmen testen möchte. Aber auch da gilt, dass der grosse Aufwand die Definition der Tests ist, die Implementation der Tests sollte simpel sein (beruht ja immer auf: gib was rein, erwarte etwas raus). Ein komplizierter Algorithmus gehört aber natürlich getestet - und UnitTests sind ein einfaches Mittel dazu.
@Dani:
Hehe... ich sehe schon wir kommen da nicht auf einen Nenner :-) Ist ja auch nicht schlimm.
Wenn ich meine Test einfach halte stellt sich bei mir die Frage, warum ich Sie dann schreibe? Um die Code Coverage Zahl hoch zu halten? Oder las es mich umdrehen... dein Prinzip Keep It Simple bei der Anzahl an Testfällen, wende ich einfach in speziellen Situationen, einfach auf Tests im allgemeinen an. Nicht immer... nicht pauschal... sondern gezielt.
zu 2.) Es gibt sehr gute Regex Strings. Die Implementierung ist also für mich als trivial zu definieren, besonders wenn es in dem spezielle konstruierten Beispiel eine Nebensache ist. Genau deswegen ist meine Aussage... KISS
Ciao Marco
Der Grund die Tests einfach zu halten, hat nichts mit der Code Coverage zu tun. Einfach != Unnütz ! Der Grund ist vielmehr, das je komplizierter der Test ist, je wahrscheinlicher ist es, dass der Test selbst Fehler beeinhaltet - und das wäre natürlich fatal.
Gezielte Unit Tests sind natürlich auch eine feine Sache und weiss Gott besser wie gar keine! Nur hat das nichts mit TDD zu tun. TDD ist quasi eine Methodik auf Implementations-Ebene. Und Methodiken habe es so an sich, dass sie eine gewisse Konsequenz erfordern - mal so mal so kommt da schlecht.
Ich kann Deine Gedanken und Erfahrungen natürlich nachvollziehen, ich hatte diese auch und habe sie heute noch ab und an. Aber meist läuft es halt darauf raus, dass ich irgendwas nicht so optimal nach TDD-Gedanke umgesetzt habe.
So... Ich will da Dein Blog nicht zuspammen :-) Bei einem Bier wäre die Diskussion interessanter ;-)
Sag bescheid... wenn du mal in der Gegend um Frankfurt bist... ich lade dich auf ein Äppler und wenn es sein muss auch auf ein Bier ein ;-) Gilt auch für den Rest :-)
Ciao Marco
Nachdem ich tatsächlich einige Comments für mein " Unit-Testing "-Beitrag bekommen habe, habe ich eine Funktion aktiviert, die ich besonders schön finde: Bei einem Reply auf ein Comment gibt es eine Mail an den ursprünglichen