Wie gehe ich mit dem Bestehen von Tests von Anfang an in TDD um?


8

Ich versuche, TDD in meinem persönlichen Projekt zu üben, und ich frage mich, wie ich mit der Situation umgehen soll, wenn sie nach dem Hinzufügen eines neuen Tests von Anfang an auf der Grundlage der vorhandenen Implementierung bestanden wird.

Einerseits kann der neue Test eine zusätzliche Dokumentation des Entwurfs und einen Schutz vor versehentlichen Verstößen gegen Annahmen bieten.

Wenn andererseits der Test ohne Codeänderung bestanden wird, ist es "verdächtig", ob er wirklich testet, was er sollte oder nicht.

Was kann grundsätzlich getan werden, um die Richtigkeit des Tests zu bestätigen, der bereits implementiertes Verhalten bestätigt?


5
Mein Lieblingsansatz ist das Testen von Mutationen. Ich ändere den getesteten Code und überprüfe, ob der Test fehlschlägt
Mücke

2
@gnat - Ich glaube, es gibt einige Überschneidungen, aber ich glaube nicht, dass es die gleiche Frage ist. Hier frage ich speziell nach TDD und ich glaube, dass "Wenn Sie sich in einer solchen Situation befinden, dann machen Sie TDD falsch, weil ..." eine gültige Antwort wäre.
AGrzes

5
Es spielt keine Rolle, weil Sie TDD nicht bereits ausführen, wenn Sie einen Test gegen eine bereits vorhandene (und ordnungsgemäß funktionierende) Implementierung schreiben
Mücke

2
"und was machst du, wenn du darauf stößt?" Ich führe den neuen Test (nur diesen) mit einem Coverage-Tool aus und überprüfe, ob der erwartete Pfad ausgeführt wird. Ein Rollback ist nur möglich, wenn Sie das Projekt für das Training durchführen. Übrigens: Der Global Day of Coderetreat ist eine großartige Gelegenheit, TDD zu üben ...
Timothy Truckle

2
@ Solomonoff'sSecret persönlich, ich vertraue keinem Test, den ich nicht gesehen habe, fehlgeschlagen.
RubberDuck

Antworten:


13

Das Bestehen eines Tests von dem Moment an, an dem Sie ihn geschrieben haben, könnte ein Problem mit Ihrem TDD-Prozess sein, bedeutet jedoch nicht, dass er von sich aus falsch ist.

Ihr Test könnte zufällig bestehen.

Angenommen, Sie haben eine Methode, mit der die Kosten für das Abheben von Geld über einen Geldautomaten zurückgegeben werden. Sie werden nun gebeten, die neue Regel hinzuzufügen, dass die Kosten 0 betragen, wenn der Karteninhaber über 60 Jahre alt ist. Wir testen sie also und erwarten, dass sie fehlschlägt:

assertTrue(ATM.withdrawalCost(clientOver60) == 0)

Sie können erwarten, dass dies fehlschlägt. Aber es geht vorbei, da der Kunde zufällig ein VIP-Kunde ist, der kostenlose Abhebungen hat. Jetzt können Sie zur RetractionCost-Methode zurückkehren und sie so ändern, dass sie fehlschlägt, aber das macht nicht wirklich Sinn. Schreiben Sie einen neuen Test, um zu zeigen, dass Ihr Code falsch ist:

assertTrue(ATM.withdrawalCost(nonVIPclientOver60) == 0)

Jetzt schlägt es fehl, Sie gehen und codieren, bis es vorbei ist, und wiederholen es dann, bis es fertig ist.

Sollten Sie den Test dann löschen, da er keinen Unterschied macht? Nein! Es beschreibt die erwartete Funktionalität der ATM-Auszahlungskostenmethode. Wenn Sie es löschen und sich eines Tages die 0 Kostenabhebungskosten für VIP-Kunden ändern, sollte die erste Behauptung immer noch wahr sein.

Trotzdem sollten Sie für eine ordnungsgemäße TDD nicht vor Ihren Tests Dinge codieren und dann die Dinge testen, von denen Sie wissen, dass Sie sie bestehen werden. Ich halte das nicht für den Fall, nach dem Sie fragen.

Ich glaube, der Fail-Code-Pass-Zyklus soll die getriebene Entwicklung "Ich schreibe diese 3 Tests, die fehlschlagen werden, und diese 2, die bestehen werden, weil ich sie bereits codiert habe und weiß, was der Test sein wird" vermeiden. Sie sollten wissen, dass manche Menschen sich anders fühlen könnten. Hören Sie ihre Gründe, sie könnten auch gültig sein.


10

Tests, die von Anfang an bestanden werden, treten normalerweise auf, wenn etwas allgemeiner implementiert wird, als es für die vorliegenden Tests tatsächlich erforderlich ist. Dies ist ganz normal : Unit-Tests können nur eine kleine, endliche Anzahl von Eingabewerten für eine bestimmte Funktion liefern, aber die meisten Funktionen sind für einen großen Bereich möglicher Eingabewerte geschrieben. Oft ist eine Implementierung, die speziell für die aktuellen Testfälle entwickelt wurde, komplizierter als eine allgemeinere Lösung. Wenn dies der Fall ist, wäre es umständlich und fehleranfällig, den Code künstlich so zu gestalten, dass er nur für die Testfälle funktioniert und für alles andere fehlschlägt.

Angenommen, Sie benötigen eine Funktion, um das Minimum einiger Werte aus einem bestimmten Array zurückzugeben. Sie haben eine Implementierung durchgeführt, die von einem Test mit einem Array gesteuert wurde, das nur einen oder zwei Werte enthält. Aber anstatt dies auf komplizierte Weise zu implementieren, indem Sie Vergleiche mit verschiedenen Elementen (möglicherweise nur den ersten beiden Elementen) durchführen, rufen Sie eine Minimum-Array-Funktion aus der Standardbibliothek Ihres Sprachökosystems auf und machen die Implementierung so zu einem Einzeiler . Wenn Sie jetzt einen Test mit einem Array mit fünf Elementen hinzufügen, wird der Test wahrscheinlich von Anfang an bestanden.

Aber woher weißt du dann, dass der Test aufgrund eines Fehlers im Test selbst nicht "grün" ist? Eine einfache und unkomplizierte Möglichkeit, dies zu erreichen, besteht darin, das zu testende Subjekt vorübergehend zu ändern , damit der Test fehlschlägt. Beispielsweise könnten Sie if (array.size()==5) return 123Ihrer Funktion absichtlich eine Zeile hinzufügen . Jetzt schlägt Ihr Fünf-Elemente-Test fehl, also wissen Sie

  • Der Test wird ausgeführt
  • Der Assert-Aufruf im Test wird ausgeführt
  • Der Assert-Aufruf im Test bestätigt das Richtige

Das sollte Ihnen ein gewisses Vertrauen in den Test geben. Wenn Sie gesehen haben, dass der Test fehlgeschlagen ist, machen Sie die Änderung rückgängig, und der Test sollte erneut bestanden werden.

Alternativ können Sie das erwartete Ergebnis eines Tests ändern: Angenommen, Ihr bestehender Test enthält eine Aussage wie

 int result = Subject.UnderTest(...);
 Assert.AreEqual(1,result);

Dann können Sie den Test bearbeiten und die "1" durch "2" ersetzen. Wenn der Test fehlschlägt (wie erwartet), wissen Sie, dass er ordnungsgemäß funktioniert, und Sie können den Austausch rückgängig machen und prüfen, ob der Test jetzt grün wird. Das Risiko, durch eine solche Ersetzung einen Fehler in den Test einzuführen, ist sehr gering, sodass dies für die meisten Fälle in der realen Welt wahrscheinlich akzeptabel ist.

Eine andere, möglicherweise umstrittene Methode besteht darin, einen Haltepunkt für den Test festzulegen und einen Debugger zu verwenden, um ihn zu durchlaufen. Dies sollte Ihnen auch ein gewisses Maß an Sicherheit geben, dass der Testcode tatsächlich ausgeführt wird, und Ihnen die Möglichkeit geben, den Pfad durch den Test durch schrittweise Überprüfung zu validieren. Es muss jedoch sehr darauf geachtet werden, Fehler in einem Codepfad nicht speziell für einen fehlgeschlagenen Test zu übersehen. Bei komplexen Tests können Sie beides in Betracht ziehen - künstliche Fehler verursachen und einen Debugger verwenden, um sie zu überprüfen.

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.