Wie kann man beim Erstellen von TDD und beim Schreiben eines Komponententests dem Drang widerstehen, beim Schreiben der ersten Iteration des zu testenden Implementierungscodes zu "schummeln"?
Zum Beispiel:
Lassen Sie mich das Faktorielle einer Zahl berechnen. Ich beginne mit einem Komponententest (mit MSTest), der ungefähr so aussieht:
[TestClass]
public class CalculateFactorialTests
{
[TestMethod]
public void CalculateFactorial_5_input_returns_120()
{
// Arrange
var myMath = new MyMath();
// Act
long output = myMath.CalculateFactorial(5);
// Assert
Assert.AreEqual(120, output);
}
}
Ich CalculateFactorial
führe diesen Code aus und er schlägt fehl, da die Methode nicht einmal existiert. Also, ich schreibe jetzt die erste Iteration des Codes das Verfahren unter Test zu implementieren, das Schreiben des Mindest Code erforderlich , um den Test zu bestehen.
Die Sache ist, ich bin immer wieder versucht, Folgendes zu schreiben:
public class MyMath
{
public long CalculateFactorial(long input)
{
return 120;
}
}
Technisch gesehen ist dies insofern korrekt, als es sich tatsächlich um den Mindestcode handelt, der erforderlich ist, um diesen bestimmten Testdurchlauf durchzuführen (grün zu werden), obwohl dies eindeutig ein "Betrug" ist, da nicht einmal versucht wird , die Funktion zum Berechnen einer Fakultät auszuführen. Natürlich wird der Refactoring-Teil jetzt zu einer Übung zum "Schreiben der richtigen Funktionalität" und nicht zu einem echten Refactoring der Implementierung. Offensichtlich schlägt das Hinzufügen zusätzlicher Tests mit anderen Parametern fehl und erzwingt ein Refactoring, aber Sie müssen mit diesem einen Test beginnen.
Meine Frage ist also, wie Sie das Gleichgewicht zwischen dem Schreiben des Mindestcodes zum Bestehen des Tests und dem Beibehalten des Funktionszustands und dem Erreichen des Ziels erreichen können.
quelle
Antworten:
Es ist absolut legitim. Rot, Grün, Refactor.
Der erste Test besteht.
Fügen Sie den zweiten Test mit einer neuen Eingabe hinzu.
Kommen Sie jetzt schnell zu Grün, und fügen Sie ein If-else hinzu, was gut funktioniert. Es vergeht, aber du bist noch nicht fertig.
Der dritte Teil von Red, Green, Refactor ist der wichtigste. Refactor zum Entfernen von Duplikaten . Sie werden jetzt Duplikate in Ihrem Code haben. Zwei Anweisungen, die Ganzzahlen zurückgeben. Die einzige Möglichkeit, diese Duplizierung zu entfernen, besteht darin, die Funktion korrekt zu codieren.
Ich sage nicht, schreibe es nicht richtig beim ersten Mal. Ich sage nur, dass es nicht betrügt, wenn Sie es nicht tun.
quelle
Es ist eindeutig ein Verständnis des Endziels und die Erreichung eines Algorithmus erforderlich, der dieses Ziel erreicht.
TDD ist kein Wundermittel für Design; Sie müssen immer noch wissen, wie Sie Probleme mit Code lösen können, und Sie müssen immer noch wissen, wie Sie dies auf einer höheren Ebene als ein paar Codezeilen tun können, um einen Testdurchlauf durchzuführen.
Ich mag die Idee von TDD, weil sie gutes Design fördert. Sie denken darüber nach, wie Sie Ihren Code so schreiben können, dass er testbar ist, und im Allgemeinen wird diese Philosophie den Code insgesamt zu einem besseren Design führen. Sie müssen jedoch noch wissen, wie Sie eine Lösung entwerfen.
Ich bevorzuge keine reduktionistischen TDD-Philosophien, die behaupten, Sie könnten eine Anwendung erweitern, indem Sie einfach die kleinste Menge Code schreiben, um einen Test zu bestehen. Ohne an Architektur zu denken, wird dies nicht funktionieren, und Ihr Beispiel beweist das.
Onkel Bob Martin sagt dies:
quelle
Eine sehr gute Frage ... und ich muss mich fast allen widersetzen, außer @Robert.
Schreiben
Für eine Fakultätsfunktion ist es Zeitverschwendung , einen Testdurchgang durchzuführen . Es ist weder ein "Betrug", noch folgt es wörtlich dem Rot-Grün-Refaktor. Es ist falsch .
Hier ist der Grund:
Die "Refactor" -Argumente sind falsch. wenn Sie zwei Testfälle für 5 und 6 haben, ist dieser Code immer noch falsch, weil Sie keine faktorielles Berechnung überhaupt :
Wenn wir dem 'refactor'-Argument wörtlich folgen , würden wir bei 5 Testfällen YAGNI aufrufen und die Funktion mithilfe einer Nachschlagetabelle implementieren:
Keiner von diesen berechnet tatsächlich etwas, das bist du . Und das ist nicht die Aufgabe!
quelle
Wenn Sie nur einen Komponententest geschrieben haben, ist die einzeilige Implementierung (
return 120;
) legitim. Eine Schleife schreiben, die den Wert von 120 berechnet - das wäre Betrug!Solche einfachen Ersttests sind eine gute Möglichkeit, Randfälle zu erkennen und einmalige Fehler zu vermeiden. Fünf ist eigentlich nicht der Eingabewert, mit dem ich beginnen würde.
Eine Faustregel, die hier nützlich sein könnte, lautet: null, eins, viele, viele . Null und Eins sind wichtige Randfälle für die Fakultät. Sie können mit Einzeilern implementiert werden. Der Testfall "viele" (zB 5!) Würde Sie dann zwingen, eine Schleife zu schreiben. Der Testfall "Lots" (1000 !?) könnte Sie zwingen, einen alternativen Algorithmus für die Verarbeitung sehr großer Zahlen zu implementieren.
quelle
factorial(5)
ein schlechter erster Test ist. Wir gehen von den einfachsten Fällen aus, und in jeder Iteration werden die Tests etwas spezifischer, sodass der Code etwas allgemeiner wird. Dies ist, was Onkel Bob nennt die Transformation Priorität Prämisse ( blog.8thlight.com/uncle-bob/2013/05/27/… )Solange Sie nur einen einzigen Test haben, ist der minimale Code, der zum Bestehen des Tests erforderlich ist, wirklich
return 120;
und Sie können ihn problemlos aufbewahren, solange Sie keine weiteren Tests haben.Auf diese Weise können Sie den weiteren Entwurf verschieben, bis Sie tatsächlich die Tests schreiben, die ANDERE Rückgabewerte dieser Methode ausüben.
Bitte denken Sie daran, dass der Test die lauffähige Version Ihrer Spezifikation ist. Wenn in dieser Spezifikation nur f (6) = 120 angegeben ist, passt das perfekt zur Rechnung.
quelle
Wenn Sie in der Lage sind, auf diese Weise zu "schummeln", deutet dies darauf hin, dass Ihre Komponententests fehlerhaft sind.
Anstatt die Fakultätsmethode mit einem einzelnen Wert zu testen, bestand der Test aus einer Reihe von Werten. Hier kann datengesteuertes Testen helfen.
Betrachten Sie Ihre Komponententests als Manifestation der Anforderungen - sie müssen gemeinsam das Verhalten der zu testenden Methode definieren. (Dies wird als verhaltensgetriebene Entwicklung bezeichnet - es ist die Zukunft
;-)
)Fragen Sie sich also: Wenn jemand die Implementierung in etwas Falsches ändern würde, bestanden Ihre Tests dann immer noch oder sagen Sie "Moment mal!"?
In Anbetracht dessen ist die entsprechende Implementierung technisch korrekt, wenn Ihr einziger Test der in Ihrer Frage war. Das Problem wird dann als schlecht definierte Anforderung angesehen.
quelle
case
Anweisungen hinzufügenswitch
, und Sie können nicht für jeden möglichen Ein- und Ausgang für das OP-Beispiel einen Test schreiben.Int64.MinValue
bis technisch testenInt64.MaxValue
. Die Ausführung würde viel Zeit in Anspruch nehmen, die Anforderung jedoch explizit definieren, ohne dass Fehler auftreten. Mit der gegenwärtigen Technologie ist dies nicht durchführbar (ich vermute, dass es in Zukunft häufiger vorkommt) und ich stimme zu, Sie könnten schummeln, aber ich denke, die OPs-Frage war keine praktische Frage (niemand würde tatsächlich so schummeln) in der Praxis), aber eine theoretische.Schreibe einfach mehr Tests. Schließlich wäre es kürzer zu schreiben
als
:-)
quelle
Das Schreiben von "Cheat" -Tests ist für ausreichend kleine Werte von "OK" in Ordnung. Der Test der Rückrufeinheit ist jedoch erst abgeschlossen, wenn alle Tests bestanden wurden und keine neuen Tests geschrieben werden können, die fehlschlagen . Wenn Sie wirklich eine CalculateFactorial-Methode haben möchten, die eine Reihe von if- Anweisungen enthält (oder noch besser eine große switch / case- Anweisung :-), können Sie dies tun, und da Sie mit einer Zahl mit fester Genauigkeit arbeiten, benötigen Sie den Code Dies zu implementieren ist endlich (obwohl wahrscheinlich ziemlich groß und hässlich und möglicherweise durch Compiler- oder Systembeschränkungen in Bezug auf die maximale Größe des Codes einer Prozedur begrenzt). An diesem Punkt, wenn Sie wirklichBestehen Sie darauf, dass die gesamte Entwicklung von einem Komponententest gesteuert werden muss. Sie können einen Test schreiben, bei dem der Code das Ergebnis in einer kürzeren Zeit berechnen muss, als dies durch Befolgen aller Zweige der if- Anweisung möglich ist.
Grundsätzlich kann TDD Sie beim Schreiben von Code unterstützen, der die Anforderungen korrekt implementiert , Sie jedoch nicht zum Schreiben von gutem Code zwingen . Das liegt an dir.
Teile und genieße.
quelle
Ich stimme dem Vorschlag von Robert Harvey zu 100% zu. Es geht nicht nur darum, Tests zu bestehen, sondern auch darum, das Gesamtziel im Auge zu behalten.
Als Lösung für Ihr Problem, dass "nur mit einer bestimmten Anzahl von Eingaben gearbeitet werden kann", würde ich die Verwendung datengesteuerter Tests wie der XUNIT-Theorie vorschlagen. Die Stärke dieses Konzepts besteht darin, dass Sie auf einfache Weise Spezifikationen für Ein- und Ausgänge erstellen können.
Für Factorials würde ein Test so aussehen:
Sie könnten sogar eine Testdaten-Bereitstellung implementieren (die zurückgibt
IEnumerable<Tuple<xxx>>
) und eine mathematische Invariante codieren, z. B. wenn Sie wiederholt durch n dividieren, erhalten Sie n-1).Ich finde, das ist eine sehr mächtige Art zu testen.
quelle
Wenn du immer noch schummeln kannst, reichen die Tests nicht aus. Schreibe mehr Tests! In Ihrem Beispiel werde ich versuchen, Tests mit den Eingaben 1, -1, -1000, 0, 10, 200 hinzuzufügen.
Dennoch, wenn Sie wirklich betrügen wollen, können Sie ein endloses Wenn-Dann schreiben. In diesem Fall kann nur die Codeüberprüfung helfen. Sie würden bald auf Abnahmetest gefangen ( geschrieben von einer anderen Person! )
Das Problem bei Unit-Tests ist, dass Programmierer sie manchmal als unnötige Arbeit ansehen. Die richtige Art und Weise, sie zu sehen, ist ein Werkzeug, mit dem Sie das Ergebnis Ihrer Arbeit korrigieren können. Wenn Sie also ein Wenn-Dann erstellen, wissen Sie unbewusst, dass andere Fälle zu berücksichtigen sind. Dies bedeutet, dass Sie weitere Tests schreiben müssen. Und so weiter und so fort, bis Sie feststellen, dass das Betrügen nicht funktioniert und es besser ist, nur den richtigen Weg zu codieren. Wenn Sie immer noch das Gefühl haben, nicht fertig zu sein, sind Sie nicht fertig.
quelle
Ich würde vorschlagen, dass Ihre Wahl des Tests nicht der beste Test ist.
Ich würde anfangen mit:
Fakultät (1) als erster Test,
Fakultät (0) als zweite
Fakultät (-ve) als dritte
und dann weiter mit nicht-trivialen Fällen
und beenden Sie mit einem Überlaufkasten.
quelle
-ve
??