Sie haben eine Klasse X und schreiben einige Komponententests, die das Verhalten X1 verifizieren. Es gibt auch Klasse A, die X als Abhängigkeit nimmt.
Wenn Sie Komponententests für A schreiben, verspotten Sie X. Mit anderen Worten, wenn Sie Komponententests für A durchführen, setzen Sie das Verhalten von Xs Verspottung auf X1 (postulieren). Die Zeit vergeht, Menschen nutzen Ihr System, müssen sich ändern, X entwickelt sich weiter: Sie modifizieren X, um Verhalten X2 zu zeigen. 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 erkennt man, dass das Ergebnis von A anders ausfällt, wenn es mit dem "echten" (modifizierten) X ausgeführt wird?
Ich erwarte Antworten auf die Frage: "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 grundlegende Änderung eingeführt haben? Und wenn sich das Verhalten einer Klasse (freiwillig oder unfreiwillig) ändert, wie können Sie (am besten automatisiert) alle Konsequenzen erkennen? Sollten wir uns nicht mehr auf Integrationstests konzentrieren?
quelle
X1
, sagen Sie, dassX
die Schnittstelle implementiertX1
. Wenn Sie die SchnittstelleX1
aufX2
den in den anderen Tests verwendeten Mock ändern , sollte die Kompilierung nicht mehr möglich sein. Daher müssen Sie auch diese Tests korrigieren. Änderungen im Klassenverhalten sollten keine Rolle spielen. Tatsächlich sollte Ihre KlasseA
nicht von Implementierungsdetails abhängen (was Sie in diesem Fall ändern würden). Die Unit-Tests fürA
sind also immer noch korrekt und zeigen an, dass diesA
bei einer idealen Implementierung der Schnittstelle funktioniert.Antworten:
Machst du? Ich nicht, es sei denn, ich muss unbedingt. Ich muss wenn:
X
ist langsam oderX
hat NebenwirkungenWenn beides nicht zutrifft, werden auch meine Unit-Tests von
A
getestetX
. Etwas anderes zu tun, würde Isolationstests auf ein unlogisches Extrem heben.Wenn Sie Teile Ihres Codes haben, die Mocks anderer Teile Ihres Codes verwenden, würde ich zustimmen: Was ist der Sinn solcher Komponententests? Also mach das nicht. Lassen Sie diese Tests die realen Abhängigkeiten verwenden, da sie auf diese Weise weitaus wertvollere Tests bilden.
Und wenn sich einige Leute darüber aufregen, dass Sie diese Tests als "Komponententests" bezeichnen, bezeichnen Sie sie einfach als "automatisierte Tests" und schreiben Sie gute automatisierte Tests.
quelle
Du brauchst beides. Unit-Tests, um das Verhalten jeder Ihrer Units zu überprüfen, und einige Integrationstests, um sicherzustellen, dass sie korrekt 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 Einheitentests erforderlich sind, um alle Pfade vollständig abzudecken. Dann haben Sie eine weitere Klasse B, für die ebenfalls 10 Komponententests erforderlich sind, um alle Pfade abzudecken, die der Code durchlaufen 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 der Verwendung von Integrationstests. Nur integrierte Tests von JB Rainsberger sind ein Betrug in HD
quelle
Woah, warte einen Moment. Die Implikationen der Tests für X-Fehler sind zu wichtig, um sie so zu beschönigen.
Wenn die Implementierung von X von X1 in X2 die Komponententests für X unterbricht, bedeutet dies, dass Sie den Vertrag X rückwärts inkompatibel geändert haben.
X2 ist im Liskov-Sinne kein X, daher sollten Sie über andere Möglichkeiten nachdenken, um die Bedürfnisse Ihrer Stakeholder zu erfüllen (wie die Einführung einer neuen Spezifikation Y, die von X2 implementiert wird).
Weitere Informationen finden Sie unter Pieter Hinjens: Das Ende der Softwareversionen oder Rich Hickey Simple Made Easy .
Aus der Sicht von A gibt es eine Vorbedingung, dass der Mitarbeiter den Vertrag X respektiert. Und Ihre Beobachtung ist effektiv, dass der isolierte Test für A Ihnen keine Gewissheit gibt, dass A Mitarbeiter erkennt, die gegen den Vertrag X verstoßen.
Review Integrated Tests sind ein Betrug ; Auf hoher Ebene werden so viele Einzeltests erwartet, wie Sie benötigen, um sicherzustellen, dass X2 den Vertrag X korrekt implementiert, und so viele Einzeltests, wie Sie benötigen, um sicherzustellen, dass A bei interessanten Antworten von einem X das Richtige tut, und eine kleinere Anzahl integrierter Tests, um sicherzustellen, dass X2 und A übereinstimmen, was X bedeutet.
Sie werden manchmal diese Unterscheidung ausgedrückt siehe Einzeltests vs
sociable
Tests; Siehe Jay Fields Effektiv mit Unit-Tests arbeiten .Auch hier ist es ein Betrug, integrierte Tests zu sehen - Rainsberger beschreibt im Detail eine positive Rückkopplungsschleife, die (nach seinen Erfahrungen) für Projekte üblich ist, die auf integrierten Tests (Rechtschreibprüfung) beruhen. Zusammenfassend lässt sich sagen, dass sich die Qualität verschlechtert , ohne dass die isolierten 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 zu wiederholen, wenn die Arbeit ausgeführt wird, und die zusätzlichen Überprüfungen zu speichern, wenn Sie glauben, dass Sie "fertig" sind.
quelle
Lassen Sie mich zunächst sagen, dass die Kernprämisse der Frage fehlerhaft ist.
Sie testen (oder verspotten) Implementierungen nie, Sie testen (und verspotten) Schnittstellen .
Wenn ich eine echte Klasse X habe, die das Interface X1 implementiert, kann ich ein Mock-XM schreiben, das auch mit X1 kompatibel ist. Dann muss meine Klasse A etwas verwenden, das X1 implementiert, was entweder Klasse X oder Schein-XM sein kann.
Angenommen, wir ändern X, um eine neue Schnittstelle X2 zu implementieren. Nun, mein Code wird offensichtlich nicht mehr kompiliert. A erfordert etwas, das X1 implementiert und das nicht mehr existiert. Das Problem wurde identifiziert und kann behoben werden.
Angenommen, statt X1 zu ersetzen, ändern wir es einfach. Jetzt ist Klasse A fertig. Das Mock-XM implementiert jedoch die Schnittstelle X1 nicht mehr. Das Problem wurde identifiziert und kann behoben werden.
Die gesamte Grundlage für Unit-Tests und Verspottungen besteht darin, dass Sie Code schreiben, der Schnittstellen verwendet. Einem Benutzer einer Schnittstelle ist es egal, wie der Code implementiert wird, nur dass derselbe Vertrag eingehalten wird (Ein- / Ausgaben).
Dies bricht zusammen, wenn Ihre Methoden Nebenwirkungen haben, aber ich denke, das kann sicher ausgeschlossen werden, da "nicht Unit-getestet oder verspottet werden kann".
quelle
Nehmen Sie Ihre Fragen der Reihe nach:
Sie sind billig zu schreiben und laufen und Sie erhalten frühzeitig Feedback. Wenn Sie X brechen, werden Sie mehr oder weniger sofort herausfinden, ob Sie gute Tests haben. Erwägen Sie nicht einmal, Integrationstests zu schreiben, es sei denn, Sie haben alle Ebenen (ja, auch in der Datenbank) getestet.
Tests zu haben, die bestanden wurden, kann Ihnen eigentlich 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 Abhilfe schaffen, ist aber keine Wunderwaffe. Möglicherweise haben Sie Tests, die immer bestehen. Daher ist Rot der häufig übersehene erste Schritt von Rot, Grün und Refaktor.
Mehr Tests - obwohl die Tools immer besser werden. ABER Sie sollten Klassenverhalten in einer Schnittstelle definieren (siehe unten). Hinweis: Über der Testpyramide befindet sich immer ein Platz für manuelle Tests.
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 ohnehin ausschließen, so dass 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 benötigen. Es macht keinen Sinn, Millionen von Einheitentests zu bestehen, die bestanden werden, 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 Concretion verwenden. Dann ist es wahrscheinlicher, dass ein Breaking Change zur Kompilierungszeit erfasst wird.
quelle
Das ist richtig.
Unit-Tests dienen dazu, die isolierte Funktionalität einer Unit zu testen, um auf den ersten Blick zu überprüfen, ob sie wie beabsichtigt funktioniert und keine dummen Fehler enthält.
Unit-Tests dienen nicht dazu, die Funktionsfähigkeit der gesamten Anwendung zu testen.
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 Unit-Tests haben, ist, dass sie billig sein sollen. Schnell zu erstellen und 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 einen vollständigen Integrationstest durchführen und den Komponententest ignorieren, wenn Sie dies tun.
Jetzt denken manche Leute, dass eine Einheit nicht nur eine Funktion in einer Klasse ist, sondern die ganze Klasse selbst (ich selbst eingeschlossen). Dies führt jedoch lediglich zu einer Vergrößerung der Einheit, sodass möglicherweise weniger Integrationstests erforderlich sind, diese jedoch weiterhin benötigt werden. Es ist immer noch unmöglich zu überprüfen, ob Ihr Programm ohne eine vollständige Integrationstestsuite das tut, was es soll.
Anschließend müssen Sie den vollständigen Integrationstest auf einem Live-System (oder einem Semi-Live-System) ausführen, um zu überprüfen, ob er unter den vom Kunden verwendeten Bedingungen funktioniert.
quelle
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 zu sagen) und möglicherweise automatisierten Korrektheitsnachweisen kombiniert, wenn die Korrektheit regelmäßig überprüft werden muss.
Wenn Sie echte Verträge haben, die aus Klasseninvarianten, Vorbedingungen und Nachbedingungen bestehen, können Sie die Korrektheit hierarchisch nachweisen, indem Sie die Korrektheit übergeordneter Komponenten auf die Verträge untergeordneter Komponenten stützen. Dies ist das grundlegende Konzept für die Vertragsgestaltung.
quelle
fac(5) == 120
, haben Sie nicht bewiesen , dassfac()
in der Tat die Fakultät ihr Argument zurückgibt. Sie haben nur bewiesen, dassfac()
beim Übergeben die Fakultät 5 zurückgegeben wird5
. Und selbst das ist nicht sicher, wie esfac()
denkbar zurückkehren könnte42
stattdessen auf dem ersten Montag nach einer totalen Sonnenfinsternis in Timbuktu ... Das Problem hier ist, dass Sie nicht die Einhaltung durch Überprüfung einzelne Testeingänge unter Beweis stellen können, müssen Sie überprüfen alle möglichen Eingaben, und auch beweisen, dass Sie keine vergessen haben (wie das Lesen der Systemuhr).Ich finde stark verspottete Tests selten nützlich. Meistens reimplementiere ich das Verhalten, das die ursprüngliche Klasse bereits hat und das den Zweck des Verspottens völlig zunichte macht.
Eine bessere Strategie ist es, die Bedenken gut voneinander zu trennen (z. B. können Sie Teil A Ihrer App testen, ohne die Teile B bis Z einzubeziehen). Solch eine gute Architektur hilft wirklich, gute Tests zu schreiben.
Außerdem bin ich mehr als bereit, Nebenwirkungen zu akzeptieren, solange ich sie rückgängig machen kann, z. B. wenn meine Methode Daten in der Datenbank ändert, lassen Sie es! Wie schadet es, wenn ich die Datenbank auf den vorherigen Status zurücksetzen kann? Es gibt auch den Vorteil, dass mein Test überprüfen kann, ob die Daten wie erwartet aussehen. In-Memory-DBs oder spezielle Testversionen von dbs helfen hier wirklich weiter (zB RavenDBs In-Memory-Testversion).
Schließlich möchte ich mich über Dienstgrenzen lustig machen, z. B. diesen http-Aufruf an Dienst b nicht machen, sondern ihn abfangen und einen entsprechenden einführen
quelle
Ich wünschte, die Leute in beiden Lagern würden verstehen, dass Klassentests und Verhaltenstests nicht orthogonal sind.
Klassentests und Komponententests werden synonym verwendet und sollten es vielleicht nicht sein. Einige Unit-Tests werden zufällig in Klassen implementiert. Das ist alles. Unit-Tests werden seit Jahrzehnten in Sprachen ohne Unterricht durchgeführt.
Das Testen von Verhalten ist innerhalb von Klassentests mit dem GWT-Konstrukt problemlos möglich.
Darüber hinaus hängt es von Ihren Prioritäten ab, ob Ihre automatisierten Tests nach Klassen- oder Verhaltensregeln ablaufen. Einige müssen schnell Prototypen erstellen und etwas herausholen, während andere aufgrund von hausinternen Stilen Einschränkungen in Bezug auf die Abdeckung haben. Viele Gründe. Sie sind beide perfekt 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 Konkretisierung ändern (zusammen mit etwaigen Tests).
Die Einführung eines neuen Verhaltens muss jedoch das System überhaupt nicht gefährden. Linux et al. Sind voll von veralteten Funktionen. Und Dinge wie Konstruktoren (und Methoden) können problemlos überladen werden, ohne dass der gesamte aufrufende Code geändert werden muss.
Wenn Klassentests gewonnen haben, müssen Sie eine Klasse ändern, die noch nicht besetzt ist (aus Zeitgründen, wegen Komplexität oder was auch immer). Es ist viel einfacher, mit einer Klasse zu beginnen, wenn sie umfassende Tests enthält.
quelle
Sofern sich die Schnittstelle für X nicht geändert hat, müssen Sie den Komponententest für A nicht ändern, da sich nichts an 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 als Unit-Test von A bezeichnet:
Im Idealfall sollte der Mock von X alle möglichen Verhaltensweisen von X simulieren , nicht nur das Verhalten, das Sie von X erwarten. Unabhängig davon, was Sie tatsächlich in X implementieren, sollte A bereits in der Lage sein, damit umzugehen. Daher hat keine Änderung an X außer der Änderung der Schnittstelle selbst Auswirkungen auf den Komponententest für A.
Beispiel: Angenommen, A ist ein Sortieralgorithmus und A stellt zu sortierende Daten bereit. Der Mock von X sollte einen Null-Rückgabewert, eine leere Liste von Daten, ein einzelnes Element, mehrere bereits sortierte Elemente, mehrere nicht bereits sortierte Elemente, mehrere rückwärts sortierte Elemente, Listen mit demselben Element, die sich wiederholen, Null-Werte, die sich lächerlich vermischen, liefern große Anzahl von Elementen, und es sollte auch eine Ausnahme auslösen.
Vielleicht hat X anfangs 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.
quelle
Man muss sich verschiedene Tests ansehen.
Unit-Tests selbst testen nur X. Sie verhindern, dass Sie das Verhalten von X ändern, sichern jedoch nicht das gesamte System. Sie stellen sicher, dass Sie Ihre Klasse umgestalten können, ohne dass sich das Verhalten ändert. Und wenn Sie X brechen, haben Sie es gebrochen ...
A sollte in der Tat X für seine Unit-Tests verspotten und der Test mit dem Mock sollte auch nach dem Ändern weiter bestehen bleiben.
Es gibt jedoch mehr als eine Teststufe! Es gibt auch Integrationstests. Diese Tests dienen dazu, die Interaktion zwischen den Klassen zu validieren. Diese Tests haben in der Regel einen höheren Preis, da sie nicht für alles Mocks verwenden. Beispielsweise könnte ein Integrationstest tatsächlich einen Datensatz in eine Datenbank schreiben, und ein Komponententest sollte keine externen Abhängigkeiten aufweisen.
Auch wenn X ein neues Verhalten haben muss, ist es besser, eine neue Methode bereitzustellen, die das gewünschte Ergebnis liefert
quelle