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


98

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?



36
Zusätzlich zu allen vorgeschlagenen Antworten muss ich sagen, dass ich die folgende Aussage beanstande: "Sagt Ihnen das wirklich nur , dass Sie, wenn alle Tests bestanden sind, keine bahnbrechende Änderung eingeführt haben?" Wenn Sie wirklich der Meinung sind, dass das Entfernen der Angst vor Umgestaltung wenig Sinn macht, sind Sie auf dem schnellen Weg, unhaltbaren Code zu schreiben
Crizzis

5
Unit-Tests zeigen Ihnen, ob sich Ihre Code-Einheit so verhält, wie Sie es erwarten. Nicht mehr oder weniger. Mocks und Test-Doubles bieten eine künstliche, kontrollierte Umgebung, in der Sie Ihre Codeeinheit (isoliert) testen können, um festzustellen, ob sie Ihren Erwartungen entspricht. Nicht mehr oder weniger.
Robert Harvey

2
Ich glaube, deine Prämisse ist falsch. Wenn Sie erwähnen X1, sagen Sie, dass Xdie Schnittstelle implementiert X1. Wenn Sie die Schnittstelle X1auf X2den 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 Klasse Anicht von Implementierungsdetails abhängen (was Sie in diesem Fall ändern würden). Die Unit-Tests für Asind also immer noch korrekt und zeigen an, dass dies Abei einer idealen Implementierung der Schnittstelle funktioniert.
Bakuriu

5
Ich weiß nichts über dich, aber wenn ich ohne Tests an einer Codebasis arbeiten muss, habe ich Todesangst, ich werde etwas kaputt machen. Und warum? Weil es so oft vorkommt, dass etwas kaputt geht, wenn es nicht dafür gedacht ist. Und segne die Herzen unserer Tester, sie können nicht alles testen. Oder sogar in der Nähe. Aber ein Komponententest tuckert glücklich durch die langweilige Routine nach der langweiligen Routine.
CorsiKa

Antworten:


125

Wenn Sie Komponententests 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 auch meine Unit-Tests von Agetestet X. 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.


94
@Laiv, Nein, Unit-Tests sollen als Unit fungieren, dh isoliert von anderen Tests ausgeführt werden . Knoten und Grafiken können eine Wanderung machen. Wenn ich in kurzer Zeit einen isolierten, nebenwirkungsfreien End-to-End-Test durchführen kann, ist das ein Unit-Test. Wenn Ihnen diese Definition nicht gefällt, nennen Sie sie einen automatisierten Test und hören Sie auf, Misttests zu schreiben, um der dummen Semantik zu entsprechen.
David Arno

9
@ DavidArno Es gibt leider eine sehr breite Definition von isoliert. Einige möchten, dass eine "Einheit" die mittlere Schicht und die Datenbank enthält. Sie können glauben, was auch immer sie wollen, aber die Chancen stehen gut, dass sich bei einer Entwicklung jeder Größe die Räder in relativ kurzer Zeit lösen, da der Build-Manager sie wegwirft. Im Allgemeinen ist dies in Ordnung, wenn sie für die Assembly (oder eine gleichwertige Instanz) isoliert sind. Hinweis: Wenn Sie mit Interfaces codieren, ist es viel einfacher, später Mocking und DI hinzuzufügen.
Robbie Dee

13
Sie befürworten einen anderen Test als die Beantwortung der Frage. Welches ist ein gültiger Punkt, aber dies ist eine ziemlich hinterhältige Art und Weise, es zu tun.
Phil Frost

17
@PhilFrost, um mich selbst zu zitieren: " Und wenn einige Leute sich darüber aufregen, dass Sie diese Tests" Komponententests "nennen, nennen Sie sie einfach" automatisierte Tests "und schreiben Sie gute automatisierte Tests. " Schreiben Sie nützliche Tests, keine dummen Tests das trifft einfach eine zufällige Definition eines Wortes. Oder akzeptieren Sie alternativ, dass möglicherweise Ihre Definition von "Komponententest" falsch ist und Sie keine Mocks mehr verwenden, weil Sie es falsch haben. In jedem Fall erhalten Sie bessere Tests.
David Arno

30
Ich bin mit @DavidArno in dieser Sache; Meine Teststrategie hat sich geändert, nachdem ich diesen Vortrag von Ian Cooper gesehen habe: vimeo.com/68375232 . Um es auf einen einzigen Satz zu bringen: Testen Sie keine Klassen . Verhalten testen . Ihre Tests sollten keine Kenntnisse der internen Klassen / Methoden haben, die zur Implementierung des gewünschten Verhaltens verwendet werden. Sie sollten nur die öffentliche Oberfläche Ihrer API / Bibliothek kennen und diese testen. Wenn die Tests über zu viel Wissen verfügen, testen Sie Implementierungsdetails und Ihre Tests werden spröde, an Ihre Implementierung gekoppelt und tatsächlich nur ein Anker um den Hals.
Richiban

79

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


1
Ich bin sicher, es ist kein Zufall, dass das Fragezeichen über die Wirksamkeit von Integrationstests mit Unit-Tests einhergegangen ist, die alle weiteren Schichten abdecken.
Robbie Dee

16
Richtig, aber nirgendwo müssen Sie sich über Ihre 20 Testeinheiten lustig machen. Wenn Sie Ihre 10 Tests von A haben, die alle von A abdecken, und Ihre 10 Tests, die alle von B abdecken und außerdem 25% von A als Bonus wiederholen, scheint dies zwischen "gut" und einer guten Sache zu liegen. Das Verspotten von A in Bs-Tests erscheint aktiv dumm (es sei denn, es gibt wirklich einen Grund, warum A ein Problem ist, z. B. die Datenbank oder ein langes Netz von anderen Dingen)
Richard Tingle

9
Ich bin mit der Vorstellung nicht einverstanden, dass ein einziger Integrationstest ausreicht, wenn Sie eine vollständige Abdeckung wünschen. Die Reaktionen von B auf die Ausgaben von A hängen von der Ausgabe ab. Wenn das Ändern eines Parameters in A seine Ausgabe ändert, kann es sein, dass B nicht richtig damit umgeht.
Matthieu M.

3
@ Eternal21: Mein Punkt war, dass manchmal das Problem nicht im individuellen Verhalten liegt, sondern in einer unerwarteten Interaktion. Das heißt, wenn sich der Klebstoff zwischen A und B in einem bestimmten Szenario unerwartet verhält. Also handeln sowohl A als auch B gemäß den Spezifikationen, und der glückliche Fall funktioniert, aber bei einigen Eingaben gibt es einen Fehler im Klebercode ...
Matthieu M.

1
@MatthieuM. Ich würde behaupten, dies würde den Rahmen von Unit Testing sprengen. Der Klebercode kann selbst in der Einheit getestet werden, während die Interaktion zwischen A und B über den Klebercode ein Integrationstest ist. Wenn bestimmte Randfälle oder Fehler gefunden werden, können sie zu den Glue-Code-Unit-Tests hinzugefügt und schließlich in Integrationstests überprüft werden.
Andrew T Finnell

72

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.

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 sociableTests; Siehe Jay Fields Effektiv mit Unit-Tests arbeiten .

Sollten wir uns nicht mehr auf Integrationstests konzentrieren?

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.


8
Dies sollte die akzeptierte Antwort sein. Die Frage beschreibt eine Situation, in der das Verhalten einer Klasse inkompatibel geändert wurde, aber äußerlich immer noch identisch aussieht. Das Problem liegt hier im Design der Anwendung, nicht in den Komponententests. Die Art und Weise, wie dieser Umstand beim Testen aufgefangen wird, ist die Verwendung eines Integrationstests zwischen diesen beiden Klassen.
Nick Coad

1
"Ohne die isolierten / einsamen Tests, die Druck auf das Design ausüben, verschlechtert sich die Qualität." Ich denke, das ist ein wichtiger Punkt. Neben Verhaltensüberprüfungen haben Unit-Tests den Nebeneffekt, dass Sie ein modulareres Design benötigen.
MickaëlG

Ich nehme an, das ist alles wahr, aber wie hilft mir das, wenn eine externe Abhängigkeit eine rückwärts inkompatible Änderung des Vertrags X einführt? Vielleicht bricht eine I / O-Klasse in einer Bibliothek die Kompatibilität, und wir machen uns über X lustig, weil wir nicht möchten, dass Komponententests in CI von starken I / O-Vorgängen abhängen. Ich glaube, das OP bittet darum, dies zu testen, und ich verstehe nicht, wie dies die Frage beantwortet. Wie kann man das testen?
Gerrit

15

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".


11
Diese Antwort enthält viele Annahmen, die nicht zutreffen müssen. Erstens wird grob vorausgesetzt, dass wir uns in C # oder Java befinden (oder genauer gesagt, dass wir in einer kompilierten Sprache sind, dass die Sprache Schnittstellen hat und dass X eine Schnittstelle implementiert; keine davon muss wahr sein). Zweitens wird davon ausgegangen, dass jede Änderung des Verhaltens oder "Vertrags" von X eine Änderung der von X implementierten Schnittstelle (wie vom Compiler verstanden) erfordert . Dies ist eindeutig nicht wahr, auch wenn wir sind in Java oder C #; Sie können eine Methodenimplementierung ändern, ohne die Signatur zu ändern.
Mark Amery

6
@MarkAmery Es ist wahr, dass die "Interface" -Terminologie spezifischer für C # oder Java ist, aber ich denke, der springende Punkt liegt in der Annahme eines definierten "Vertrags" des Verhaltens (und wenn das nicht kodifiziert ist, ist es unmöglich, dies automatisch zu erkennen). Sie haben auch völlig Recht, dass eine Implementierung ohne Vertragsänderung geändert werden kann. Eine Änderung der Implementierung ohne Änderung der Benutzeroberfläche (oder des Vertrags) sollte jedoch keine Auswirkungen auf den Verbraucher haben. Wenn das Verhalten von A davon abhängt, wie die Schnittstelle (oder der Vertrag) implementiert ist, ist es unmöglich, einen Komponententest (sinnvoll) durchzuführen.
Vlad274

1
"Sie haben auch völlig Recht, dass eine Implementierung geändert werden kann, ohne den Vertrag zu ändern" - auch wenn dies nicht der Punkt ist, den ich anstrebte. Ich mache vielmehr eine Unterscheidung zwischen dem Vertrag (das Verständnis eines Programmierers für die Aufgabe eines Objekts, das möglicherweise in der Dokumentation angegeben ist) und der Schnittstelle (eine Liste von Methodensignaturen, die vom Compiler verstanden werden) und der Aussage, dass der Vertrag dies kann geändert werden, ohne die Schnittstelle zu ändern. Alle Funktionen mit der gleichen Signatur sind aus Sicht des Typsystems austauschbar, jedoch nicht in der Realität!
Mark Amery

4
@MarkAmery: Ich glaube nicht, dass Vlad das Wort "Schnittstelle" in demselben Sinne verwendet, wie Sie es verwenden. So wie ich die Antwort lese, handelt es sich nicht um Schnittstellen im engeren Sinne von C # / Java (dh eine Reihe von Methodensignaturen), sondern im allgemeinen Sinne des Wortes, wie es z. B. in den Begriffen "Anwendungsprogrammierschnittstelle" oder sogar "verwendet wird. Benutzeroberfläche". [...]
Ilmari Karonen

6
@IlmariKaronen Wenn Vlad "interface" verwendet, um "contract" zu bedeuten, und nicht im engen C # / Java-Sinne, dann die Anweisung "Nehmen wir an, wir ändern X, um eine neue Schnittstelle X2 zu implementieren. Nun, mein Code wird offensichtlich nicht mehr kompiliert. " ist einfach falsch, da Sie einen Vertrag ändern können, ohne Methodensignaturen zu ändern. Aber ehrlich gesagt denke ich, das Problem dabei ist, dass Vlad keine der beiden Bedeutungen konsequent verwendet, sondern sie miteinander verschmilzt - was dazu führt, dass behauptet wird, dass jede Änderung an Vertrag X1 notwendigerweise einen Kompilierungsfehler hervorruft, ohne zu bemerken , dass dies falsch ist .
Mark Amery

9

Nehmen Sie Ihre Fragen der Reihe nach:

Welchen Wert hat Unit Testing dann?

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.

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

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.

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

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.

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 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.


9

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.


2

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.


Unit-Tests beweisen nicht die Richtigkeit von irgendetwas . Nicht sicher, ob ich das verstehe, Unit-Tests überprüfen ihre eigenen Ergebnisse sicher? Oder war damit gemeint, dass ein Verhalten nicht korrekt nachgewiesen werden kann, da es mehrere Ebenen umfassen kann?
Robbie Dee

7
@RobbieDee Ich denke, meinte er , dass , wenn Sie testen fac(5) == 120, haben Sie nicht bewiesen , dass fac()in der Tat die Fakultät ihr Argument zurückgibt. Sie haben nur bewiesen, dass fac()beim Übergeben die Fakultät 5 zurückgegeben wird 5. Und selbst das ist nicht sicher, wie es fac()denkbar zurückkehren könnte 42stattdessen 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).
cmaster

1
@RobbieDee-Tests (einschließlich Unit-Tests) sind ein schlechter Ersatz, oft der beste verfügbare, für das eigentliche Ziel ein maschinengeprüfter Beweis. Berücksichtigen Sie den gesamten Statusraum der zu testenden Einheiten, einschließlich des Statusraums aller darin enthaltenen Komponenten oder Verspottungen. Wenn Sie keinen sehr eingeschränkten Statusbereich haben, können Tests diesen Statusbereich nicht abdecken. Eine vollständige Abdeckung wäre ein Beweis, aber dies ist nur für winzige Zustandsräume verfügbar, z. B. zum Testen eines einzelnen Objekts, das ein einzelnes veränderbares Byte oder eine 16-Bit-Ganzzahl enthält. Automatisierte Proofs sind weitaus wertvoller.
Frank Hileman

1
@cmaster Du hast den Unterschied zwischen einem Test und einem Beweis sehr gut zusammengefasst. Vielen Dank!
Frank Hileman

2

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


1

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.


Wo Sie konkretes haben, hätte ich Implementierung geschrieben . Ist das ein Wort aus einer anderen Sprache (ich denke, Französisch) oder gibt es einen wichtigen Unterschied zwischen Konkretisierung und Umsetzung ?
Peter Taylor

0

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:

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).

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.


Was ist, wenn X den Index in dem zurückgegebenen Array von foobar in foobarRandomNumber umbenannt hat? Wie kann ich damit rechnen? Wenn Sie meinen Standpunkt verstehen, handelt es sich im Grunde genommen um mein Problem. Ich habe eine zurückgegebene Spalte von secondName in Zuname umbenannt, eine klassische Aufgabe, aber mein Test wird es nie erfahren, da sie verspottet ist. Ich habe so ein komisches Gefühl, als ob viele Leute in dieser Frage so etwas noch nie probiert hätten, bevor sie einen Kommentar abgegeben haben
FantomX1

Der Compiler sollte diese Änderung erkannt und Ihnen einen Compilerfehler gemeldet haben. Wenn Sie so etwas wie Javascript verwenden, empfehle ich, entweder zu Typescript zu wechseln oder einen Compiler wie Babel zu verwenden, der dieses Zeug erkennt.
Moby Disk

Was ist, wenn ich Arrays in PHP, Java oder Javascript verwende, wenn Sie einen Index eines Arrays ändern oder entfernen, keiner dieser Sprachen, die Compiler Ihnen darüber mitteilen, wird der Index möglicherweise an der 36. durchdachten Nummer verschachtelt verschachtelte Ebene des Arrays, daher denke ich, Compiler ist nicht die Lösung dafür.
FantomX1

-2

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

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.