Ist doppelter Code in Unit-Tests erträglicher?

112

Ich ruiniert mehrere Einheit vor einiger Zeit testet , als ich ging durch und Refactoring sie ihnen mehr zu machen DRY --die Absicht der einzelnen Tests war nicht mehr klar. Es scheint einen Kompromiss zwischen Lesbarkeit und Wartbarkeit der Tests zu geben. Wenn ich in Unit-Tests doppelten Code belasse, sind diese besser lesbar. Wenn ich jedoch das SUT ändere , muss ich jede Kopie des duplizierten Codes aufspüren und ändern.

Stimmen Sie zu, dass dieser Kompromiss besteht? Wenn ja, möchten Sie, dass Ihre Tests lesbar oder wartbar sind?

Daryl Spitzer
quelle

Antworten:

69

Duplizierter Code ist im Unit-Test-Code genauso ein Geruch wie in anderem Code. Wenn Sie Code in Tests dupliziert haben, ist es schwieriger, den Implementierungscode umzugestalten, da Sie überproportional viele Tests aktualisieren müssen. Tests sollten Ihnen dabei helfen, sicher umzugestalten, anstatt eine große Belastung zu sein, die Ihre Arbeit an dem zu testenden Code behindert.

Wenn sich die Duplizierung im Fixture-Setup befindet, sollten Sie die setUpMethode stärker nutzen oder mehr (oder flexiblere) Erstellungsmethoden bereitstellen .

Wenn sich die Duplizierung im Code befindet, der das SUT manipuliert, fragen Sie sich, warum mehrere sogenannte "Unit" -Tests genau dieselbe Funktionalität ausüben.

Wenn die Duplizierung in den Zusicherungen enthalten ist, benötigen Sie möglicherweise einige benutzerdefinierte Zusicherungen . Zum Beispiel, wenn mehrere Tests eine Reihe von Aussagen haben wie:

assertEqual('Joe', person.getFirstName())
assertEqual('Bloggs', person.getLastName())
assertEqual(23, person.getAge())

Dann brauchen Sie vielleicht eine einzige assertPersonEqualMethode, damit Sie schreiben können assertPersonEqual(Person('Joe', 'Bloggs', 23), person). (Oder Sie müssen den Gleichheitsoperator einfach überladen Person.)

Wie Sie bereits erwähnt haben, ist es wichtig, dass der Testcode lesbar ist. Insbesondere ist es wichtig, dass die Absicht eines Tests klar ist. Ich finde, wenn viele Tests größtenteils gleich aussehen (z. B. drei Viertel der Linien gleich oder praktisch gleich), ist es schwierig, die signifikanten Unterschiede zu erkennen, ohne sie sorgfältig zu lesen und zu vergleichen. Daher finde ich, dass Refactoring zum Entfernen von Duplikaten die Lesbarkeit verbessert, da jede Zeile jeder Testmethode direkt für den Zweck des Tests relevant ist. Das ist für den Leser viel hilfreicher als eine zufällige Kombination von Zeilen, die direkt relevant sind, und Zeilen, die nur als Boilerplate dienen.

Allerdings üben Tests manchmal komplexe Situationen aus, die ähnlich, aber immer noch erheblich unterschiedlich sind, und es ist schwierig, einen guten Weg zu finden, um die Doppelarbeit zu reduzieren. Verwenden Sie den gesunden Menschenverstand: Wenn Sie der Meinung sind, dass die Tests lesbar sind und ihre Absicht klarstellen, und Sie möglicherweise mehr als eine theoretisch minimale Anzahl von Tests aktualisieren müssen, wenn Sie den von den Tests aufgerufenen Code umgestalten, akzeptieren Sie die Unvollkommenheit und verschieben Sie sie auf etwas produktiveres. Sie können jederzeit zurückkommen und die Tests später umgestalten, wenn Inspiration aufkommt!

spiv
quelle
27
"Duplizierter Code riecht im Unit-Test-Code genauso wie in anderen Codes." Nein. "Wenn Sie Code in Tests dupliziert haben, ist es schwieriger, den Implementierungscode umzugestalten, da Sie überproportional viele Tests aktualisieren müssen." Dies liegt daran, dass Sie die private API anstelle der öffentlichen API testen.
14
Um jedoch doppelten Code in Komponententests zu vermeiden, müssen Sie normalerweise eine neue Logik einführen. Ich denke nicht, dass Unit-Tests Logik enthalten sollten, da Sie dann Unit-Tests von Unit-Tests benötigen würden.
Petr Peller
@ user11617 Bitte definieren Sie "private API" und "public Api". Nach meinem Verständnis ist die öffentliche API die API, die für die Außenwelt / Drittanbieter sichtbar und explizit über SemVer oder ähnliches versioniert ist. Alles andere ist privat. Mit dieser Definition testen fast alle Unit-Tests die "private API" und sind daher empfindlicher gegenüber Codeduplizierungen, was ich für richtig halte.
KolA
@KolA "Öffentlich" bedeutet nicht, dass Drittanbieter - dies ist keine Web-API. Die öffentliche API einer Klasse bezieht sich auf die Methoden, die vom Clientcode verwendet werden sollen (was sich normalerweise nicht so stark ändert / ändern sollte) - im Allgemeinen die "öffentlichen" Methoden. Die private API bezieht sich auf die Logik und Methoden, die intern verwendet werden. Auf diese sollte nicht von außerhalb der Klasse zugegriffen werden. Dies ist ein Grund, warum es wichtig ist, die Logik in einer Klasse mithilfe von Zugriffsmodifikatoren oder der Konvention in der verwendeten Sprache korrekt zu kapseln.
Nathan
@Nathan jedes Bibliothek / DLL / Nuget-Paket hat Konsumenten von Drittanbietern, es muss keine Web-API sein. Ich habe darauf hingewiesen, dass es sehr häufig vorkommt, öffentliche Klassen und Mitglieder zu deklarieren, die nicht direkt von Bibliothekskonsumenten verwendet werden sollen (oder sie bestenfalls intern zu machen und Assemblys mit InternalsVisibleToAttribute zu kommentieren), damit Unit-Tests sie direkt erreichen können. Es führt zu einer Vielzahl von Tests, die mit der Implementierung verbunden sind, und macht sie eher zu einer Belastung als zu einem Vorteil
KolA
184

Die Lesbarkeit ist für Tests wichtiger. Wenn ein Test fehlschlägt, soll das Problem offensichtlich sein. Der Entwickler sollte nicht viel stark faktorisierten Testcode durchgehen müssen, um genau zu bestimmen, was fehlgeschlagen ist. Sie möchten nicht, dass Ihr Testcode so komplex wird, dass Sie Unit-Test-Tests schreiben müssen.

Das Eliminieren von Duplikaten ist jedoch normalerweise eine gute Sache, solange nichts verdeckt wird. Das Eliminieren von Duplikaten in Ihren Tests kann zu einer besseren API führen. Stellen Sie nur sicher, dass Sie nicht über den Punkt hinausgehen, an dem die Renditen sinken.

Kristopher Johnson
quelle
xUnit und andere enthalten in Assert-Aufrufen ein 'message'-Argument. Gute Idee, aussagekräftige Ausdrücke zu verwenden, damit Entwickler fehlgeschlagene Testergebnisse schnell finden können.
Seand
1
@seand Sie können versuchen zu erklären, was Ihre Behauptung überprüft, aber wenn sie fehlschlägt und etwas verdeckten Code enthält, muss der Entwickler sie trotzdem abwickeln. IMO Es ist wichtiger, Code zu haben, um sich dort selbst zu beschreiben.
IgorK
1
@Kristopher ,? Warum ist dies ein Community-Wiki?
Pacerier
@ Pacerier Ich weiß es nicht. Früher gab es komplizierte Regeln dafür, dass Dinge automatisch zum Community-Wiki werden.
Kristopher Johnson
Da die Lesbarkeit von Berichten wichtiger ist als Tests, insbesondere bei der Integration oder bei End-to-End-Tests, können die Szenarien komplex genug sein, um ein kleines bisschen Navigieren zu vermeiden. Es ist in Ordnung, den Fehler zu finden, aber für mich sollte der Fehler in den Berichten erneut auftreten Erklären Sie das Problem gut genug.
Anirudh
47

Implementierungscode und Tests sind unterschiedliche Tiere und Factoring-Regeln gelten für sie unterschiedlich.

Doppelter Code oder doppelte Struktur riechen immer im Implementierungscode. Wenn Sie anfangen, Boilerplate in der Implementierung zu haben, müssen Sie Ihre Abstraktionen überarbeiten.

Auf der anderen Seite muss der Testcode einen Duplizierungsgrad beibehalten. Durch die Vervielfältigung des Testcodes werden zwei Ziele erreicht:

  • Tests entkoppelt halten. Übermäßige Testkopplung kann es schwierig machen, einen einzelnen fehlgeschlagenen Test zu ändern, der aktualisiert werden muss, da sich der Vertrag geändert hat.
  • Halten Sie die Tests isoliert aussagekräftig. Wenn ein einzelner Test fehlschlägt, muss es ziemlich einfach sein, genau herauszufinden, was er testet.

Ich neige dazu, triviale Duplikate im Testcode zu ignorieren, solange jede Testmethode kürzer als etwa 20 Zeilen bleibt. Ich mag es, wenn der Setup-Run-Verify-Rhythmus in Testmethoden sichtbar ist.

Wenn sich im "Verifizieren" -Teil von Tests Duplikate bilden, ist es häufig vorteilhaft, benutzerdefinierte Assertionsmethoden zu definieren. Natürlich müssen diese Methoden immer noch eine klar identifizierte Beziehung testen, die im Methodennamen deutlich wird: assertPegFitsInHole-> gut, assertPegIsGood-> schlecht.

Wenn Testmethoden langwierig und sich wiederholend werden, finde ich es manchmal nützlich, Testvorlagen zum Ausfüllen der Lücken zu definieren, die einige Parameter enthalten. Anschließend werden die eigentlichen Testmethoden auf einen Aufruf der Template-Methode mit den entsprechenden Parametern reduziert.

Für viele Dinge beim Programmieren und Testen gibt es keine eindeutige Antwort. Sie müssen einen Geschmack entwickeln, und der beste Weg, dies zu tun, besteht darin, Fehler zu machen.

ddaa
quelle
8

Sie können die Wiederholung mithilfe verschiedener Testmethoden reduzieren .

Ich bin toleranter gegenüber Wiederholungen im Testcode als im Produktionscode, aber ich war manchmal frustriert darüber. Wenn Sie das Design einer Klasse ändern und 10 verschiedene Testmethoden anpassen müssen, die alle dieselben Einrichtungsschritte ausführen, ist dies frustrierend.

Don Kirkby
quelle
7

Genau. Der Kompromiss besteht, ist aber an verschiedenen Orten unterschiedlich.

Es ist wahrscheinlicher, dass ich doppelten Code zum Einrichten des Status umgestalte. Es ist jedoch weniger wahrscheinlich, dass der Teil des Tests, der den Code tatsächlich ausführt, überarbeitet wird. Das heißt, wenn das Ausüben des Codes immer mehrere Codezeilen erfordert, könnte ich denken, dass dies ein Geruch ist, und den tatsächlich getesteten Code umgestalten. Dies verbessert die Lesbarkeit und Wartbarkeit sowohl des Codes als auch der Tests.

stucampbell
quelle
Ich denke, das ist eine gute Idee. Wenn Sie viele Duplikate haben, prüfen Sie, ob Sie einen Refactor erstellen können, um ein gemeinsames "Testgerät" zu erstellen, unter dem viele Tests ausgeführt werden können. Dadurch werden doppelte Setup- / Teardown-Codes vermieden.
Outlaw Programmer
6

Jay Felder prägte den Satz , dass „DSLs DAMP werden sollte, nicht trocken“, wo DAMP Mittel beschreibende und bedeutungsvollen Phrasen . Ich denke, dasselbe gilt auch für Tests. Offensichtlich ist zu viel Vervielfältigung schlecht. Das Entfernen von Duplikaten um jeden Preis ist jedoch noch schlimmer. Tests sollten als aufschlussreiche Spezifikationen dienen. Wenn Sie beispielsweise dasselbe Feature aus verschiedenen Blickwinkeln angeben, ist ein gewisses Maß an Duplizierung zu erwarten.

Jörg W Mittag
quelle
3

Ich LIEBE rspec aus diesem Grund:

Es gibt zwei Dinge zu helfen -

  • gemeinsame Beispielgruppen zum Testen des allgemeinen Verhaltens.
    Sie können eine Reihe von Tests definieren und diese dann in Ihre realen Tests einbeziehen.

  • verschachtelte Kontexte.
    Sie können im Wesentlichen eine "Setup" - und "Teardown" -Methode für eine bestimmte Teilmenge Ihrer Tests verwenden, nicht nur für jeden in der Klasse.

Je früher .NET / Java / andere Test-Frameworks diese Methoden anwenden, desto besser (oder Sie können IronRuby oder JRuby verwenden, um Ihre Tests zu schreiben, was meiner Meinung nach die bessere Option ist).

Orion Edwards
quelle
3

Ich bin der Meinung, dass Testcode ein ähnliches technisches Niveau erfordert, das normalerweise auf Produktionscode angewendet wird. Es kann durchaus Argumente für die Lesbarkeit geben, und ich würde zustimmen, dass dies wichtig ist.

Nach meiner Erfahrung sind gut faktorisierte Tests jedoch leichter zu lesen und zu verstehen. Wenn es 5 Tests gibt, die bis auf eine geänderte Variable und die Behauptung am Ende gleich aussehen, kann es sehr schwierig sein, das einzelne unterschiedliche Element zu finden. Wenn berücksichtigt wird, dass nur die sich ändernde Variable und die Behauptung sichtbar sind, ist es ebenfalls einfach, sofort herauszufinden, was der Test tut.

Es kann schwierig sein, beim Testen die richtige Abstraktionsebene zu finden, und ich halte es für sinnvoll, dies zu tun.

Kevin London
quelle
2

Ich glaube nicht, dass es eine Beziehung zwischen mehr dupliziertem und lesbarem Code gibt. Ich denke, Ihr Testcode sollte so gut sein wie Ihr anderer Code. Nicht wiederholter Code ist besser lesbar als duplizierter Code, wenn er gut gemacht wird.

Paco
quelle
2

Im Idealfall sollten sich Unit-Tests nach dem Schreiben nicht wesentlich ändern, sodass ich mich der Lesbarkeit zuwenden würde.

Wenn Unit-Tests so diskret wie möglich sind, können Sie die Tests auch auf die spezifischen Funktionen konzentrieren, auf die sie abzielen.

Trotzdem versuche ich, bestimmte Codeteile, die ich immer wieder verwende, wiederzuverwenden, z. B. Setup-Code, der für eine Reihe von Tests genau gleich ist.

17 von 26
quelle
2

"Sie wurden überarbeitet, um sie trockener zu machen - die Absicht jedes Tests war nicht mehr klar."

Es hört sich so an, als hätten Sie Probleme beim Refactoring. Ich vermute nur, aber wenn es weniger klar ist, heißt das nicht, dass Sie noch mehr Arbeit zu tun haben, damit Sie einigermaßen elegante Tests haben, die vollkommen klar sind?

Aus diesem Grund sind Tests eine Unterklasse von UnitTest. So können Sie gute Testsuiten entwerfen, die korrekt, einfach zu validieren und klar sind.

In früheren Zeiten hatten wir Testwerkzeuge, die verschiedene Programmiersprachen verwendeten. Es war schwierig (oder unmöglich), mit Tests angenehm und einfach zu arbeiten.

Sie haben die volle Leistungsfähigkeit von Python, Java, C # - egal welche Sprache Sie verwenden - also verwenden Sie diese Sprache gut. Sie können einen gut aussehenden Testcode erzielen, der klar und nicht zu redundant ist. Es gibt keinen Kompromiss.

S.Lott
quelle