Ist es normal, so viel, wenn nicht mehr Zeit mit dem Schreiben von Tests zu verbringen als mit dem eigentlichen Code?

211

Ich finde Tests viel kniffliger und schwerer zu schreiben als den eigentlichen Code, den sie testen. Es ist nicht ungewöhnlich, dass ich mehr Zeit mit dem Schreiben des Tests verbringe als mit dem Code, den er testet.

Ist das normal oder mache ich etwas falsch?

Die Fragen „ Lohnt sich ein Unit-Test oder eine testgetriebene Entwicklung? Wir verbringen mehr Zeit mit der Implementierung von Funktionstests als mit der Implementierung des Systems selbst. Ist das normal? "Und ihre Antworten sind mehr darüber, ob sich das Testen lohnt (wie in" Sollen wir das Schreiben von Tests insgesamt auslassen? "). Obwohl ich davon überzeugt bin, dass Tests wichtig sind, frage ich mich, ob ich mehr Zeit für Tests aufgewendet habe als für den eigentlichen Code, oder ob es nur ich bin.

Gemessen an der Anzahl der Aufrufe, Antworten und positiven Bewertungen meiner Frage kann ich nur davon ausgehen, dass dies ein berechtigtes Anliegen ist, das in keiner anderen Frage auf der Website angesprochen wird.

gefedert
quelle
20
Anekdotenhaft, aber ich finde, ich verbringe ungefähr so ​​viel Zeit damit, Tests zu schreiben, wie Code zu schreiben, wenn ich TDD. Es ist, wenn ich versuche, Tests zu schreiben, nachdem ich mehr Zeit für die Tests aufgewendet habe als für den Code.
RubberDuck
10
Sie verbringen außerdem mehr Zeit mit dem Lesen als mit dem Schreiben Ihres Codes.
Thorbjørn Ravn Andersen
27
Tests sind auch aktueller Code. Sie versenden dieses Teil einfach nicht an Kunden.
Thorbjørn Ravn Andersen
5
Im Idealfall verbringen Sie mehr Zeit mit dem Laufen als mit dem Schreiben Ihres Codes. (Andernfalls erledigen Sie die Aufgabe einfach von Hand.)
Joshua Taylor
5
@RubberDuck: Gegenteilige Erfahrung hier. Manchmal, wenn ich Tests nachträglich schreibe, sind der Code und das Design bereits ziemlich ordentlich, sodass ich den Code und die Tests nicht zu sehr umschreiben muss. Das Schreiben der Tests nimmt also weniger Zeit in Anspruch. Es ist keine Regel, aber es passiert mir ziemlich oft.
Giorgio

Antworten:

205

Ich erinnere mich an einen Software-Engineering-Kurs, in dem einer ca. 10% der Entwicklungszeit für das Schreiben von neuem Code verwendet und die anderen 90% für das Debuggen, Testen und Dokumentieren.

Da Unit-Tests das Debugging und den Testaufwand in (möglicherweise automatisierbaren) Code erfassen, ist es sinnvoll, dass mehr Aufwand in sie fließt. Die tatsächliche Zeit sollte nicht viel länger sein als das Debuggen und Testen ohne das Schreiben der Tests.

Schließlich sollten Tests auch als Dokumentation dienen! Man sollte Unit-Tests so schreiben, wie der Code verwendet werden soll. dh die Tests (und die Benutzung) sollten einfach sein, das komplizierte Zeug in die Implementierung einfließen lassen.

Wenn Ihre Tests schwer zu schreiben sind, ist der Code, den sie testen, wahrscheinlich schwer zu verwenden!

esoterik
quelle
4
Es könnte ein guter Zeitpunkt sein, um zu untersuchen, warum der Code so schwer zu testen ist :) Versuchen Sie, komplexe Multifunktionszeilen, die nicht unbedingt erforderlich sind, wie massiv verschachtelte binäre / ternäre Operatoren, zu "entrollen" ... Ich hasse wirklich unnötige binäre Funktionen / ternärer Operator, der auch einen binären / ternären Operator als einen der Pfade hat ...
Nelson
53
Ich bitte, dem letzten Teil nicht zuzustimmen. Wenn Sie eine sehr hohe Einheitentestabdeckung anstreben, müssen Sie Anwendungsfälle abdecken, die selten und manchmal direkt gegen die beabsichtigte Verwendung Ihres Codes verstoßen. Das Schreiben von Tests für diese Eckfälle ist möglicherweise der zeitaufwändigste Teil der gesamten Aufgabe.
Otto
Ich habe dies an anderer Stelle gesagt, aber Unit-Tests dauern in der Regel viel länger, da der meiste Code einer Art "Pareto-Prinzip" folgt: Sie können ungefähr 80% Ihrer Logik mit ungefähr 20% des benötigten Codes abdecken decken Sie 100% Ihrer Logik ab (dh das Abdecken aller Edge-Fälle benötigt etwa das Fünffache an Unit-Test-Code). Natürlich können Sie je nach Framework die Umgebung für mehrere Tests initialisieren und so den gesamten Code reduzieren, aber auch das erfordert zusätzliche Planung. Die Annäherung an 100% iges Vertrauen erfordert viel mehr Zeit als nur das Testen der Hauptpfade.
Phyrfox
2
@phyrfox Ich denke, das ist zu vorsichtig, es ist eher wie "die anderen 99% des Codes sind Randfälle". Dies bedeutet, dass die anderen 99% der Tests für diese Randfälle bestimmt sind.
14.
@ Nelson Ich stimme zu, dass verschachtelte ternäre Operatoren schwer zu lesen sind, aber ich denke nicht, dass sie das Testen besonders schwierig machen (ein gutes Coverage-Tool wird Ihnen sagen, wenn Sie eine der möglichen Kombinationen verpasst haben). IMO, Software ist schwer zu testen, wenn sie zu eng gekoppelt ist oder von fest verdrahteten Daten oder Daten abhängt, die nicht als Parameter übergeben wurden (z. B. wenn eine Bedingung von der aktuellen Zeit abhängt und diese nicht als Parameter übergeben wird). Dies hängt nicht direkt damit zusammen, wie "lesbar" der Code ist, obwohl natürlich alle anderen Dinge gleich sind, lesbarer Code ist besser!
Andres F.
96

Es ist.

Selbst wenn Sie nur Unit-Tests durchführen, ist es nicht ungewöhnlich, dass mehr Code in den Tests enthalten ist als der tatsächlich getestete Code. Daran ist nichts auszusetzen.

Betrachten Sie einen einfachen Code:

public void SayHello(string personName)
{
    if (personName == null) throw new NullArgumentException("personName");

    Console.WriteLine("Hello, {0}!", personName);
}

Was wären die Tests? Es gibt mindestens vier einfache Fälle, die hier getestet werden müssen:

  1. Der Name der Person lautet null. Wird die Ausnahme tatsächlich geworfen? Das sind mindestens drei Zeilen Testcode, die geschrieben werden müssen.

  2. Der Name der Person lautet "Jeff". Bekommen wir eine "Hello, Jeff!"Antwort? Das sind vier Zeilen Testcode.

  3. Der Name der Person ist eine leere Zeichenfolge. Welche Leistung erwarten wir? Was ist die tatsächliche Ausgabe? Nebenfrage: Entspricht es den funktionalen Anforderungen? Das bedeutet weitere vier Codezeilen für den Unit-Test.

  4. Der Name der Person ist kurz genug für eine Zeichenfolge, aber zu lang, um mit "Hello, "dem Ausrufezeichen kombiniert zu werden . Was passiert? ¹

Dies erfordert viel Testcode. Darüber hinaus erfordern die elementarsten Codeteile häufig Setup-Code, der die für den zu testenden Code erforderlichen Objekte initialisiert, was auch häufig zum Schreiben von Stubs und Mocks usw. führt.

Wenn das Verhältnis sehr groß ist, können Sie in diesem Fall einige Dinge überprüfen:

  • Gibt es Code-Duplikationen zwischen den Tests? Die Tatsache, dass es sich um Testcode handelt, bedeutet nicht, dass der Code zwischen ähnlichen Tests dupliziert (kopiert und eingefügt) werden sollte. Eine solche Duplizierung erschwert die Wartung dieser Tests.

  • Gibt es redundante Tests? Als Faustregel gilt: Wenn Sie einen Komponententest entfernen, sollte sich die Zweigabdeckung verringern. Wenn dies nicht der Fall ist, wird möglicherweise angezeigt, dass der Test nicht benötigt wird, da die Pfade bereits von anderen Tests abgedeckt werden.

  • Testen Sie nur den Code, den Sie testen sollten? Es wird nicht erwartet, dass Sie das zugrunde liegende Framework der Bibliotheken von Drittanbietern testen , sondern ausschließlich den Code des Projekts selbst.

Mit Rauch-, System- und Integrationstests, Funktions- und Abnahmetests sowie Belastungs- und Belastungstests fügen Sie noch mehr Testcode hinzu, sodass Sie sich keine Sorgen machen sollten, vier oder fünf LOC-Tests für jeden LOC-Code zu haben.

Ein Hinweis zu TDD

Wenn Sie sich Sorgen über die Zeit machen, die zum Testen Ihres Codes erforderlich ist, liegt es möglicherweise an Ihnen, dass Sie etwas falsch machen. Das heißt, dass der Code zuerst und später getestet wird. In diesem Fall kann TDD helfen, indem es Sie ermutigt, in Iterationen von 15 bis 45 Sekunden zwischen Code und Tests zu wechseln. Laut den Befürwortern von TDD wird der Entwicklungsprozess beschleunigt, indem sowohl die Anzahl der erforderlichen Tests als auch, was noch wichtiger ist, der Umfang des Geschäftscodes, der zum Testen geschrieben und insbesondere neu geschrieben werden muss, verringert wird .


¹ Sei n sein die maximale Länge eines Strings . Wir können SayHelloeine Zeichenkette der Länge n - 1 aufrufen und als Referenz übergeben, was gut funktionieren sollte. Bei Console.WriteLineSchritt sollte die Formatierung nun mit einer Zeichenfolge der Länge n + 8 enden , was zu einer Ausnahme führt. Möglicherweise führt aufgrund der Speicherbeschränkungen sogar eine Zeichenfolge mit n / 2 Zeichen zu einer Ausnahme. Die Frage, die man sich stellen sollte, ist, ob dieser vierte Test ein Komponententest ist (er sieht aus wie einer, kann jedoch im Vergleich zu durchschnittlichen Komponententests eine viel größere Auswirkung auf die Ressourcen haben) und ob er den tatsächlichen Code oder das zugrunde liegende Framework testet.

Arseni Mourzenko
quelle
5
Vergessen Sie nicht, dass eine Person auch den Namen null haben kann. stackoverflow.com/questions/4456438/…
psatek
1
@JacobRaihle Ich nehme an, @MainMa bedeutet, dass der Wert von personNamein a passt string, aber der Wert von personNameplus der Überlauf verketteter Werte string.
Woz
@JacobRaihle: Ich habe meine Antwort bearbeitet, um diesen Punkt zu erklären. Siehe die Fußnote.
Arseni Mourzenko
4
As a rule of thumb, if you remove a unit test, the branch coverage should decrease.Wenn ich alle vier oben genannten Tests schreibe und dann den dritten Test entferne, verringert sich dann die Abdeckung?
Vivek
3
"lang genug" → "zu lang" (in Punkt 4)?
Paŭlo Ebermann
59

Ich halte es für wichtig, zwischen zwei Arten von Teststrategien zu unterscheiden: Komponententests und Integrations- / Akzeptanztests.

In einigen Fällen ist ein Komponententest erforderlich, der jedoch häufig hoffnungslos übertrieben wird. Dies wird durch bedeutungslose Kennzahlen, die den Entwicklern auferlegt werden, wie "100% ige Abdeckung", noch verstärkt. http://www.rbcs-us.com/documents/Why-Most-Unit-Testing-is-Waste.pdf liefert hierfür ein überzeugendes Argument. Berücksichtigen Sie die folgenden Probleme bei aggressiven Komponententests:

  • Eine Fülle sinnloser Tests, die keinen Geschäftswert dokumentieren, sondern nur existieren, um dieser 100% igen Abdeckung näher zu kommen. Wo ich arbeite, müssen wir Komponententests für Fabriken schreiben , die nichts anderes tun, als neue Instanzen von Klassen zu erstellen. Es wird kein Wert hinzugefügt. Oder diese langwierigen .equals () -Methoden, die von Eclipse generiert wurden - es ist nicht erforderlich, diese zu testen.
  • Um das Testen zu vereinfachen, unterteilen Entwickler komplexe Algorithmen in kleinere testbare Einheiten. Klingt nach einem Sieg, oder? Nicht, wenn 12 Klassen geöffnet sein müssen, um einem gemeinsamen Codepfad zu folgen. In diesen Fällen kann der Komponententest die Lesbarkeit des Codes verringern. Ein weiteres Problem dabei ist, dass Sie, wenn Sie Ihren Code in zu kleine Teile zerlegen, am Ende eine Vielzahl von Klassen (oder Codeteilen) haben, die keine Berechtigung zu haben scheinen, als eine Teilmenge eines anderen Codeteils zu sein.
  • Das Refactoring von umfangreichem Code kann schwierig sein, da Sie auch die Fülle von Komponententests beibehalten müssen, die davon abhängen, dass sie genau so funktionieren . Dies wird durch Verhaltens-Unit-Tests noch verstärkt, bei denen ein Teil Ihres Tests auch die Interaktion der Mitarbeiter einer Klasse überprüft (in der Regel verspottet).

Integrations- / Akzeptanztests sind hingegen ein äußerst wichtiger Bestandteil der Softwarequalität, und meiner Erfahrung nach sollten Sie viel Zeit darauf verwenden, sie richtig zu machen.

Viele Geschäfte haben das TDD kool-aid getrunken, aber wie der obige Link zeigt, zeigen einige Studien, dass sein Nutzen nicht schlüssig ist.

Firtydank
quelle
10
+1 für den Punkt zum Refactoring. Früher arbeitete ich an einem älteren Produkt, das seit über einem Jahrzehnt gepatcht und gepatcht wurde. Nur zu versuchen, die Abhängigkeiten für eine bestimmte Methode zu bestimmen, könnte den größten Teil eines Tages in Anspruch nehmen, und dann herauszufinden, wie man sie verspottet, könnte noch länger dauern. Es war nicht ungewöhnlich, dass für eine Änderung in fünf Zeilen mehr als 200 Zeilen Testcode erforderlich waren und ein Großteil der Woche in Anspruch genommen wurde.
TMN
3
Diese. In MainMas Antwort sollte Test 4 nicht durchgeführt werden (außerhalb eines akademischen Kontexts), da darüber nachgedacht werden sollte, wie dies in der Praxis geschehen würde ... wenn der Name einer Person in der Nähe der Maximalgröße für eine Zeichenfolge liegt, ist ein Fehler aufgetreten. Nicht testen, in den meisten Fällen ist kein Codepfad vorhanden, um dies zu erkennen. Die angemessene Antwort besteht darin, das Framework die zugrunde liegende Ausnahmebedingung "out of memory" auslösen zu lassen, da dies das eigentliche Problem ist.
14.
3
Ich feuere Sie an, bis "Sie diese langwierigen .equals()Methoden, die von Eclipse generiert wurden, nicht mehr testen müssen ". Ich habe ein Testgeschirr für equals()und compareTo() github.com/GlenKPeterson/TestUtils geschrieben. Fast jede Implementierung, die ich jemals getestet habe, fehlte. Wie verwenden Sie Sammlungen , wenn equals()und hashCode()funktionieren nicht richtig zusammen und effizient? Ich feuere den Rest Ihrer Antwort noch einmal an und habe dafür gestimmt. Ich gebe sogar zu, dass einige automatisch generierte equals () -Methoden möglicherweise nicht getestet werden müssen, aber ich hatte so viele Fehler mit schlechten Implementierungen, dass ich nervös werde.
GlenPeterson
1
@ GlenPeterson Zustimmen. Ein Kollege von mir hat EqualsVerifier zu diesem Zweck geschrieben. Siehe github.com/jqno/equalsverifier
Tohnmeister
@ ӍσӍ nein, man muss noch inakzeptable Eingaben testen, so finden die Leute Sicherheits-Exploits.
gbjbaanb
11

Kann nicht verallgemeinert werden.

Wenn ich eine Formel oder einen Algorithmus aus dem physisch basierten Rendern implementieren muss, kann es sehr gut sein, dass ich 10 Stunden mit paranoiden Einheitentests verbringe, da ich weiß, dass der kleinste Fehler oder die geringste Ungenauigkeit dazu führen kann, dass Fehler Monate später kaum zu diagnostizieren sind .

Wenn ich nur einige Codezeilen logisch gruppieren und ihr einen Namen geben möchte, der nur im Dateibereich verwendet wird, kann ich ihn möglicherweise gar nicht testen (wenn Sie darauf bestehen, Tests für jede einzelne Funktion zu schreiben, greifen Programmierer möglicherweise ausnahmslos zurück so wenig Funktionen wie möglich zu schreiben).

phresnel
quelle
Dies ist eine wirklich wertvolle Sichtweise. Die Frage braucht mehr Kontext, um vollständig beantwortet zu werden.
CLF
3

Ja, es ist normal, wenn Sie über TDDing sprechen. Bei automatisierten Tests stellen Sie das gewünschte Verhalten Ihres Codes sicher. Wenn Sie Ihre Tests zuerst schreiben, stellen Sie fest, ob der vorhandene Code bereits das gewünschte Verhalten aufweist.

Das bedeutet, dass:

  • Wenn Sie einen Test schreiben, der fehlschlägt, ist das Korrigieren des Codes mit der einfachsten Funktion kürzer als das Schreiben des Tests.
  • Wenn Sie einen Test schreiben, der bestanden wurde, müssen Sie keinen zusätzlichen Code schreiben, sodass Sie mehr Zeit für das Schreiben des Tests als für den Code aufwenden müssen.

(Dies gilt nicht für Code-Refactoring, das darauf abzielt, weniger Zeit für das Schreiben des nachfolgenden Codes zu verwenden. Es wird durch Test-Refactoring ausgeglichen, das darauf abzielt, weniger Zeit für das Schreiben der nachfolgenden Tests zu verwenden.)

Ja, auch wenn Sie darüber reden, Tests nachträglich zu schreiben, werden Sie mehr Zeit damit verbringen:

  • Ermittlung des gewünschten Verhaltens.
  • Möglichkeiten zum Testen des gewünschten Verhaltens festlegen.
  • Code-Abhängigkeiten erfüllen, um die Tests schreiben zu können.
  • Korrekturcode für fehlgeschlagene Tests.

Dann müssen Sie tatsächlich Code schreiben.

Also ja, es ist eine erwartete Maßnahme.

Laurent LA RIZZA
quelle
3

Ich finde es das Wichtigste.

Beim Unit-Testing geht es nicht immer darum, zu sehen, ob es richtig funktioniert, sondern darum, zu lernen. Sobald Sie etwas ausreichend getestet haben, wird es in Ihrem Gehirn "hartcodiert", und Sie verkürzen schließlich die Zeit für das Testen von Einheiten und können ganze Klassen und Methoden schreiben, ohne etwas zu testen, bis Sie fertig sind.

Aus diesem Grund wird in einer der anderen Antworten auf dieser Seite erwähnt, dass in einem "Kurs" 90% der Tests durchgeführt wurden, weil jeder die Einschränkungen seines Ziels erlernen musste.

Unit-Tests sind nicht nur eine sehr wertvolle Nutzung Ihrer Zeit, da sie Ihre Fähigkeiten buchstäblich verbessern. Sie sind auch eine gute Möglichkeit, Ihren eigenen Code noch einmal durchzugehen und dabei einen logischen Fehler zu finden.

Jesse
quelle
2

Es kann für viele Menschen sein, aber es kommt darauf an.

Wenn Sie zuerst Tests schreiben (TDD), kann es vorkommen, dass sich die für das Schreiben des Tests aufgewendete Zeit zum Schreiben des Codes überschneidet. Erwägen:

  • Ermitteln von Ergebnissen und Eingaben (Parametern)
  • Regeln der Namensgebung
  • Struktur - wo Dinge abzulegen sind.
  • Einfaches altes Denken

Wenn Sie Tests nach dem Schreiben des Codes schreiben, stellen Sie möglicherweise fest, dass Ihr Code nicht leicht testbar ist. Daher ist das Schreiben von Tests schwieriger / dauert länger.

Die meisten Programmierer haben viel länger Code geschrieben als Tests, daher würde ich erwarten, dass die meisten von ihnen nicht so fließend sind. Außerdem müssen Sie mehr Zeit aufwenden, um Ihr Test-Framework zu verstehen und zu nutzen.

Ich denke, wir müssen unsere Denkweise dahingehend ändern, wie lange es dauert, Code zu erstellen und wie Unit-Tests durchgeführt werden. Schauen Sie sich das niemals kurzfristig an und vergleichen Sie niemals nur die Gesamtzeit für die Bereitstellung eines bestimmten Features, da Sie nicht nur berücksichtigen müssen, dass Sie besseren / weniger fehlerhaften Code schreiben, sondern auch Code, der einfacher zu ändern und dennoch zu erstellen ist Besser / Weniger Buggy.

Irgendwann sind wir alle nur in der Lage, Code zu schreiben, der so gut ist, dass einige Tools und Techniken nur so viel zur Verbesserung unserer Fähigkeiten beitragen können. Es ist nicht so, dass ich ein Haus bauen könnte, wenn ich nur eine lasergeführte Säge hätte.

JeffO
quelle
2

Ist es normal, so viel, wenn nicht mehr Zeit mit dem Schreiben von Tests zu verbringen als mit dem eigentlichen Code?

Ja ist es. Mit einigen Einschränkungen.

Erstens ist es "normal" in dem Sinne, dass die meisten großen Läden auf diese Weise funktionieren. Selbst wenn diese Vorgehensweise völlig falsch und dumm war, macht es die Tatsache, dass die meisten großen Läden auf diese Weise funktionieren, dennoch "normal".

Damit meine ich nicht, dass das Testen falsch ist. Ich habe in Umgebungen ohne Tests gearbeitet und in Umgebungen mit Zwangstests, und ich kann Ihnen immer noch sagen, dass selbst die Zwangstests besser waren als keine Tests.

Und ich mache noch kein TDD (wer weiß, ich könnte es in Zukunft tun), aber ich mache die überwiegende Mehrheit meiner Edit-Run-Debug-Zyklen durch Ausführen der Tests, nicht der eigentlichen Anwendung, also arbeite ich natürlich viel auf meine Tests, um so viel wie möglich zu vermeiden, die eigentliche Anwendung ausführen zu müssen.

Beachten Sie jedoch, dass übermäßige Tests und insbesondere der Zeitaufwand für die Wartung der Tests mit Gefahren verbunden sind . (Ich schreibe in erster Linie diese Antwort, um dies ausdrücklich herauszustellen.)

Im Vorwort von Roy Osheroves " The Art of Unit Testing" (Manning, 2009) gibt der Autor zu, an einem Projekt teilgenommen zu haben, das größtenteils aufgrund der enormen Entwicklungsbelastung durch schlecht gestaltete Komponententests, die während des gesamten Berichtszeitraums aufrechterhalten werden mussten, gescheitert war Dauer des Entwicklungsaufwands. Wenn Sie also zu viel Zeit damit verbringen, nichts zu tun, als Ihre Tests beizubehalten, bedeutet dies nicht unbedingt, dass Sie auf dem richtigen Weg sind, denn es ist "normal". Möglicherweise ist Ihre Entwicklungsarbeit in einen fehlerhaften Zustand übergegangen, in dem möglicherweise ein radikales Überdenken Ihrer Testmethode erforderlich ist, um das Projekt zu speichern.

Mike Nakis
quelle
0

Ist es normal, so viel, wenn nicht mehr Zeit mit dem Schreiben von Tests zu verbringen als mit dem eigentlichen Code?

  • Ja zum Schreiben von Unit-Tests (Testen Sie ein Modul isoliert), wenn der Code stark gekoppelt ist (Legacy, nicht ausreichende Trennung von Bedenken , fehlende Abhängigkeitsinjektion , nicht entwickeltes Tdd )
  • Ja zum Schreiben von Integrations- / Abnahmetests, wenn die zu testende Logik nur über GUI-Code erreichbar ist
  • Nein zum Schreiben von Integrations- / Akzeptanztests, solange der GUI-Code und die Geschäftslogik getrennt sind (der Test muss nicht mit der GUI interagieren)
  • Nein zum Schreiben von Unit-Tests, wenn Bedenken bestehen, Abhängigkeitsinjektion, Code wurde testgetrieben entwickelt (tdd)
k3b
quelle