Wie tief sind Ihre Unit-Tests?


88

Das, was ich über TDD herausgefunden habe, ist, dass es Zeit braucht, um Ihre Tests einzurichten, und weil ich von Natur aus faul bin, möchte ich immer so wenig Code wie möglich schreiben. Das erste, was ich zu tun scheine, ist zu testen, ob mein Konstruktor alle Eigenschaften festgelegt hat, aber ist das ein Overkill?

Meine Frage ist, auf welcher Granularitätsstufe schreiben Sie Ihre Unit-Tests?

..und gibt es einen Fall von zu viel testen?

Antworten:


221

Ich werde für Code bezahlt, der funktioniert, nicht für Tests. Meine Philosophie ist es daher, so wenig wie möglich zu testen, um ein bestimmtes Vertrauensniveau zu erreichen (ich vermute, dass dieses Vertrauensniveau im Vergleich zu Industriestandards hoch ist, aber das könnte nur Hybris sein). . Wenn ich normalerweise keinen Fehler mache (wie das Setzen der falschen Variablen in einem Konstruktor), teste ich nicht darauf. Ich neige dazu, Testfehler zu verstehen, daher bin ich besonders vorsichtig, wenn ich Logik mit komplizierten Bedingungen habe. Wenn ich in einem Team codiere, ändere ich meine Strategie, um Code sorgfältig zu testen, den wir gemeinsam tendenziell falsch machen.

Unterschiedliche Personen haben unterschiedliche Teststrategien, die auf dieser Philosophie basieren. Dies erscheint mir jedoch angesichts des unreifen Verständnisses, wie Tests am besten in die innere Codierungsschleife passen, vernünftig. In zehn oder zwanzig Jahren werden wir wahrscheinlich eine universellere Theorie haben, welche Tests zu schreiben sind, welche Tests nicht zu schreiben sind und wie man den Unterschied erkennt. In der Zwischenzeit scheint das Experimentieren angebracht.


40
Die Welt glaubt nicht, dass Kent Beck das sagen würde! Es gibt Legionen von Entwicklern, die pflichtbewusst eine 100% ige Abdeckung anstreben, weil sie denken, dass Kent Beck dies tun würde! Ich habe vielen gesagt, dass Sie in Ihrem XP-Buch gesagt haben, dass Sie sich nicht immer religiös an Test First halten. Aber ich bin auch überrascht.
Charlie Flowers

6
Eigentlich nicht einverstanden, weil der Code, den ein Entwickler erstellt, nicht sein eigener ist und beim nächsten Sprint jemand anderes ihn ändert und Fehler begeht, von denen Sie "wissen, dass Sie das nicht tun". Auch TDD denken Sie zuerst über die Tests nach. Wenn Sie also TDD durchführen und davon ausgehen, dass Sie einen Teil des Codes testen, machen Sie es falsch
Ricardo Rodrigues,

2
Ich bin nicht an Berichterstattung interessiert. Ich bin sehr daran interessiert, wie oft Herr Beck Code festschreibt, der nicht als Antwort auf einen fehlgeschlagenen Test geschrieben wurde.
Sheldonh

1
@ RicardoRodrigues, Sie können keine Tests schreiben, um Code abzudecken, den andere Leute später schreiben werden. Das ist ihre Verantwortung.
Kief

2
Das habe ich nicht geschrieben, sorgfältig gelesen; Ich schrieb, wenn Sie Tests schreiben, die nur einen Teil Ihres eigenen Codes abdecken, und unbedeckte Teile zurücklassen, in denen "Sie wussten, dass Sie keine Fehler machen" und diese Teile geändert werden und keine richtigen Tests haben, haben Sie genau dort ein Problem. und das ist überhaupt nicht TDD.
Ricardo Rodrigues

20

Schreiben Sie Komponententests für Dinge, von denen Sie erwarten, dass sie brechen, und für Randfälle. Danach sollten Testfälle hinzugefügt werden, sobald Fehlerberichte eingehen - bevor das Update für den Fehler geschrieben wird. Der Entwickler kann dann sicher sein, dass:

  1. Der Fehler ist behoben;
  2. Der Fehler wird nicht wieder angezeigt.

Gemäß dem beigefügten Kommentar - Ich denke, dieser Ansatz zum Schreiben von Komponententests könnte Probleme verursachen, wenn im Laufe der Zeit viele Fehler in einer bestimmten Klasse entdeckt werden. Hier ist wahrscheinlich Diskretion hilfreich - Hinzufügen von Komponententests nur für Fehler, die wahrscheinlich erneut auftreten oder deren erneutes Auftreten ernsthafte Probleme verursachen würde. Ich habe festgestellt, dass ein Maß für Integrationstests in Komponententests in diesen Szenarien hilfreich sein kann - das Testen von Code in höheren Codepfaden kann die Codepfade weiter unten abdecken.


Mit der Menge an Fehlern, die ich schreibe, kann dies ein Anti-Muster werden. Bei Hunderten von Tests mit Code, bei denen Probleme aufgetreten sind, kann dies dazu führen, dass Ihre Tests nicht mehr lesbar sind. Wenn die Zeit zum Umschreiben dieser Tests gekommen ist, kann dies zu einem Overhead werden.
Johnno Nolan

@ JohnNolan: Ist die Lesbarkeit von Tests so wichtig? IMHO ist es nicht, zumindest für diese fehlerspezifischen Regressionstests. Wenn Sie Tests häufig umschreiben, testen Sie möglicherweise auf einer zu niedrigen Ebene. Idealerweise sollten Ihre Schnittstellen auch dann relativ stabil bleiben, wenn sich Ihre Implementierungen ändern, und Sie sollten auf Schnittstellenebene testen (obwohl mir klar ist, dass die reale Welt dies häufig nicht ist). t like that ...: - /) Wenn sich Ihre Schnittstellen stark ändern, würde ich es vorziehen, die meisten oder alle dieser fehlerspezifischen Tests zu verschrotten, anstatt sie neu zu schreiben.
j_random_hacker

@j_random_hacker Ja, natürlich ist die Lesbarkeit wichtig. Tests sind eine Form der Dokumentation und ebenso wichtig wie Produktionscode. Ich bin damit einverstanden, dass das Verschrotten von Tests auf größere Änderungen eine gute Sache ist (tm) und dass Tests auf Schnittstellenebene durchgeführt werden sollten.
Johnno Nolan

19

Alles sollte so einfach wie möglich gemacht werden, aber nicht einfacher. - A. Einstein

Eines der am meisten missverstandenen Dinge über TDD ist das erste Wort darin. Prüfung. Deshalb kam BDD. Weil die Leute nicht wirklich verstanden haben, dass das erste D das wichtige ist, nämlich Driven. Wir alle neigen dazu, ein bisschen zu viel über das Testen und ein bisschen zu wenig über das Fahren des Designs nachzudenken. Und ich denke, dass dies eine vage Antwort auf Ihre Frage ist, aber Sie sollten wahrscheinlich überlegen, wie Sie Ihren Code steuern, anstatt was Sie tatsächlich testen. Das ist etwas, bei dem Ihnen ein Coverage-Tool helfen kann. Design ist ein ziemlich größeres und problematischeres Thema.


Ja, es ist vage ... Bedeutet dies, dass ein Konstruktor kein Teil des Verhaltens ist, wir sollten es nicht testen. Aber ich sollte die MyClass.DoSomething () testen?
Johnno Nolan

Nun, hängt ab von: P ... Ein Konstruktionstest ist oft ein guter Anfang, wenn Sie versuchen, Legacy-Code zu testen. Aber ich würde wahrscheinlich (in den meisten Fällen) einen Bautest weglassen, wenn ich anfange, etwas von Grund auf neu zu entwerfen.
Kitofr

Es ist eine getriebene Entwicklung, kein getriebenes Design. Das heißt, Sie erhalten eine funktionierende Basislinie, schreiben Tests, um die Funktionalität zu überprüfen, und treiben die Entwicklung voran. Ich schreibe meine Tests fast immer direkt bevor ich zum ersten Mal Code faktorisiere.
Evan Plaice

Ich würde sagen, dass das letzte D, Design, das Wort ist, das die Leute vergessen, wodurch der Fokus verloren geht. Beim testgetriebenen Design schreiben Sie Code als Antwort auf fehlgeschlagene Tests. Wie viel ungetesteten Code haben Sie, wenn Sie testgetriebenes Design ausführen?
Sheldonh

15

Für diejenigen, die vorschlagen, "alles" zu testen: Stellen Sie fest, dass für das "vollständige Testen" einer solchen Methode int square(int x)etwa 4 Milliarden Testfälle in gängigen Sprachen und typischen Umgebungen erforderlich sind.

In der Tat, es ist sogar noch schlimmer als das: ein Verfahren , void setX(int newX)auch verpflichtet ist , nicht die Werte aller anderen Elemente außer zu verändern x- testen Sie , dass obj.y, obj.zusw. alle bleiben unverändert nach dem Aufruf obj.setX(42);?

Es ist nur praktisch, eine Teilmenge von "alles" zu testen. Sobald Sie dies akzeptieren, wird es schmackhafter, nicht unglaublich einfaches Verhalten zu testen. Jeder Programmierer hat eine Wahrscheinlichkeitsverteilung der Fehlerstellen; Der intelligente Ansatz besteht darin, Ihre Energie auf Testregionen zu konzentrieren, in denen Sie die Fehlerwahrscheinlichkeit als hoch einschätzen.


9

Die klassische Antwort lautet "Teste alles, was möglicherweise kaputt gehen könnte". Ich interpretiere das so, dass das Testen von Setzern und Gettern, die nichts anderes als Setzen oder Erhalten tun, wahrscheinlich zu viel Testen ist, ohne sich die Zeit nehmen zu müssen. Wenn Ihre IDE diese nicht für Sie schreibt, können Sie dies auch tun.

Wenn Ihr Konstruktor, der keine Eigenschaften festlegt, später zu Fehlern führen kann, ist das Testen, dass diese festgelegt sind, kein Overkill.


yup und dies ist eine Bindung für eine Klasse mit vielen Eigenschaften und vielen Konstruktoren.
Johnno Nolan

Je trivialer ein Problem ist (wie das Vergessen, ein Mitglied auf Null zu setzen), desto länger dauert das Debuggen.
Lev

5

Ich schreibe Tests, um die Annahmen der Klassen abzudecken, die ich schreiben werde. Die Tests erzwingen die Anforderungen. Wenn x beispielsweise niemals 3 sein kann, werde ich sicherstellen, dass es einen Test gibt, der diese Anforderung abdeckt.

Wenn ich keinen Test schreibe, um einen Zustand abzudecken, wird er immer später während "menschlicher" Tests auftauchen. Ich werde dann sicherlich eine schreiben, aber ich würde sie lieber früh fangen. Ich denke, der Punkt ist, dass das Testen (vielleicht) mühsam, aber notwendig ist. Ich schreibe genug Tests, um vollständig zu sein, aber nicht mehr.


5

Ein Teil des Problems beim Überspringen einfacher Tests besteht darin, dass durch das Refactoring diese einfache Eigenschaft in Zukunft mit viel Logik sehr kompliziert werden könnte. Ich denke, die beste Idee ist, dass Sie Tests verwenden können, um die Anforderungen für das Modul zu überprüfen. Wenn Sie beim Bestehen von X Y zurückerhalten sollten, möchten Sie dies testen. Wenn Sie den Code später ändern, können Sie überprüfen, ob X Ihnen Y gibt, und Sie können einen Test für A hinzufügen, der Ihnen B ergibt, wenn diese Anforderung später hinzugefügt wird.

Ich habe festgestellt, dass sich die Zeit, die ich während der ersten Entwicklung für das Schreiben von Tests verbringe, in der ersten oder zweiten Fehlerbehebung auszahlt. Die Möglichkeit, Code abzurufen, den Sie seit 3 ​​Monaten nicht mehr angesehen haben, und einigermaßen sicher zu sein, dass Ihr Fix alle Fälle abdeckt und "wahrscheinlich" nichts kaputt macht, ist äußerst wertvoll. Sie werden auch feststellen, dass Unit-Tests dazu beitragen, Fehler weit über die Stapelverfolgung usw. hinaus zu ermitteln. Wenn Sie sehen, wie einzelne Teile der App funktionieren und fehlschlagen, erhalten Sie einen umfassenden Einblick, warum sie funktionieren oder als Ganzes fehlschlagen.


4

In den meisten Fällen würde ich sagen, wenn es dort Logik gibt, testen Sie sie. Dies schließt Konstruktoren und Eigenschaften ein, insbesondere wenn mehr als eine Sache in der Eigenschaft festgelegt wird.

In Bezug auf zu viele Tests ist es umstritten. Einige würden sagen, dass alles auf Robustheit getestet werden sollte, andere sagen, dass für effiziente Tests nur Dinge getestet werden sollten, die möglicherweise brechen (dh Logik).

Ich würde mich mehr auf das zweite Lager konzentrieren, nur aus persönlicher Erfahrung, aber wenn jemand beschließen würde, alles zu testen, würde ich nicht sagen, dass es zu viel war ... ein bisschen übertrieben vielleicht für mich, aber nicht zu viel für sie.

Also, nein - ich würde sagen, es gibt nicht so etwas wie "zu viele" Tests im allgemeinen Sinne, nur für Einzelpersonen.


3

Testgesteuerte Entwicklung bedeutet, dass Sie die Codierung beenden, wenn alle Ihre Tests bestanden wurden.

Wenn Sie keinen Test für eine Eigenschaft haben, warum sollten Sie ihn dann implementieren? Was sollte die Eigenschaft tun, wenn Sie das erwartete Verhalten im Falle einer "illegalen" Zuweisung nicht testen / definieren?

Deshalb bin ich total dafür, jedes Verhalten zu testen, das eine Klasse zeigen sollte. Einschließlich "primitiver" Eigenschaften.

Um diesen Test zu vereinfachen, habe ich eine einfache NUnit erstellt TestFixture, die Erweiterungspunkte zum Festlegen / Abrufen des Werts bereitstellt, Listen gültiger und ungültiger Werte erstellt und einen einzigen Test durchführt, um zu überprüfen, ob die Eigenschaft ordnungsgemäß funktioniert. Das Testen einer einzelnen Eigenschaft könnte folgendermaßen aussehen:

[TestFixture]
public class Test_MyObject_SomeProperty : PropertyTest<int>
{

    private MyObject obj = null;

    public override void SetUp() { obj = new MyObject(); }
    public override void TearDown() { obj = null; }

    public override int Get() { return obj.SomeProperty; }
    public override Set(int value) { obj.SomeProperty = value; }

    public override IEnumerable<int> SomeValidValues() { return new List() { 1,3,5,7 }; }
    public override IEnumerable<int> SomeInvalidValues() { return new List() { 2,4,6 }; }

}

Mit Lambdas und Attributen kann dies sogar kompakter geschrieben werden. Ich nehme an, MBUnit hat sogar native Unterstützung für solche Dinge. Der Punkt ist jedoch, dass der obige Code die Absicht der Eigenschaft erfasst.

PS: Wahrscheinlich sollte der PropertyTest auch eine Möglichkeit haben, zu überprüfen, ob sich andere Eigenschaften des Objekts nicht geändert haben. Hmm .. zurück zum Zeichenbrett.


Ich ging zu einer Präsentation auf mbUnit. Das sieht großartig aus.
Johnno Nolan

Aber David, lassen Sie mich fragen: Waren Sie von Kent Becks Antwort oben überrascht? Fragen Sie sich bei seiner Antwort, ob Sie Ihren Ansatz überdenken sollten? Natürlich nicht, weil irgendjemand "Antworten von oben" hat. Aber Kent wird zuerst als einer der Hauptbefürworter des Tests angesehen. Ein Penny für deine Gedanken!
Charlie Flowers

@ Charles: Kents Antwort ist sehr pragmatisch. Ich arbeite "nur" an einem Projekt, in dem ich Code aus verschiedenen Quellen integrieren werde, und ich möchte ein sehr hohes Maß an Vertrauen schaffen.
David Schmitt

Trotzdem bemühe ich mich um Tests, die einfacher als der getestete Code sind, und diese Detailgenauigkeit lohnt sich möglicherweise nur bei Integrationstests, bei denen alle Generatoren, Module, Geschäftsregeln und Validatoren zusammenkommen.
David Schmitt

1

Ich mache einen Unit-Test, um die maximal mögliche Abdeckung zu erreichen. Wenn ich keinen Code erreichen kann, überarbeite ich, bis die Abdeckung so vollständig wie möglich ist

Nachdem ich mit dem Blinding-Schreibtest fertig bin, schreibe ich normalerweise einen Testfall, der jeden Fehler reproduziert

Ich bin es gewohnt, zwischen Codetests und Integrationstests zu unterscheiden. Während des Integrationstests (der ebenfalls ein Komponententest ist, jedoch Gruppen von Komponenten, also nicht genau das, wofür ein Komponententest vorgesehen ist) werde ich testen, ob die Anforderungen korrekt implementiert werden.


1

Je mehr ich meine Programmierung durch das Schreiben von Tests vorantreibe, desto weniger mache ich mir Sorgen über den Grad der Granualität der Tests. Rückblickend scheint es, als würde ich das Einfachste tun, um mein Validierungsziel zu erreichen Verhalten zu . Dies bedeutet, dass ich eine Vertrauensschicht generiere, dass mein Code das tut, was ich verlange. Dies wird jedoch nicht als absolute Garantie dafür angesehen, dass mein Code fehlerfrei ist. Ich bin der Meinung, dass die richtige Balance darin besteht, das Standardverhalten und möglicherweise ein oder zwei Randfälle zu testen und dann mit dem nächsten Teil meines Entwurfs fortzufahren.

Ich akzeptiere, dass dies nicht alle Fehler abdeckt und verwende andere traditionelle Testmethoden, um diese zu erfassen.


0

Im Allgemeinen fange ich klein an, mit Ein- und Ausgängen, von denen ich weiß, dass sie funktionieren müssen. Wenn ich dann Fehler behebe, füge ich weitere Tests hinzu, um sicherzustellen, dass die von mir behobenen Fehler getestet werden. Es ist organisch und funktioniert gut für mich.

Kannst du zu viel testen? Wahrscheinlich, aber es ist wahrscheinlich besser, generell auf Nummer sicher zu gehen, obwohl dies davon abhängt, wie geschäftskritisch Ihre Anwendung ist.


0

Ich denke, Sie müssen alles in Ihrem "Kern" Ihrer Geschäftslogik testen. Getter ans Setter auch, weil sie negative Werte oder Nullwerte akzeptieren könnten, die Sie möglicherweise nicht akzeptieren möchten. Wenn Sie Zeit haben (immer von Ihrem Chef abhängig), ist es gut, andere Geschäftslogik und alle Controller, die diese Objekte aufrufen, zu testen (Sie wechseln langsam vom Komponententest zum Integrationstest).


0

Ich teste keine einfachen Setter / Getter-Methoden, die keine Nebenwirkungen haben. Aber ich teste jede andere öffentliche Methode. Ich versuche, Tests für alle Randbedingungen in meinen Algorthims zu erstellen und die Abdeckung meiner Unit-Tests zu überprüfen.

Es ist viel Arbeit, aber ich denke, es lohnt sich. Ich würde lieber Code schreiben (sogar Code testen), als Code in einem Debugger durchzugehen. Ich finde den Code-Build-Deploy-Debug-Zyklus sehr zeitaufwändig und je umfassender die Unit-Tests, die ich in meinen Build integriert habe, desto weniger Zeit verbringe ich mit diesem Code-Build-Deploy-Debug-Zyklus.

Sie haben nicht gesagt, warum Architektur Sie auch codieren. Aber für Java verwende ich Maven 2 , JUnit , DbUnit , Cobertura und EasyMock .


Ich habe nicht gesagt, was eine ziemlich sprachunabhängige Frage ist.
Johnno Nolan

Unit-Tests in TDD decken Sie nicht nur beim Schreiben des Codes ab, sondern schützen auch vor der Person, die Ihren Code erbt, und halten es dann für sinnvoll, einen Wert im Getter zu formatieren!
Paxic

0

Je mehr ich darüber lese, desto mehr denke ich, dass einige Unit-Tests genau wie einige Muster sind: Ein Geruch von unzureichenden Sprachen.

Wenn Sie testen müssen, ob Ihr Trivial-Getter tatsächlich den richtigen Wert zurückgibt, liegt dies daran, dass Sie den Getter-Namen und den Namen der Mitgliedsvariablen miteinander mischen können. Geben Sie 'attr_reader: name' von Ruby ein, und das kann nicht mehr passieren. In Java einfach nicht möglich.

Wenn Ihr Getter jemals nicht trivial wird, können Sie trotzdem einen Test dafür hinzufügen.


Ich bin damit einverstanden, dass das Testen eines Getters trivial ist. Ich kann jedoch dumm genug sein, zu vergessen, es in einem Konstruktor festzulegen. Daher ist ein Test erforderlich. Meine Gedanken haben sich geändert, seit ich die Frage gestellt habe. Siehe meine Antwort stackoverflow.com/questions/153234/how-deep-are-your-unit-tests/…
Johnno Nolan

1
Eigentlich würde ich argumentieren, dass Unit-Tests als Ganzes in gewisser Weise nach einem Sprachproblem riechen. Sprachen wie Eiffel, die Verträge unterstützen (Vor- / Nachbedingungen für Methoden), benötigen noch einige Komponententests, aber weniger. In der Praxis machen es selbst einfache Verträge wirklich einfach, Fehler zu finden: Wenn der Vertrag einer Methode bricht, liegt der Fehler normalerweise in dieser Methode.
Damien Pollet

@ Damien: Vielleicht sind Unit-Tests und Verträge in der Verkleidung wirklich dasselbe? Was ich damit meine ist, dass eine Sprache, die Verträge "unterstützt", es im Grunde nur einfach macht, Codeausschnitte zu schreiben - Tests - die (optional) vor und nach anderen Codeausschnitten ausgeführt werden, richtig? Wenn die Grammatik einfach genug ist, kann eine Sprache, die Verträge nicht nativ unterstützt, leicht erweitert werden, um sie durch Schreiben eines Präprozessors zu unterstützen, richtig? Oder gibt es einige Dinge, die ein Ansatz (Verträge oder Komponententests) kann, die der andere einfach nicht kann?
j_random_hacker

0

Testen Sie den Quellcode, der Sie beunruhigt.

Ist nicht nützlich, um Teile des Codes zu testen, mit denen Sie sehr, sehr vertraut sind, solange Sie darin keine Fehler machen.

Testen Sie Bugfixes, damit Sie zum ersten und letzten Mal einen Bug beheben.

Testen Sie, um das Vertrauen in obskure Codeteile zu erhalten, damit Sie Wissen erstellen.

Testen Sie vor schwerem und mittlerem Refactoring, damit vorhandene Funktionen nicht beschädigt werden.


0

Diese Antwort dient eher dazu, herauszufinden, wie viele Komponententests für eine bestimmte Methode verwendet werden sollen, von der Sie wissen, dass Sie sie aufgrund ihrer Kritikalität / Wichtigkeit testen möchten. Mit der Basis Path Testing- Technik von McCabe können Sie Folgendes tun, um quantitativ ein besseres Vertrauen in die Codeabdeckung zu erzielen als mit der einfachen "Anweisungsabdeckung" oder "Zweigabdeckung":

  1. Bestimmen Sie den Wert für die zyklomatische Komplexität Ihrer Methode, die Sie einem Komponententest unterziehen möchten (Visual Studio 2010 Ultimate kann diesen Wert beispielsweise mit statischen Analysewerkzeugen für Sie berechnen, andernfalls können Sie ihn manuell über die Flowgraph-Methode http://users.csc berechnen . calpoly.edu/~jdalbey/206/Lectures/BasisPathTutorial/index.html )
  2. Listen Sie den Basissatz unabhängiger Pfade auf, die durch Ihre Methode fließen - siehe Link oben für ein Beispiel für einen Flussgraphen
  3. Bereiten Sie Unit-Tests für jeden unabhängigen Basispfad vor, der in Schritt 2 ermittelt wurde

Sie werden dies für jede Methode tun? Ernsthaft?
Kristopher Johnson
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.