Unit Test Adoption [geschlossen]

69

Wir haben versucht, Unit-Tests in unser aktuelles Projekt einzuführen, aber es scheint nicht zu funktionieren. Der zusätzliche Code scheint zu einem Wartungsproblem geworden zu sein, da wir bei Änderungen unseres internen Frameworks alle Unit-Tests beheben müssen, die daran hängen.

Wir haben eine abstrakte Basisklasse für Unit-Tests unserer Controller, die als Vorlage für die abstrakten Methodenimplementierungen der untergeordneten Klassen fungiert, dh Framework-Aufrufe Initialize, sodass alle unsere Controller-Klassen ihre eigene Initialize-Methode haben.

Früher war ich ein Verfechter von Unit-Tests, aber es scheint nicht an unserem aktuellen Projekt zu funktionieren.

Kann jemand helfen, das Problem zu identifizieren und wie wir Unit-Tests für uns und nicht gegen uns durchführen können?

Burt
quelle
Interessante Frage. Halten Sie uns bitte auf dem Laufenden!
Borjab

Antworten:

108

Tipps:

Vermeiden Sie das Schreiben von Verfahrenscode

Tests können ein Bär sein, wenn sie gegen prozeduralen Code geschrieben werden, der stark vom globalen Zustand abhängt oder tief im Körper einer hässlichen Methode liegt. Wenn Sie Code in einer OO-Sprache schreiben, verwenden Sie OO-Konstrukte effektiv, um dies zu reduzieren.

  • Vermeiden Sie nach Möglichkeit den globalen Zustand.
  • Vermeiden Sie statische Aufladungen, da diese dazu neigen, Ihre Codebasis zu durchdringen und schließlich dazu führen, dass Dinge statisch sind, die nicht sein sollten. Sie blähen auch Ihren Testkontext auf (siehe unten).
  • Nutzen Sie den Polymorphismus effektiv, um übermäßige Wenns und Flaggen zu verhindern

Finden Sie, was sich ändert, kapseln Sie es und trennen Sie es von dem, was gleich bleibt.

Es gibt Drosselstellen im Code, die sich viel häufiger ändern als andere Teile. Wenn Sie dies in Ihrer Codebasis tun, werden Ihre Tests gesünder.

  • Eine gute Einkapselung führt zu guten, lose gekoppelten Designs.
  • Refactor und modularisieren.
  • Halten Sie die Tests klein und konzentriert.

Je größer der Kontext eines Tests ist, desto schwieriger wird es, ihn aufrechtzuerhalten.

Tun Sie alles, um Tests und den umgebenden Kontext, in dem sie ausgeführt werden, zu verkleinern.

  • Verwenden Sie das Refactoring von zusammengesetzten Methoden, um kleinere Codestücke zu testen.
  • Verwenden Sie ein neueres Testframework wie TestNG oder JUnit4? Mit ihnen können Sie Doppelspurigkeiten in Tests entfernen, indem Sie feinkörnigere Haken in den Testlebenszyklus einbauen.
  • Untersuchen Sie mithilfe von Test-Doubles (Mocks, Fakes, Stubs), um die Größe des Testkontexts zu verringern.
  • Untersuchen Sie das Test Data Builder- Muster.

Entfernen Sie Duplikate aus Tests, stellen Sie jedoch sicher, dass sie den Fokus behalten.

Sie werden wahrscheinlich nicht in der Lage sein, alle Duplikate zu entfernen, aber dennoch versuchen, sie dort zu entfernen, wo sie Schmerzen verursachen. Stellen Sie sicher, dass Sie nicht so viele Duplikate entfernen, dass jemand nicht hereinkommen und auf einen Blick erkennen kann, was der Test bewirkt. ( Eine alternative Erklärung desselben Konzepts finden Sie in Paul Wheatons Artikel "Evil Unit Tests" .)

  • Niemand wird einen Test reparieren wollen, wenn er nicht herausfinden kann, was er tut.
  • Folgen Sie dem Muster Anordnen, Handeln, Bestätigen.
  • Verwenden Sie nur eine Behauptung pro Test.

Testen Sie auf der richtigen Ebene, was Sie überprüfen möchten.

Denken Sie an die Komplexität eines Selenium-Tests zum Aufzeichnen und Wiedergeben und darüber, was sich unter Ihnen im Vergleich zum Testen einer einzelnen Methode ändern könnte.

  • Abhängigkeiten voneinander isolieren.
  • Verwenden Sie die Abhängigkeitsinjektion / Inversion der Kontrolle.
  • Verwenden Sie Test-Doubles, um ein zu testendes Objekt zu initialisieren, und stellen Sie sicher, dass Sie einzelne Codeeinheiten isoliert testen.
  • Stellen Sie sicher, dass Sie relevante Tests schreiben
    • "Spring the Trap", indem Sie absichtlich einen Fehler einführen und sicherstellen, dass er von einem Test erfasst wird.
  • Siehe auch: Integrationstests sind ein Betrug

Wissen, wann State Based vs Interaction Based Testing verwendet werden muss

Echte Unit-Tests erfordern echte Isolation. Unit-Tests treffen keine Datenbank und öffnen keine Sockets. Hören Sie auf, sich über diese Interaktionen lustig zu machen. Stellen Sie sicher, dass Sie korrekt mit Ihren Mitarbeitern sprechen und nicht, dass das richtige Ergebnis dieses Methodenaufrufs "42" war.

Demonstrieren Sie den Testfahrcode

Es steht zur Debatte, ob ein bestimmtes Team den gesamten Code testen oder "Tests zuerst" für jede Codezeile schreiben wird. Aber sollten sie zuerst mindestens einige Tests schreiben? Absolut. Es gibt Szenarien, in denen Test-First zweifellos der beste Weg ist, um ein Problem anzugehen.

Ressourcen:

cwash
quelle
1
Warum bekommt diese Frage nicht mehr positive Stimmen? Das sind ein paar gute Sachen!
Epaga
1
Dies war ein Stub-Blogeintrag von mir ... Ich werde die Frage wahrscheinlich wiederholen und sie bald veröffentlichen.
cwash
2
Im Ernst, eine Behauptung pro Test? Wie viel Zeit verbringen Sie mit dem Schreiben von Signaturen für Testmethoden?
erikkallen
1
Warum magst du keine statischen Methoden? Ich finde es oft viel einfacher zu testen, ob ich eine Reihe statischer Methoden habe (natürlich keine Nebenwirkungen) und dann mutierende Instanzmethoden in der Form "mem = f1 (mem1, mem2); mem2 = f2 (mem1)" habe. ; "und testen Sie dann einfach die statische Methode.
Erikkallen
1
@erikkallen - statische Methoden mit statischen Methoden, statische Methoden sind eine Black Box. Ich kann nichts zum Testen ändern. Sie neigen dazu, die Verwendung des globalen Staates zu verbreiten und erschweren daher das Testen. Wenn Sie eine statische Methode schreiben, die keinen globalen Status verwendet, werden wahrscheinlich alle in der Methode verwendeten Status über Argumente eingegeben. Warum nicht als Instanzmethode schreiben? Siehe: googletesting.blogspot.com/2008/12/… Auch dies ist kein Gesetz des Universums, aber eine gute Idee, wenn Sie sich mit Testbarkeit oder wartbaren Tests befassen.
cwash
19

Testen Sie ausreichend kleine Codeeinheiten? Sie sollten nicht zu viele Änderungen sehen, es sei denn, Sie ändern grundlegend alles in Ihrem Kerncode.

Sobald die Dinge stabil sind, werden Sie die Komponententests mehr zu schätzen wissen, aber selbst jetzt zeigen Ihre Tests, inwieweit Änderungen an Ihrem Framework durchgesetzt werden.

Es lohnt sich, bleiben Sie dabei, so gut Sie können.

cjk
quelle
Ich stimme ck zu. Für das, was ich bei der Integration von Unit-Tests in einigen Unternehmen gesehen habe, werden Unit-Test-Frameworks häufiger für Funktionstests als für Unit-Tests verwendet.
Nicolas
Ich habe vor ein paar Monaten einen Blog zu diesem Thema geschrieben: cwash.org/2009/02/17/dont-unit-test-anymore-no-really
cwash
Das heißt, wie die Terminologie "Entwicklertest" mit der Terminologie "
Komponententest
12

Ohne weitere Informationen ist es schwierig, einen guten Eindruck davon zu bekommen, warum Sie unter diesen Problemen leiden. Manchmal ist es unvermeidlich, dass das Ändern von Schnittstellen usw. viele Dinge kaputt macht, manchmal liegt es an Designproblemen.

Es ist eine gute Idee, die aufgetretenen Fehler zu kategorisieren. Welche Probleme haben Sie? Ist es beispielsweise eine Testwartung (wie beim Kompilieren nach dem Refactoring!) Aufgrund von API-Änderungen oder liegt es am Verhalten der API-Änderung? Wenn Sie ein Muster sehen, können Sie versuchen, das Design des Produktionscodes zu ändern oder die Tests besser vor Änderungen zu schützen.

Wenn das Ändern einer Handvoll Dinge an vielen Stellen zu einer unermesslichen Verwüstung Ihrer Testsuite führt, können Sie einige Dinge tun (die meisten davon sind nur allgemeine Tipps zum Testen von Einheiten):

  • Entwickeln Sie kleine Codeeinheiten und testen Sie kleine Codeeinheiten. Extrahieren Sie Schnittstellen oder Basisklassen, wenn dies sinnvoll ist, damit Codeeinheiten 'Nähte' enthalten. Je mehr Abhängigkeiten Sie einbeziehen müssen (oder schlimmer noch, Sie müssen innerhalb der Klasse mit 'new' instanziieren), desto anfälliger ist es, Ihren Code zu ändern. Wenn jede Codeeinheit eine Handvoll Abhängigkeiten aufweist (manchmal ein paar oder gar keine), ist sie besser vor Änderungen geschützt.

  • Stellen Sie immer nur fest, was der Test benötigt. Setzen Sie sich nicht für einen Zwischen-, Neben- oder Nicht-Zustandszustand ein. Design by Contract und Test by Contract (z. B. wenn Sie eine Stack-Pop-Methode testen, testen Sie die count-Eigenschaft nach dem Push nicht - dies sollte in einem separaten Test erfolgen).

    Ich sehe dieses Problem ziemlich oft, besonders wenn jeder Test eine Variante ist. Wenn sich einer dieser zufälligen Zustände ändert, wird alles unterbrochen, was darauf behauptet wird (unabhängig davon, ob die Zusicherungen benötigt werden oder nicht).

  • Verwenden Sie wie bei normalem Code Fabriken und Builder für Ihre Komponententests. Ich habe erfahren, dass bei etwa 40 Tests ein Konstruktoraufruf erforderlich war, der nach einer API-Änderung aktualisiert wurde ...

  • Verwenden Sie genauso wichtig zuerst die Vordertür. Ihre Tests sollten immer den normalen Zustand verwenden, wenn er verfügbar ist. Verwendete interaktionsbasierte Tests nur, wenn dies erforderlich ist (dh kein Status, anhand dessen überprüft werden kann).

Das Wesentliche dabei ist jedenfalls, dass ich versuchen würde herauszufinden, warum / wo die Tests abbrechen und von dort aus fortfahren. Geben Sie Ihr Bestes, um sich vor Veränderungen zu schützen.

Mark Simpson
quelle
8

Einer der Vorteile von Unit-Tests besteht darin, dass Sie bei solchen Änderungen nachweisen können, dass Sie Ihren Code nicht beschädigen. Sie müssen Ihre Tests zwar mit Ihrem Framework synchronisieren, aber diese eher banale Arbeit ist viel einfacher, als herauszufinden, was beim Refactoring kaputt gegangen ist.

Jon B.
quelle
4

Ich würde darauf bestehen, dass Sie sich an die TDD halten.Versuchen Sie, Ihr Unit-Testing-Framework zu überprüfen. Führen Sie mit Ihrem Team eine RCA (Root Cause Analysis) durch und identifizieren Sie den Bereich.

Korrigieren Sie den Unit-Test-Code auf Suite-Ebene und ändern Sie Ihren Code nicht häufig, insbesondere nicht die Funktionsnamen oder andere Module.

Würden Sie sich freuen, wenn Sie Ihre Fallstudie gut teilen können, dann können wir mehr über den Problembereich herausfinden?

Sourabh
quelle
4

Gute Frage!

Das Entwerfen guter Komponententests ist schwierig wie das Entwerfen der Software selbst. Dies wird von Entwicklern selten anerkannt, so dass das Ergebnis häufig hastig geschriebene Komponententests sind, die gewartet werden müssen, wenn sich das zu testende System ändert. Ein Teil der Lösung Ihres Problems könnte darin bestehen, mehr Zeit für die Verbesserung des Designs Ihrer Komponententests aufzuwenden.

Ich kann ein großartiges Buch empfehlen, das seine Abrechnung verdient The Design Patterns of Unit-Testing

HTH

Azheglov
quelle
Auf jeden Fall ein gutes Buch, das sich wahrscheinlich mit dem Problem des Fragestellers befasst
Frank Schwieterman
1
Viele Bücher helfen Ihnen beim Einstieg in TDD und Unit-Tests. Das obige Buch kann helfen, wenn Sie nicht weiterkommen.
Pete TerMaat
Tatsächlich. Wenn Sie nicht weiterkommen, müssen Sie es nur auf der richtigen Seite öffnen!
Azheglov
4

Wenn das Problem darin besteht, dass Ihre Tests mit dem tatsächlichen Code nicht mehr aktuell sind, können Sie einen oder beide der folgenden Schritte ausführen:

  1. Trainieren Sie alle Entwickler, um keine Codeüberprüfungen zu bestehen, bei denen Unit-Tests nicht aktualisiert werden.
  2. Richten Sie eine automatische Testbox ein, die nach jedem Check-in den gesamten Satz von Komponententests ausführt und diejenigen per E-Mail benachrichtigt, die den Build brechen. (Früher dachten wir, dass dies nur für die "großen Jungs" war, aber wir verwendeten ein Open-Source-Paket auf einer dedizierten Box.)
Robert Gowland
quelle
3

Nun, wenn sich die Logik im Code geändert hat und Sie Tests für diese Codeteile geschrieben haben, würde ich annehmen, dass die Tests geändert werden müssten, um die neue Logik zu überprüfen. Unit-Tests sollen ziemlich einfacher Code sein, der die Logik Ihres Codes testet.

CSharpAtl
quelle
3

Ihre Unit-Tests machen das, was sie machen sollen. Bringen Sie Verhaltensstörungen aufgrund von Änderungen im Framework, im Sofortcode oder in anderen externen Quellen an die Oberfläche. Auf diese Weise können Sie feststellen, ob sich das Verhalten geändert hat und die Komponententests entsprechend geändert werden müssen oder ob ein Fehler aufgetreten ist, der dazu führt, dass der Komponententest fehlschlägt und korrigiert werden muss.

Gib nicht auf, während es jetzt frustrierend ist, wird der Nutzen realisiert.

David Yancey
quelle
2

Ich bin mir nicht sicher, welche spezifischen Probleme es schwierig machen, Tests für Ihren Code zu verwalten, aber ich kann einige meiner eigenen Erfahrungen teilen, als ich ähnliche Probleme mit meinen Tests hatte. Letztendlich habe ich erfahren, dass die mangelnde Testbarkeit hauptsächlich auf einige Designprobleme mit der getesteten Klasse zurückzuführen ist:

  • Verwenden konkreter Klassen anstelle von Schnittstellen
  • Singletons verwenden
  • Aufruf vieler statischer Methoden für Geschäftslogik und Datenzugriff anstelle von Schnittstellenmethoden

Aus diesem Grund stellte ich fest, dass meine Tests normalerweise unterbrochen wurden - nicht aufgrund einer Änderung in der zu testenden Klasse -, sondern aufgrund von Änderungen in anderen Klassen, die von der zu testenden Klasse aufgerufen wurden. Im Allgemeinen macht das Refactoring von Klassen, um nach ihren Datenabhängigkeiten zu fragen, und das Testen mit Scheinobjekten (EasyMock et al. Für Java) das Testen viel fokussierter und wartbarer. Ich habe einige Websites zu diesem Thema wirklich genossen:

Kyle Krull
quelle
+1 Das Google-Zeug ist wirklich sehr gut. googletesting.blogspot.com/2008/11/… ist eine ausgezeichnete Präsentation für diejenigen von uns, die nicht so gerne lesen
;-)
2

Warum sollten Sie Ihre Komponententests jedes Mal ändern müssen, wenn Sie Änderungen an Ihrem Framework vornehmen? Sollte das nicht umgekehrt sein?

Wenn Sie TDD verwenden, sollten Sie zunächst entscheiden, dass Ihre Tests das falsche Verhalten testen, und stattdessen überprüfen, ob das gewünschte Verhalten vorliegt. Nachdem Sie Ihre Tests behoben haben, schlagen Ihre Tests fehl und Sie müssen die Fehler in Ihrem Framework beseitigen, bis Ihre Tests erneut erfolgreich sind.

SingleNegationElimination
quelle
1

Alles ist natürlich mit dem Preis verbunden. In diesem frühen Entwicklungsstadium ist es normal, dass viele Komponententests geändert werden müssen.

Möglicherweise möchten Sie einige Teile Ihres Codes überprüfen, um mehr Kapselung zu erzielen, weniger Abhängigkeiten zu erstellen usw.

Wenn Sie sich dem Produktionsdatum nähern, werden Sie froh sein, dass Sie diese Tests haben, vertrauen Sie mir :)

Gerrie Schenck
quelle
Wir befinden uns momentan im Release-Zyklus und dies ist das Problem, da wir es uns nicht leisten können, Zeit mit dem Beheben von Tests zu verbringen, wenn wir Fehler bei der Standortakzeptanz beheben müssen.
Burt
2
@Burt Das macht keinen Sinn. Sie schreiben einen Komponententest für Verhaltensweisen, die Sie überhaupt nicht ändern möchten . Wenn Sie möchten, dass sich das Verhalten jeden zweiten Tag ändert, schreiben Sie keine Komponententests. Was ein bisschen seltsam ist, ist, sie zu schreiben und sich dann zu beschweren, dass sie versagen. Sie benötigen Unit-Tests, wenn Sie nur Code mit stabilem, wiederholbarem Verhalten wünschen. Wenn Sie möchten, dass Ihre Software ihre Funktionen ständig ändert, testen Sie sie nicht im Unit-Modus. In diesem Szenario jedoch viel Glück beim manuellen Testen.
Daniel Daranas
Wie sollen wir das Framework weiterentwickeln, ohne es zu ändern? Es handelt sich um Infrastrukturcode, ich habe eine Weile mit dem Castle-Stack gearbeitet und den Trunk-Code abgearbeitet. Er hat sich stark geändert. Ich gehe daher davon aus, dass sich ein Framework häufig durch Refactoring und Änderungen verbessert, wenn neue Anforderungen identifiziert werden.
Burt
Entweder (A) Sie testen es automatisch. Wenn sich das Verhalten ändert, müssen Sie den Test ändern. (B) Sie testen es manuell, mit einem Word-Dokument oder Mundpropaganda-Erwartungen an das Verhalten. Wenn Sie es dann testen, müssen Sie dem Tester sagen: "Hey Joe, von nun an müssen Sie X und Y, Z tun passieren, statt W "oder (C) Sie testen es nicht. Es sieht so aus, als würden Sie eine Zwischenschicht zwischen (A) und (C) berechnen. Beides hat Kosten und Vorteile.
Daniel Daranas
1

Sind Ihre Unit-Tests nicht zu Black-Box-orientiert? Ich meine ... lassen Sie mich ein Beispiel nehmen: Angenommen, Sie testen eine Art Container in einer Einheit, verwenden Sie die Methode get () des Containers, um zu überprüfen, ob ein neues Element tatsächlich gespeichert wurde, oder schaffen Sie es, ein Handle zu erhalten den tatsächlichen Speicher, um den Artikel direkt dort abzurufen, wo er gespeichert ist? Letzteres führt zu spröden Tests: Wenn Sie die Implementierung ändern, brechen Sie die Tests.

Sie sollten anhand der Schnittstellen testen, nicht anhand der internen Implementierung.

Und wenn Sie das Framework ändern, sollten Sie zuerst versuchen, die Tests und dann das Framework zu ändern.

Philant
quelle
1

Ich würde vorschlagen, in ein Testautomatisierungstool zu investieren. Wenn Sie eine kontinuierliche Integration verwenden, können Sie diese zusammen ausführen. Es gibt Tools, die Ihre Codebasis scannen und Tests für Sie generieren. Dann werden sie ausgeführt. Nachteil dieses Ansatzes ist, dass es zu allgemein ist. Denn in vielen Fällen besteht der Zweck des Unit-Tests darin, das System zu beschädigen. Ich habe zahlreiche Tests geschrieben und ja, ich muss sie ändern, wenn sich die Codebasis ändert.

Es gibt eine feine Linie mit dem Automatisierungstool, mit dem Sie definitiv eine bessere Codeabdeckung haben würden.

Mit einem gut entwickelten Entwickler-basierten Test testen Sie jedoch auch die Systemintegrität.

Hoffe das hilft.

Boris Kleynbok
quelle
1

Wenn Ihr Code wirklich schwer zu testen ist und der Testcode kaputt geht oder viel Aufwand erfordert, um synchron zu bleiben, haben Sie ein größeres Problem.

Verwenden Sie das Refactoring der Extraktionsmethode, um kleine Codeblöcke herauszuziehen, die nur eines und nur eines tun. ohne Abhängigkeiten und schreiben Sie Ihre Tests auf diese kleinen Methoden.

sal
quelle
1

Der zusätzliche Code scheint zu einem Wartungsproblem geworden zu sein, da wir bei Änderungen unseres internen Frameworks alle Unit-Tests korrigieren müssen, die daran hängen.

Die Alternative besteht darin, dass Sie die Änderungen nicht testen, wenn sich Ihr Framework ändert. Oder Sie testen das Framework überhaupt nicht. Ist es das was du willst?

Sie können versuchen, Ihr Framework so umzugestalten, dass es aus kleineren Teilen besteht, die unabhängig getestet werden können. Wenn sich dann Ihr Framework ändert, hoffen Sie, dass sich entweder (a) weniger Teile ändern oder (b) die Änderungen hauptsächlich in der Art und Weise liegen, wie die Teile zusammengesetzt sind. In beiden Fällen können Sie Code und Tests besser wiederverwenden. Aber es geht um echte intellektuelle Anstrengungen. Erwarten Sie nicht, dass es einfach ist.

Norman Ramsey
quelle
Idealerweise sollte unser Framework schnittstellengesteuert sein, was bedeutet, dass wir viel davon verspotten konnten, aber es wurde entschieden, dass Vererbung eine bessere Option ist. Es ist die Vererbung, die die Probleme verursacht und das Testen der Funktionalität erschwert.
Burt
1

Ich fand heraus, dass die Unit-Tests, wenn Sie nicht die IoC / DI-Methodik verwenden, die das Schreiben sehr kleiner Klassen fördert und das Prinzip der Einzelverantwortung religiös befolgt, letztendlich die Interaktion mehrerer Klassen testen, was sie sehr komplex und daher fragil macht.

Mein Punkt ist, dass viele der neuartigen Softwareentwicklungstechniken nur funktionieren, wenn sie zusammen verwendet werden. Insbesondere MVC, ORM, IoC, Unit-Testing und Mocking. DDD (im modernen primitiven Sinne) und TDD / BDD sind unabhängiger, sodass Sie sie verwenden können oder nicht.

zvolkov
quelle
0

Irgendwann werden beim Entwerfen der TDD-Tests Fragen zum Design der Anwendung selbst gestellt. Überprüfen Sie, ob Ihre Klassen gut gestaltet wurden, Ihre Methoden immer nur eine Aufgabe ausführen ... Bei gutem Design sollte es einfach sein, Code zu schreiben, um einfache Methoden und Klassen zu testen.

sptremblay
quelle
0

Ich habe selbst über dieses Thema nachgedacht. Ich bin sehr begeistert vom Wert von Unit-Tests, aber nicht von strengen TDD. Es scheint mir, dass Sie bis zu einem gewissen Punkt explorative Programmierung durchführen, bei der sich die Art und Weise ändern muss, wie Sie Dinge in Klassen / Schnittstellen aufteilen. Wenn Sie viel Zeit in Komponententests für die alte Klassenstruktur investiert haben, ist dies eine erhöhte Trägheit gegenüber Refactoring, schmerzhaftes Verwerfen dieses zusätzlichen Codes usw.

Anon
quelle