Wenn ich in TDD einen Testfall schreibe, der ohne Änderung des Produktionscodes erfolgreich ist, was bedeutet das?

17

Dies sind die Regeln von Robert C. Martin für TDD :

  • Sie dürfen keinen Produktionscode schreiben, es sei denn, es wird ein fehlerhafter Einheitentest bestanden.
  • Sie dürfen nicht mehr von einem Komponententest schreiben, als zum Scheitern ausreicht. und Kompilierungsfehler sind Fehler.
  • Sie dürfen nicht mehr Seriencode schreiben, als ausreicht, um den einen fehlgeschlagenen Komponententest zu bestehen.

Wenn ich einen Test schreibe, der sich lohnt, aber ohne Änderung des Produktionscodes besteht:

  1. Bedeutet das, dass ich etwas falsch gemacht habe?
  2. Sollte ich in Zukunft vermeiden, solche Tests zu schreiben, wenn es geholfen werden kann?
  3. Soll ich diesen Test dort lassen oder entfernen?

Hinweis: Ich habe versucht , diese Frage hier zu stellen: Kann ich mit einem bestandenen Komponententest beginnen? Aber ich konnte die Frage bis jetzt nicht gut genug formulieren.

Daniel Kaplan
quelle
Die "Bowling Game Kata", auf die in dem von Ihnen zitierten Artikel verwiesen wird, hat als letzten Schritt einen sofort bestandenen Test.
jscs

Antworten:

21

Es heißt, dass Sie keinen Produktionscode schreiben können, es sei denn, es muss ein fehlerhafter Komponententest bestanden werden. Die Absicht der Regel ist, zu sagen: "Wenn Sie Produktionscode bearbeiten müssen, stellen Sie sicher, dass Sie zuerst einen Test dafür schreiben oder ändern."

Manchmal schreiben wir Tests, um eine Theorie zu beweisen. Der Test besteht und das widerlegt unsere Theorie. Wir entfernen den Test dann nicht. Wir könnten jedoch (da wir wissen, dass wir die Quellcodeverwaltung unterstützen) den Produktionscode brechen, um sicherzustellen, dass wir verstehen, warum er bestanden hat, als wir es nicht erwartet hatten.

Wenn sich herausstellt, dass es sich um einen gültigen und korrekten Test handelt und kein vorhandener Test dupliziert wird, lassen Sie ihn dort.

pdr
quelle
Die Verbesserung der Testabdeckung des vorhandenen Codes ist ein weiterer Grund, einen (hoffentlich) bestandenen Test zu schreiben.
Jack
13

Es bedeutet, dass entweder:

  1. Sie haben den Produktionscode geschrieben, der die gewünschte Funktion erfüllt, ohne zuvor den Test zu schreiben (eine Verletzung des "religiösen TDD"), oder
  2. Die Funktion, die Sie benötigen, wird vom Produktionscode bereits erfüllt, und Sie schreiben lediglich einen weiteren Komponententest, um diese Funktion abzudecken.

Die letztere Situation ist häufiger als Sie vielleicht denken. Nehmen wir an, Sie haben als ein völlig unspezifisches und triviales (aber dennoch veranschaulichendes) Beispiel den folgenden Komponententest geschrieben (Pseudocode, weil ich faul bin):

public void TestAddMethod()
{
    Assert.IsTrue(Add(2,3) == 5);
}

Denn alles, was Sie wirklich brauchen, ist das Ergebnis von 2 und 3 zusammen.

Ihre Implementierungsmethode wäre:

public int add(int x, int y)
{
    return x + y;
}

Angenommen, ich muss jetzt 4 und 6 addieren:

public void TestAddMethod2()
{
    Assert.IsTrue(Add(4,6) == 10);
}

Ich muss meine Methode nicht umschreiben, da sie bereits den zweiten Fall abdeckt.

Nehmen wir an, ich habe herausgefunden, dass meine Add-Funktion wirklich eine Zahl mit einer Obergrenze von 100 zurückgeben muss. Ich kann eine neue Methode schreiben, die dies testet:

public void TestAddMethod3()
{
    Assert.IsTrue(Add(100,100) == 100);
}

Und dieser Test wird jetzt fehlschlagen. Ich muss jetzt meine Funktion umschreiben

public int add(int x, int y)
{
    var a = x + y;
    return a > 100 ? 100 : a;
}

es passieren zu lassen.

Der gesunde Menschenverstand schreibt vor, dass wenn

public void TestAddMethod2()
{
    Assert.IsTrue(Add(4,6) == 10);
}

Wenn der Test bestanden wird, wird die Methode nicht absichtlich zum Fehlschlagen gebracht, nur damit Sie einen fehlgeschlagenen Test durchführen und neuen Code schreiben können, um den Test zu bestehen.

Robert Harvey
quelle
5
Wenn Sie den Beispielen von Martin vollständig gefolgt wären (und er schlägt dies nicht unbedingt vor), add(2,3)würden Sie buchstäblich 5 zurückgeben , um den Pass zu bestehen. Dann würden Sie den Test schreiben, für add(4,6)den Sie gezwungen wären, den Produktionscode zu schreiben, der ihn bestehen lässt, ohne gleichzeitig zu brechen add(2,3). Sie würden am Ende mit return x + y, aber Sie würden nicht mit ihm beginnen. In der Theorie. Natürlich mag Martin (oder vielleicht war es jemand anderes, an den ich mich nicht erinnere) solche Beispiele für die Erziehung, aber er erwartet nicht, dass Sie tatsächlich so trivialen Code schreiben.
Anthony Pegram
1
@tieTYT, im Allgemeinen, wenn ich mich an Martins Buch (e) richtig erinnere, würde der zweite Testfall normalerweise ausreichen, um die allgemeine Lösung für eine einfache Methode zu schreiben (und in Wirklichkeit würden Sie tatsächlich nur dafür sorgen, dass sie funktioniert) erstes Mal). Kein dritter nötig.
Anthony Pegram
2
@tieTYT, dann würden Sie so lange Tests schreiben, bis Sie dies getan haben. :)
Anthony Pegram
4
Es gibt eine dritte Möglichkeit, die Ihrem Beispiel widerspricht: Sie haben einen Doppeltest geschrieben. Wenn Sie TDD "religiös" folgen, dann ist eine neue bestandene Prüfung immer eine rote Fahne. Nach DRY sollten Sie niemals zwei Tests schreiben, die im Wesentlichen dasselbe testen.
Congusbongus
1
"Wenn Sie Martins Beispielen vollständig gefolgt wären (und er schlägt dies nicht unbedingt vor), würden Sie mit add (2,3) pass buchstäblich 5 zurückgeben. Hard-coded." - das ist das bisschen strenge TDD, das mich immer beschäftigt hat. Die Vorstellung, dass Sie Code schreiben, den Sie kennen, ist falsch in der Erwartung, dass ein zukünftiger Test daherkommt und ihn beweist. Was ist, wenn dieser zukünftige Test aus irgendeinem Grund nie geschrieben wird und Kollegen annehmen, dass "alle Tests grün" "alle Codes korrekt" implizieren?
Julia Hayward
2

Ihr Test bestanden, aber Sie sind nicht falsch. Ich denke, es ist passiert, weil der Produktionscode nicht von Anfang an TDD ist.

Nehmen wir an, kanonisches (?) TDD. Es gibt keinen Seriencode, aber ein paar Testfälle (das schlägt natürlich immer fehl). Wir fügen Produktionscode hinzu, um zu übergeben. Dann hören Sie hier auf, um weitere Fehlertestfälle hinzuzufügen. Fügen Sie erneut Produktionscode hinzu, um zu übergeben.

Mit anderen Worten, Ihr Test kann eine Art Funktionstest sein, kein einfacher TDD-Komponententest. Diese sind immer ein wertvolles Gut für die Produktqualität.

Ich persönlich mag solche totalitären, unmenschlichen Regeln nicht.

9dan
quelle
2

Eigentlich ist das gleiche Thema gestern Abend in einem Dojo aufgetaucht.

Ich habe schnell recherchiert. Das habe ich mir ausgedacht:

Grundsätzlich ist dies nach den TDD-Regeln nicht explizit verboten. Möglicherweise sind einige zusätzliche Tests erforderlich, um zu beweisen, dass eine Funktion für eine verallgemeinerte Eingabe korrekt funktioniert. In diesem Fall bleibt die TDD-Praxis nur für kurze Zeit bestehen. Beachten Sie, dass ein kurzes Verlassen der TDD-Praxis nicht unbedingt gegen die TDD-Regeln verstößt, solange in der Zwischenzeit kein Seriencode hinzugefügt wurde.

Zusätzliche Tests können geschrieben werden, solange sie nicht redundant sind. Eine gute Vorgehensweise wäre, Partitionstests für Äquivalenzklassen durchzuführen. Das heißt, für jede Äquivalenzklasse werden die Kantenfälle und mindestens ein Innenfall geprüft.

Ein Problem, das bei diesem Ansatz auftreten kann, besteht darin, dass bei einem erneuten Bestehen der Tests nicht sichergestellt werden kann, dass keine falsch positiven Ergebnisse vorliegen. Dies bedeutet, dass möglicherweise Tests bestanden werden, weil die Tests nicht ordnungsgemäß implementiert wurden und der Produktionscode nicht ordnungsgemäß funktioniert. Um dies zu verhindern, sollte der Produktionscode leicht geändert werden, um den Test zu unterbrechen. Wenn der Test dadurch fehlschlägt, ist der Test höchstwahrscheinlich korrekt implementiert und der Produktionscode kann zurückgesetzt werden, um den Test erneut zu bestehen.

Wenn Sie nur striktes TDD üben möchten, schreiben Sie möglicherweise keine zusätzlichen Tests, die von Anfang an bestehen. Andererseits sollte man in einer Unternehmensentwicklungsumgebung die TDD-Praxis tatsächlich verlassen, wenn zusätzliche Tests nützlich erscheinen.

leifbattermann
quelle
0

Ein Test, der ohne Änderung des Produktionscodes bestanden wird, ist von Natur aus nicht schlecht und wird oft benötigt, um eine zusätzliche Anforderung oder einen Grenzfall zu beschreiben. Solange Ihr Test "lohnenswert erscheint", wie Sie es sagen, behalten Sie ihn bei.

In Schwierigkeiten geraten Sie, wenn Sie einen bereits bestandenen Test als Ersatz für das tatsächliche Verständnis des Problembereichs schreiben.

Wir können uns zwei Extreme vorstellen: Ein Programmierer, der eine große Anzahl von Tests schreibt, "nur für den Fall", dass man einen Fehler entdeckt; und einen zweiten Programmierer, der den Problemraum sorgfältig analysiert, bevor er eine minimale Anzahl von Tests schreibt. Angenommen, beide versuchen, eine Absolutwertfunktion zu implementieren.

Der erste Programmierer schreibt:

assert abs(-88888) == 88888
assert abs(-12345) == 12345
assert abs(-5000) == 5000
assert abs(-32) == 32
assert abs(46) == 46
assert abs(50) == 50
assert abs(5001) == 5001
assert abs(999999) == 999999
...

Der zweite Programmierer schreibt:

assert abs(-1) == 1
assert abs(0) == 0
assert abs(1) == 1

Die Implementierung des ersten Programmierers kann zu folgenden Ergebnissen führen:

def abs(n):
    if n < 0:
        return -n
    elif n > 0:
        return n

Die Implementierung des zweiten Programmierers kann zu folgenden Ergebnissen führen:

def abs(n):
    if n < 0:
        return -n
    else:
        return n

Alle Tests sind bestanden, aber der erste Programmierer hat nicht nur mehrere redundante Tests geschrieben (die den Entwicklungszyklus unnötig verlangsamen), sondern auch einen Grenzfall nicht getestet ( abs(0)).

Wenn Sie Tests schreiben, die ohne Änderung des Produktionscodes bestanden wurden, fragen Sie sich, ob Ihre Tests wirklich einen Mehrwert bringen oder ob Sie mehr Zeit für das Verständnis des Problemumfelds benötigen.

Thinkterry
quelle
Nun, der zweite Programmierer war auch bei den Tests sichtlich nachlässig, weil sein Kollege neu definiert abs(n) = n*nund bestanden hat.
Eiko
@Eiko Du hast absolut recht. Wenn Sie zu wenig Tests schreiben , können Sie das genauso schwer tun. Der zweite Programmierer war zu geizig, nicht zuletzt durch Tests abs(-2). Moderation ist wie bei allem der Schlüssel.
Thinkterry