it-swarm.com.de

Wie erkennen Sie Abhängigkeitsprobleme bei Komponententests, wenn Sie Scheinobjekte verwenden?

Sie haben eine Klasse X und schreiben einige Komponententests, die das Verhalten X1 überprüfen. Es gibt auch Klasse A, die X als Abhängigkeit nimmt.

Wenn Sie Unit-Tests für A schreiben, verspotten Sie X. Mit anderen Worten, während Sie Unit-Tests für A durchführen, setzen Sie das Post-Verhalten von X auf X1. Die Zeit vergeht, die Leute benutzen Ihr System, müssen sich ändern, X entwickelt sich weiter: Sie ändern X, um das Verhalten X2 anzuzeigen. Offensichtlich schlagen Unit-Tests für X fehl und Sie müssen sie anpassen.

Aber was ist mit A? Unit-Tests für A schlagen nicht fehl, wenn das Verhalten von X geändert wird (aufgrund der Verspottung von X). Wie kann man feststellen, dass das Ergebnis von A anders ist, wenn es mit dem "echten" (modifizierten) X ausgeführt wird?

Ich erwarte Antworten in der Art: "Das ist nicht der Zweck von Unit-Tests", aber welchen Wert haben Unit-Tests dann? Sagt es Ihnen wirklich nur, dass Sie, wenn alle Tests bestanden sind, keine bahnbrechende Änderung eingeführt haben? Und wenn sich das Verhalten einer Klasse (freiwillig oder unfreiwillig) ändert, wie können Sie (vorzugsweise auf automatisierte Weise) alle Konsequenzen erkennen? Sollten wir uns nicht mehr auf Integrationstests konzentrieren?

98
bvgheluwe

Wenn Sie Unit-Tests für A schreiben, verspotten Sie X.

Machst du? Ich nicht, es sei denn, ich muss unbedingt. Ich muss wenn:

  1. X ist langsam oder
  2. X hat Nebenwirkungen

Wenn beides nicht zutrifft, werden meine Unit-Tests von A auch X testen. Alles andere zu tun, würde Isolationstests auf ein unlogisches Extrem bringen.

Wenn Sie Teile Ihres Codes haben, die Mocks anderer Teile Ihres Codes verwenden, würde ich zustimmen: Was ist der Sinn solcher Unit-Tests? Also tu das nicht. Lassen Sie diese Tests die realen Abhängigkeiten verwenden, da sie auf diese Weise weitaus wertvollere Tests bilden.

Und wenn einige Leute sich darüber aufregen, dass Sie diese Tests "Komponententests" nennen, dann nennen Sie sie einfach "automatisierte Tests" und schreiben Sie gute automatisierte Tests.

125
David Arno

Du brauchst beides. Unit-Tests, um das Verhalten jeder Ihrer Einheiten zu überprüfen, und einige Integrationstests, um sicherzustellen, dass sie richtig angeschlossen sind. Das Problem, sich nur auf Integrationstests zu verlassen, ist die kombinatorische Explosion, die aus Interaktionen zwischen all Ihren Einheiten resultiert.

Angenommen, Sie haben Klasse A, für die 10 Komponententests erforderlich sind, um alle Pfade vollständig abzudecken. Dann haben Sie eine andere Klasse B, für die ebenfalls 10 Komponententests erforderlich sind, um alle Pfade abzudecken, die der Code durch sie führen kann. Angenommen, Sie müssen in Ihrer Anwendung die Ausgabe von A in B einspeisen. Jetzt kann Ihr Code 100 verschiedene Pfade von der Eingabe von A zur Ausgabe von B nehmen.

Bei Unit-Tests benötigen Sie nur 20 Unit-Tests + 1 Integrationstest, um alle Fälle vollständig abzudecken.

Bei Integrationstests benötigen Sie 100 Tests, um alle Codepfade abzudecken.

Hier ist ein sehr gutes Video über die Nachteile, sich nur auf Integrationstests zu verlassen JB Rainsberger Integrierte Tests sind ein Betrug HD

79
Eternal21

Wenn Sie Unit-Tests für A schreiben, verspotten Sie X. Mit anderen Worten, während Sie Unit-Tests für A durchführen, setzen Sie das Post-Verhalten von X auf X1. Die Zeit vergeht, die Leute benutzen Ihr System, müssen sich ändern, X entwickelt sich weiter: Sie ändern X, um das Verhalten X2 anzuzeigen. Offensichtlich schlagen Unit-Tests für X fehl und Sie müssen sie anpassen.

Woah, warte einen Moment. Die Auswirkungen der Tests auf X-Fehler sind zu wichtig, um sie so zu beschönigen.

Wenn das Ändern der Implementierung von X von X1 auf X2 die Komponententests für X unterbricht, bedeutet dies, dass Sie eine rückwärts inkompatible Änderung am Vertrag X vorgenommen haben.

X2 ist kein X im Sinne von Liskov , daher sollten Sie über andere Möglichkeiten nachdenken, um die Bedürfnisse von zu erfüllen Ihre Stakeholder (wie die Einführung einer neuen Spezifikation Y, die von X2 implementiert wird).

Für tiefere Einblicke siehe Pieter Hinjens: Das Ende der Softwareversionen oder Rich Hickey Simple Made Easy .

Aus der Sicht von A gibt es eine Voraussetzung , dass der Mitarbeiter den Vertrag X respektiert. Und Ihre Beobachtung ist effektiv, dass der isolierte Test für A Ihnen keine Sicherheit gibt, dass A Mitarbeiter erkennt, die gegen den Vertrag X verstoßen X Vertrag.

Review Integrierte Tests sind ein Betrug ; Auf hohem Niveau wird erwartet, dass Sie so viele isolierte Tests haben, wie Sie benötigen, um sicherzustellen, dass X2 den Vertrag X korrekt implementiert, und so viele isolierte Tests, wie Sie benötigen, um sicherzustellen, dass A das Richtige tut, wenn interessante Antworten von einem X und gegeben werden eine kleinere Anzahl von integrierten Tests, um sicherzustellen, dass X2 und A übereinstimmen, was X bedeutet.

Sie werden diese Unterscheidung manchmal ausgedrückt als einzeln tests vs sociable tests; siehe Jay Fields Effektiv mit Unit-Tests arbeiten .

Sollten wir uns nicht mehr auf Integrationstests konzentrieren?

Siehe auch integrierte Tests sind ein Betrug - Rainsberger beschreibt ausführlich eine positive Rückkopplungsschleife, die (nach seinen Erfahrungen) bei Projekten üblich ist, die auf integrierten Tests (Rechtschreibprüfung) beruhen. Zusammenfassend lässt sich sagen, dass sich die Qualität verschlechtert, ohne dass die isolierten/einsamen Tests Druck auf das Design ausüben , was zu mehr Fehlern und mehr integrierten Tests führt.

Sie benötigen auch (einige) Integrationstests. Zusätzlich zu der Komplexität, die durch mehrere Module verursacht wird, hat die Ausführung dieser Tests tendenziell mehr Widerstand als die isolierten Tests. Es ist effizienter, sehr schnelle Überprüfungen während der Arbeit durchzuführen, und die zusätzlichen Überprüfungen zu speichern, wenn Sie glauben, "fertig" zu sein.

73
VoiceOfUnreason

Lassen Sie mich zunächst sagen, dass die Kernprämisse der Frage fehlerhaft ist.

Sie testen (oder verspotten) niemals Implementierungen, Sie testen (und verspotten) Schnittstellen.

Wenn ich eine echte Klasse X habe, die die Schnittstelle X1 implementiert, kann ich ein Schein-XM schreiben, das auch X1 entspricht. Dann muss meine Klasse A etwas verwenden, das X1 implementiert, das entweder Klasse X oder Schein-XM sein kann.

Angenommen, wir ändern X, um eine neue Schnittstelle X2 zu implementieren. Nun, offensichtlich wird mein Code nicht mehr kompiliert. A erfordert etwas, das X1 implementiert und nicht mehr existiert. Das Problem wurde identifiziert und kann behoben werden.

Angenommen, anstatt X1 zu ersetzen, ändern wir es einfach. Jetzt ist Klasse A fertig. Das Schein-XM implementiert jedoch nicht mehr die Schnittstelle X1. Das Problem wurde identifiziert und kann behoben werden.


Die gesamte Basis für Unit-Tests und Verspottungen besteht darin, dass Sie Code schreiben, der Schnittstellen verwendet. Einem Konsumenten einer Schnittstelle ist es egal wie der Code wird implementiert, nur dass derselbe Vertrag eingehalten wird (Ein-/Ausgänge).

Dies bricht zusammen, wenn Ihre Methoden Nebenwirkungen haben, aber ich denke, dass dies sicher ausgeschlossen werden kann, da "nicht Unit-getestet oder verspottet werden kann".

15
Vlad274

Nehmen Sie Ihre Fragen der Reihe nach:

welchen Wert hat Unit Testing dann?

Sie sind billig zu schreiben und auszuführen und Sie erhalten frühzeitig Feedback. Wenn Sie X brechen, werden Sie mehr oder weniger sofort herausfinden, ob Sie gute Tests haben. Denken Sie nicht einmal daran, Integrationstests zu schreiben, es sei denn, Sie haben alle Ebenen auf Einheit getestet (ja, sogar in der Datenbank).

Sagt es Ihnen wirklich nur, dass Sie, wenn alle Tests bestanden sind, keine bahnbrechende Änderung eingeführt haben?

Bestehende Tests können Ihnen tatsächlich nur sehr wenig sagen. Möglicherweise haben Sie nicht genügend Tests geschrieben. Möglicherweise haben Sie nicht genügend Szenarien getestet. Code-Coverage kann hier helfen, ist aber keine Wunderwaffe. Möglicherweise haben Sie Tests, die immer bestehen. Daher ist Rot der oft übersehene erste Schritt des roten, grünen Refaktors.

Und wenn sich das Verhalten einer Klasse (freiwillig oder unfreiwillig) ändert, wie können Sie alle Konsequenzen (vorzugsweise auf automatisierte Weise) erkennen?

Mehr Tests - obwohl die Tools immer besser werden. ABER Sie sollten das Klassenverhalten in einer Schnittstelle definieren (siehe unten). N.B. Auf der Testpyramide ist immer ein Platz für manuelle Tests vorhanden.

Sollten wir uns nicht mehr auf Integrationstests konzentrieren?

Immer mehr Integrationstests sind auch nicht die Antwort, sie sind teuer zu schreiben, auszuführen und zu warten. Abhängig von Ihrem Build-Setup kann Ihr Build-Manager sie trotzdem ausschließen, sodass sie darauf angewiesen sind, dass sich ein Entwickler daran erinnert (niemals eine gute Sache!).

Ich habe gesehen, wie Entwickler stundenlang versucht haben, fehlerhafte Integrationstests zu beheben, die sie in fünf Minuten gefunden hätten, wenn sie gute Komponententests gehabt hätten. Andernfalls führen Sie einfach die Software aus - das ist alles, was Ihre Endbenutzer interessieren. Es macht keinen Sinn, Millionen Unit-Tests durchzuführen, die bestehen, wenn das gesamte Kartenhaus herunterfällt, wenn der Benutzer die gesamte Suite ausführt.

Wenn Sie sicherstellen möchten, dass Klasse A Klasse X auf dieselbe Weise verwendet, sollten Sie eine Schnittstelle anstelle einer Konkretion verwenden. Dann ist es wahrscheinlicher, dass eine brechende Änderung zur Kompilierungszeit erfasst wird.

9
Robbie Dee

Das ist richtig.

Unit-Tests dienen dazu, die isolierte Funktionalität einer Einheit zu testen. Auf den ersten Blick wird überprüft, ob sie wie beabsichtigt funktioniert und keine dummen Fehler enthält.

Unit-Tests sind nicht dazu da, um zu testen, ob die gesamte Anwendung funktioniert.

Was viele Leute vergessen, ist, dass Unit-Tests nur das schnellste und schmutzigste Mittel sind, um Ihren Code zu validieren. Sobald Sie wissen, dass Ihre kleinen Routinen funktionieren, müssen Sie auch Integrationstests ausführen. Unit-Tests an sich sind nur unwesentlich besser als keine Tests.

Der Grund, warum wir überhaupt Unit-Tests haben, ist, dass sie billig sein sollen. Schnell zu erstellen, auszuführen und zu warten. Sobald Sie anfangen, sie in minimale Integrationstests umzuwandeln, befinden Sie sich in einer Welt voller Schmerzen. Sie können auch den vollständigen Integrationstest durchführen und den Komponententest ganz ignorieren, wenn Sie dies tun.

einige Leute denken, dass eine Einheit nicht nur eine Funktion in einer Klasse ist, sondern die gesamte Klasse selbst (ich selbst eingeschlossen). Dies erhöht jedoch nur die Größe des Geräts, sodass Sie möglicherweise weniger Integrationstests benötigen, diese aber dennoch benötigen. Es ist immer noch unmöglich zu überprüfen, ob Ihr Programm das tut, was es tun soll, ohne eine vollständige Integrationstestsuite.

anschließend müssen Sie noch die vollständigen Integrationstests auf einem Live- (oder Semi-Live-) System ausführen, um zu überprüfen, ob es unter den vom Kunden verwendeten Bedingungen funktioniert.

9
gbjbaanb

Unit-Tests beweisen nicht die Richtigkeit von irgendetwas. Dies gilt für alle Tests. In der Regel werden Unit-Tests mit vertragsbasiertem Design (Design by Contract ist eine andere Art, es auszudrücken) und möglicherweise automatisierten Korrektheitsnachweisen kombiniert, wenn die Korrektheit regelmäßig überprüft werden muss.

Wenn Sie echte Verträge haben, die aus Klasseninvarianten, Voraussetzungen und Nachbedingungen bestehen, können Sie die Korrektheit hierarchisch nachweisen, indem Sie die Korrektheit von Komponenten höherer Ebene auf die Verträge von Komponenten niedrigerer Ebene stützen. Dies ist das grundlegende Konzept hinter Design by Contract.

2
Frank Hileman

Ich finde stark verspottete Tests selten nützlich. Die meiste Zeit implementiere ich das Verhalten, das die ursprüngliche Klasse bereits hat, neu, was den Zweck des Verspottens völlig zunichte macht.

Mir. Eine bessere Strategie ist eine gute Trennung der Bedenken (z. B. können Sie Teil A Ihrer App testen, ohne die Teile B bis Z einzubringen). Eine so gute Architektur hilft wirklich, einen guten Test zu schreiben.

Außerdem bin ich mehr als bereit, Nebenwirkungen zu akzeptieren, solange ich sie zurückdrehen kann, z. Wenn meine Methode Daten in der Datenbank ändert, lassen Sie es! Was schadet es, wenn ich die Datenbank auf den vorherigen Zustand zurücksetzen kann? Es gibt auch den Vorteil, dass mein Test prüfen kann, ob die Daten wie erwartet aussehen. In-Memory-DBs oder bestimmte Testversionen von dbs helfen hier wirklich (z. B. RavenDBs In-Memory-Testversion).

Schließlich verspotte ich gerne Dienstgrenzen, z. Führen Sie diesen http-Aufruf nicht an Service b aus, sondern fangen Sie ihn ab und führen Sie einen geeigneten ein

2
Christian Sauer

Ich wünschte, die Leute in beiden Lagern würden verstehen, dass Klassentests und Verhaltenstests nicht orthogonal sind.

Klassentests und Unit-Tests werden synonym verwendet und sollten es vielleicht auch nicht sein. Einige Unit-Tests werden zufällig in Klassen implementiert. Das ist alles. Unit-Tests finden seit Jahrzehnten in Sprachen ohne Unterricht statt.

Das Testen von Verhaltensweisen ist innerhalb von Klassentests mit dem GWT-Konstrukt durchaus möglich.

Darüber hinaus hängt es eher von Ihren Prioritäten ab, ob Ihre automatisierten Tests nach Klassen- oder Verhaltenslinien ablaufen. Einige müssen schnell Prototypen erstellen und etwas aus der Tür holen, während andere aufgrund von internen Stilen Einschränkungen hinsichtlich der Abdeckung haben. Viele Gründe. Sie sind beide vollkommen gültige Ansätze. Sie bezahlen Ihr Geld, Sie treffen Ihre Wahl.

Was tun, wenn der Code kaputt geht? Wenn es für eine Schnittstelle codiert wurde, muss sich nur die Konkretion ändern (zusammen mit allen Tests).

Die Einführung eines neuen Verhaltens muss das System jedoch überhaupt nicht beeinträchtigen. Linux et al. Sind voll von veralteten Funktionen. Und Dinge wie Konstruktoren (und Methoden) können glücklicherweise überladen werden, ohne dass sich der gesamte aufrufende Code ändern muss.

Wenn Klassentests gewinnen, müssen Sie eine Änderung an einer Klasse vornehmen, die noch nicht aktiviert wurde (aufgrund von Zeitbeschränkungen, Komplexität oder was auch immer). Es ist viel einfacher, mit einer Klasse zu beginnen, wenn sie umfassende Tests enthält.

1
Belgian Dog

Sofern sich die Schnittstelle für X nicht geändert hat, müssen Sie den Komponententest für A nicht ändern, da sich nichts in Bezug auf A geändert hat. Es hört sich so an, als hätten Sie wirklich einen Unit-Test von X und A zusammen geschrieben, aber es einen Unit-Test von A genannt:

Wenn Sie Unit-Tests für A schreiben, verspotten Sie X. Mit anderen Worten, während Sie Unit-Tests für A durchführen, setzen Sie das Post-Verhalten von X auf X1.

Idealerweise sollte der Mock von X alles Mögliche Verhalten von X simulieren, nicht nur das Verhalten, das Sie erwarten von X. Egal was Sie tatsächlich in X implementieren, A sollte es bereits sein in der Lage sein, damit umzugehen. Daher hat keine Änderung von X außer der Änderung der Schnittstelle selbst Auswirkungen auf den Komponententest für A.

Beispiel: Angenommen, A ist ein Sortieralgorithmus und A liefert zu sortierende Daten. Das Modell von X sollte einen Null-Rückgabewert, eine leere Datenliste, ein einzelnes Element, mehrere bereits sortierte Elemente, mehrere noch nicht sortierte Elemente, mehrere rückwärts sortierte Elemente, Listen mit demselben wiederholten Element, vermischte Nullwerte, eine lächerliche, liefern große Anzahl von Elementen, und es sollte auch eine Ausnahme auslösen.

Vielleicht hat X zunächst montags sortierte Daten und dienstags leere Listen zurückgegeben. Aber jetzt, wo X montags unsortierte Daten zurückgibt und dienstags Ausnahmen auslöst, ist es A egal - diese Szenarien wurden bereits im Unit-Test von A behandelt.

0
Moby Disk