Schreiben Sie den Mindestcode, um einen Komponententest zu bestehen - ohne zu schummeln!

36

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

CraigTP
quelle
4
Es ist eine menschliche Sache: Sie müssen dem Drang zu betrügen widerstehen. Da ist nichts mehr dran. Sie könnten mehr Tests hinzufügen und mehr Testcode schreiben als zu testender Code, aber wenn Sie diesen Luxus nicht haben, müssen Sie einfach widerstehen. Es gibt VIELE Stellen in der Codierung, an denen wir dem Drang zu hacken oder zu betrügen widerstehen müssen, weil wir wissen, dass es heute vielleicht funktioniert, aber später nicht.
Dan Rosenstark
7
Bei TDD ist es sicher ein Betrug, wenn man es umgekehrt macht - dh Return 120 ist der richtige Weg. Ich finde es ziemlich schwierig, mich dazu zu bewegen und nicht vorauszurennen und die Fakultätsberechnung zu schreiben.
Paul Butcher
2
Ich würde dies als Betrug betrachten, nur weil es den Test bestehen kann, aber keine echte Funktionalität hinzufügt oder Sie einer endgültigen Lösung des vorliegenden Problems näher bringt.
GrumpyMonkey
3
Wenn sich herausstellt, dass Client-Code-Code immer nur eine 5 übergibt, ist die Rückgabe von 120 nicht nur ein Nicht-Cheat, sondern eine legitime Lösung.
Kramii setzt Monica
Ich stimme @PaulButcher zu - tatsächlich würden viele Unit-Test-Beispiele in Texten und Artikeln diesen Ansatz verfolgen.
HorusKol

Antworten:

45

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.

CaffGeek
quelle
12
Dies wirft nur die Frage auf, warum man die Funktion nicht gleich richtig schreibt.
Robert Harvey
8
@ Robert, Fakultätszahlen sind einfach. Der eigentliche Vorteil von TDD besteht darin, dass Sie nicht-triviale Bibliotheken schreiben. Wenn Sie den Test zuerst schreiben, müssen Sie die API vor der Implementierung entwerfen, was meiner Erfahrung nach zu besserem Code führt.
1
@Robert, du bist es, der sich Sorgen macht, das Problem zu lösen, anstatt den Test zu bestehen. Ich sage Ihnen, dass es bei nicht trivialen Problemen einfach besser ist, das harte Design aufzuschieben, bis Sie Tests durchgeführt haben.
1
@ Thorbjørn Ravn Andersen, nein, ich sage nicht, dass Sie nur eine Rückkehr haben können. Es gibt gültige Gründe für mehrere (dh Schutzaussagen). Das Problem ist, dass beide return-Anweisungen "gleich" waren. Sie haben dasselbe getan. Sie hatten einfach unterschiedliche Werte. Bei TDD geht es nicht um Steifheit, sondern um die Einhaltung eines bestimmten Test / Code-Verhältnisses. Es geht darum, ein Komfortniveau in Ihrer Codebasis zu schaffen. Wenn Sie einen fehlerhaften Test schreiben können, dann ist eine Funktion, die für zukünftige Tests dieser Funktion funktioniert, großartig. Machen Sie es und schreiben Sie Ihre Edge-Case-Tests, um sicherzustellen, dass Ihre Funktion immer noch funktioniert.
CaffGeek,
3
Wenn Sie nicht die vollständige (wenn auch einfache) Implementierung auf einmal schreiben, haben Sie keine Garantie dafür, dass Ihre Tests sogar fehlschlagen KÖNNEN. Der Grund, warum ein Test fehlschlägt, bevor er bestanden wird, besteht darin, dass Sie tatsächlich den Beweis haben, dass Ihre Änderung am Code die Behauptung erfüllt, die Sie darauf gemacht haben. Dies ist der einzige Grund, warum TDD so großartig ist, um eine Regressionstestsuite zu erstellen und den Boden mit dem "Test nach" -Ansatz in diesem Sinne vollständig abzuwischen.
Sara
25

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:

Wenn Sie nicht Test Driven Development betreiben, ist es sehr schwierig, sich selbst als Profi zu bezeichnen. Jim Coplin rief mich auf dem Teppich an. Das hat ihm nicht gefallen, das habe ich gesagt. Tatsächlich ist seine derzeitige Position, dass Test Driven Development Architekturen zerstört, weil die Leute Tests schreiben, um jede andere Art von Gedanken aufzugeben, und ihre Architekturen in der wahnsinnigen Eile auseinander reißen, um Tests zu bestehen, und er hat einen interessanten Punkt. Das ist eine interessante Art, das Ritual zu missbrauchen und die Absicht hinter der Disziplin zu verlieren.

Wenn Sie nicht über die Architektur nachdenken, sondern stattdessen die Architektur ignorieren und die Tests zusammenwerfen und zum Bestehen bringen, zerstören Sie das, was dem Gebäude erlaubt, aufrecht zu bleiben, weil es die Konzentration auf das Wesentliche ist Struktur des Systems und solide Entwurfsentscheidungen, mit denen das System seine strukturelle Integrität beibehält.

Sie können nicht einfach eine ganze Reihe von Tests zusammenwerfen und sie Jahrzehnt für Jahrzehnt durchlaufen lassen und davon ausgehen, dass Ihr System überleben wird. Wir wollen uns nicht zur Hölle entwickeln. Ein guter, testgetriebener Entwickler ist sich immer der architektonischen Entscheidungen bewusst und denkt immer an das große Ganze.

Robert Harvey
quelle
Keine wirkliche Antwort auf die Frage, aber 1+
Nobody
2
@rmx: Ähm, die Frage ist: Wie kommt man zu dem Gleichgewicht zwischen dem Schreiben des Mindestcodes, um den Test zu bestehen, und dem Beibehalten des Funktionszustands und dem Erreichen der Ziele? Lesen wir die gleiche Frage?
Robert Harvey
Die ideale Lösung ist ein Algorithmus und hat nichts mit Architektur zu tun. Durch TDD werden Sie keine Algorithmen erfinden. Irgendwann müssen Sie Schritte in Bezug auf einen Algorithmus / eine Lösung ausführen.
Joppe
Ich bin mit @rmx einverstanden. Dies beantwortet nicht wirklich meine spezifische Frage an sich, aber es gibt Anlass zu Überlegungen, wie TDD im Allgemeinen in das Gesamtbild des gesamten Softwareentwicklungsprozesses passt. Aus diesem Grund +1.
CraigTP
Ich denke, Sie könnten "Architektur" durch "Algorithmen" - und andere Begriffe - ersetzen, und das Argument ist immer noch gültig. Es geht nur darum, vor lauter Bäumen den Wald nicht sehen zu können. Wenn Sie nicht für jede einzelne Ganzzahleingabe einen eigenen Test schreiben, kann TDD nicht zwischen einer ordnungsgemäßen faktoriellen Implementierung und einer perversen Hardcodierung unterscheiden, die für alle getesteten Fälle funktioniert, für andere jedoch nicht. Das Problem bei TDD ist die Leichtigkeit, mit der "alle Tests bestanden" und "der Code ist gut" zusammengeführt werden. Irgendwann muss ein hohes Maß an gesundem Menschenverstand angewendet werden.
Julia Hayward
16

Eine sehr gute Frage ... und ich muss mich fast allen widersetzen, außer @Robert.

Schreiben

return 120;

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:

  • Calculate Factorial ist das Feature, nicht "eine Konstante zurückgeben". "return 120" ist keine Berechnung.
  • 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 :

    if (input == 5) { return 120; } //input=5 case
    else { return 720; }   //input=6 case
    
  • Wenn wir dem 'refactor'-Argument wörtlich folgen , würden wir bei 5 Testfällen YAGNI aufrufen und die Funktion mithilfe einer Nachschlagetabelle implementieren:

    if (factorialDictionary.Contains(input)) {
        return factorialDictionary[input]; 
    }
    throw new Exception("Input failure");
    

Keiner von diesen berechnet tatsächlich etwas, das bist du . Und das ist nicht die Aufgabe!

Steven A. Lowe
quelle
1
@ rmx: nein, habe es nicht verpasst; "refactor to remove duplication" kann mit einer Nachschlagetabelle befriedigt werden. Übrigens ist das Prinzip, dass Komponententests die Codierungsanforderungen erfüllen, nicht BDD-spezifisch, sondern ein allgemeines Prinzip von Agile / XP. Wenn die Anforderung "Beantworte die Frage 'Was ist die Fakultät von 5'" lautete, dann "gebe 120 zurück;" wäre
Steven A. Lowe
2
@Chad all das ist unnötige Arbeit - schreibe einfach die Funktion zum ersten Mal ;-)
Steven A. Lowe
2
@Steven A.Lowe, nach dieser Logik, warum irgendwelche Tests schreiben ?! "Schreiben Sie einfach die Anwendung zum ersten Mal!" Der Punkt von TDD sind kleine, sichere, inkrementelle Änderungen.
CaffGeek
1
@Chad: Strohmann.
Steven A. Lowe
2
Wenn Sie nicht die vollständige (wenn auch einfache) Implementierung auf einmal schreiben, haben Sie keine Garantie dafür, dass Ihre Tests sogar fehlschlagen KÖNNEN. Der Grund, warum ein Test fehlschlägt, bevor er bestanden wird, besteht darin, dass Sie tatsächlich den Beweis haben, dass Ihre Änderung am Code die Behauptung erfüllt, die Sie darauf gemacht haben. Dies ist der einzige Grund, warum TDD so großartig ist, um eine Regressionstestsuite zu erstellen und den Boden mit dem "Test nach" -Ansatz in diesem Sinne vollständig abzuwischen. Sie schreiben nie versehentlich einen Test, der nicht scheitern kann. Werfen Sie auch einen Blick auf Onkel Bob Prime Factor Kata.
Sara
10

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.

Azheglov
quelle
2
Der "-1" -Fall wäre interessant. Denn es ist nicht gut definiert, die beide so der Typ den Test und der Mann Schreiben Sie den Code schreiben muss zustimmen erste , was sollte passieren.
gnasher729
2
+1 für den Hinweis, dass dies 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/… )
Sara
5

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
Ernst? Nach dieser Logik müssen Sie den Code jedes Mal neu schreiben, wenn jemand eine neue Eingabe vornimmt.
Robert Harvey
6
@Robert, wenn Sie irgendwann einen neuen Fall hinzufügen, wird dies nicht mehr zu einem möglichst einfachen Code führen. An diesem Punkt schreiben Sie eine neue Implementierung. Da Sie die Tests bereits durchgeführt haben, wissen Sie genau, wann Ihre neue Implementierung dieselbe wie die alte Implementierung ausführt.
1
@ Thorbjørn Ravn Andersen, genau der wichtigste Teil von Red-Green-Refactor, ist das Refactoring.
CaffGeek
+1: Dies ist auch die allgemeine Idee meines Wissens, aber es muss etwas über die Erfüllung des implizierten Vertrages gesagt werden (dh der Methodenname Fakultät ). Wenn Sie immer nur f (6) = 120 spezifizieren (dh testen), müssen Sie immer nur 120 zurückgeben. Wenn Sie Tests hinzufügen, um sicherzustellen, dass f (x) == x * x-1 ... * xx-1: upperBound> = x> = 0 ist, gelangen Sie zu einer Funktion, die die Fakultätsgleichung erfüllt.
Steven Evers
1
@SnOrfus, der Ort für "implizite Verträge" ist in den Testfällen. Wenn Sie Auftrag für factorials ist, Sie TEST , wenn bekannt factorials sind und wenn bekannt Nicht-factorials nicht. Viele davon. Es dauert nicht lange, die Liste der zehn ersten Fakultäten in eine for-Schleife umzuwandeln, um jede Zahl bis zur zehnten Fakultät zu testen.
4

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.

Niemand
quelle
Wie Nanda hervorhob, können Sie zu a immer eine endlose Reihe von caseAnweisungen hinzufügen switch, und Sie können nicht für jeden möglichen Ein- und Ausgang für das OP-Beispiel einen Test schreiben.
Robert Harvey
Sie können die Werte von Int64.MinValuebis technisch testen Int64.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.
Niemand
@rmx: Wenn Sie dies tun könnten, wären die Tests der Algorithmus, und Sie müssten den Algorithmus nicht mehr schreiben.
Robert Harvey
Es ist wahr. Meine Hochschularbeit beinhaltet eigentlich die automatische Generierung der Implementierung unter Verwendung der Unit-Tests als Leitfaden mit einem genetischen Algorithmus als Hilfe für TDD - und dies ist nur mit soliden Tests möglich. Der Unterschied besteht darin, dass das Binden Ihrer Anforderungen an Ihren Code in der Regel weitaus schwieriger zu lesen und zu erfassen ist als eine einzelne Methode, die die Komponententests umfasst. Dann kommt die Frage: Wenn Ihre Implementierung eine Manifestation Ihrer Komponententests ist und Ihre Komponententests eine Manifestation Ihrer Anforderungen sind, warum nicht einfach das Testen insgesamt überspringen? Ich habe keine Antwort.
Niemand
Sind wir als Menschen nicht genauso wahrscheinlich, dass wir in Unit-Tests einen Fehler machen wie im Implementierungscode? Warum also überhaupt Unit-Test?
Niemand
3

Schreibe einfach mehr Tests. Schließlich wäre es kürzer zu schreiben

public long CalculateFactorial(long input)
{
    return input <= 1 ? 1 : CalculateFactorial(input-1)*input;
}

als

public long CalculateFactorial(long input)
{
    switch (input) {
       case 0: return 1;
       case 1: return 1;
       case 2: return 2;
       case 3: return 6;
       case 4: return 24;
       case 5: return 120;
    }
}

:-)

P Shved
quelle
3
Wer schreibt den Algorithmus nicht gleich richtig?
Robert Harvey
3
@ Robert, es ist der richtige Algorithmus zur Berechnung der Fakultät einer Zahl von 0 bis 5. Außerdem, was bedeutet "richtig"? Dies ist ein sehr einfaches Beispiel, aber wenn es komplexer wird, gibt es viele Abstufungen dessen, was "richtig" bedeutet. Ist ein Programm, das Root-Zugriff erfordert, "korrekt" genug? Ist die Verwendung von XML "korrekt" anstelle von CSV? Das kannst du nicht beantworten. Jeder Algorithmus ist korrekt, solange er einige Geschäftsanforderungen erfüllt, die als Tests in TDD formuliert sind.
P Shved
3
Da der Ausgabetyp lang ist, gibt es nur eine kleine Anzahl von Eingabewerten (etwa 20), die die Funktion möglicherweise korrekt verarbeiten kann. Daher ist eine große switch-Anweisung nicht unbedingt die schlechteste Implementierung - wenn die Geschwindigkeit höher ist Je nach Prioritäten ist die switch-Anweisung möglicherweise der richtige Weg.
user281377
3

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.

Bob Jarvis
quelle
+1 für "Komponententests sind erst abgeschlossen, wenn alle Tests bestanden wurden und keine neuen Tests geschrieben werden können, die fehlschlagen" wenn die Gesamtanforderungen nur diese speziellen Fälle benötigen "
Thymine
1

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:

    [Theory]
    [InlineData(0, 1)]
    [InlineData( 1, 1 )]
    [InlineData( 2, 2 )]
    [InlineData( 3, 6 )]
    [InlineData( 4, 24 )]
    public void Test_Factorial(int input, int expected)
    {
        int result = Factorial( input );
        Assert.Equal( result, expected);
    }

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.

Johannes Rudolph
quelle
1

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.

Nanda
quelle
1
Es hört sich also so an, als würden Sie sagen, dass es nicht ausreicht , nur genug Code zu schreiben, damit der Test bestanden werden kann (wie TDD befürwortet) . Sie müssen auch die Prinzipien des Sound-Software-Designs berücksichtigen. Ich stimme dir übrigens zu.
Robert Harvey
0

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.

Chris Cudmore
quelle
Was ist -ve??
Robert Harvey
ein negativer Wert.
Chris Cudmore