13 Stimmen

Wie verwendet man TDD richtig, um eine numerische Methode zu implementieren?

Ich versuche, meine Signalverarbeitungsbibliothek mit Hilfe von Test Driven Development zu implementieren. Aber ich habe ein paar Zweifel: Nehmen wir an, ich versuche, eine Sinusmethode zu implementieren (was ich nicht tue):

  1. Schreiben Sie den Test (Pseudocode)

    assertEqual(0, sine(0))
  2. Schreiben Sie die erste Implementierung

    function sine(radians)
        return 0
  3. Zweiter Test

    assertEqual(1, sine(pi))

Zu diesem Zeitpunkt, sollte ich:

  1. einen intelligenten Code zu implementieren, der für pi und andere Werte funktioniert, oder
  2. den dümmsten Code implementieren, der nur für 0 und pi funktioniert?

Wenn Sie die zweite Option wählen, wann kann ich zur ersten Option wechseln? Ich werde es irgendwann tun müssen...

1 Stimmen

Wenn Sie Fließkommazahlen auf Gleichheit prüfen, verwenden Sie etwas wie assertDoubleEqual, wenn Ihre Bibliothek es bereitstellt, oder schreiben Sie Ihre eigene Methode wie assert( abs(float1-float2)<DELTA );

0 Stimmen

Ich bin mit Matlab xUnit, die assertElementsAlmostEqual Methode bietet

0 Stimmen

@Jader Dias: Bitte zeigen Sie Code, der dem entspricht, was Sie tatsächlich tun. Wenn Sie assertElementsAlmostEqual verwenden, zeigen Sie das bitte in Ihrem Code.

2voto

paxdiablo Punkte 809679

Sie sollten alle Ihre Unit-Tests in einem Rutsch programmieren (meiner Meinung nach). Der Gedanke, nur Tests zu erstellen, die genau das abdecken, was getestet werden muss, ist zwar richtig, aber Ihre spezielle Spezifikation erfordert eine funktionierende sine() Funktion, no a sine() Funktion, die für 0 und PI funktioniert.

Suchen Sie sich eine Quelle, der Sie vertrauen (ein befreundeter Mathematiker, Tabellen am Ende eines Mathematikbuchs oder ein anderes Programm, in dem die Sinusfunktion bereits implementiert ist).

Ich habe mich für bash/bc weil ich zu faul bin, alles per Hand einzutippen :-). Wenn es waren a sine() Funktion, würde ich einfach das folgende Programm ausführen und es in den Testcode einfügen. Ich würde auch eine Kopie dieses Skripts als Kommentar einfügen, damit ich es wiederverwenden kann, wenn sich etwas ändert (z. B. die gewünschte Auflösung, wenn sie in diesem Fall mehr als 20 Grad beträgt, oder der Wert von PI, den Sie verwenden möchten).

#!/bin/bash
d=0
while [[ ${d} -le 400 ]] ; do
    r=$(echo "3.141592653589 * ${d} / 180" | bc -l)
    s=$(echo "s(${r})" | bc -l)
    echo "assertNear(${s},sine(${r})); // ${d} deg."
    d=$(expr ${d} + 20)
done

Diese Ausgaben:

assertNear(0,sine(0)); // 0 deg.
assertNear(.34202014332558591077,sine(.34906585039877777777)); // 20 deg.
assertNear(.64278760968640429167,sine(.69813170079755555555)); // 40 deg.
assertNear(.86602540378430644035,sine(1.04719755119633333333)); // 60 deg.
assertNear(.98480775301214683962,sine(1.39626340159511111111)); // 80 deg.
assertNear(.98480775301228458404,sine(1.74532925199388888888)); // 100 deg.
assertNear(.86602540378470305958,sine(2.09439510239266666666)); // 120 deg.
assertNear(.64278760968701194759,sine(2.44346095279144444444)); // 140 deg.
assertNear(.34202014332633131111,sine(2.79252680319022222222)); // 160 deg.
assertNear(.00000000000079323846,sine(3.14159265358900000000)); // 180 deg.
assertNear(-.34202014332484051044,sine(3.49065850398777777777)); // 200 deg.
assertNear(-.64278760968579663575,sine(3.83972435438655555555)); // 220 deg.
assertNear(-.86602540378390982112,sine(4.18879020478533333333)); // 240 deg.
assertNear(-.98480775301200909521,sine(4.53785605518411111111)); // 260 deg.
assertNear(-.98480775301242232845,sine(4.88692190558288888888)); // 280 deg.
assertNear(-.86602540378509967881,sine(5.23598775598166666666)); // 300 deg.
assertNear(-.64278760968761960351,sine(5.58505360638044444444)); // 320 deg.
assertNear(-.34202014332707671144,sine(5.93411945677922222222)); // 340 deg.
assertNear(-.00000000000158647692,sine(6.28318530717800000000)); // 360 deg.
assertNear(.34202014332409511011,sine(6.63225115757677777777)); // 380 deg.
assertNear(.64278760968518897983,sine(6.98131700797555555555)); // 400 deg.

Natürlich müssen Sie diese Antwort auf das abbilden, was Ihre eigentliche Funktion tun soll. Ich will damit sagen, dass der Test das Verhalten des Codes in dieser Iteration vollständig validieren sollte. Wenn diese Iteration eine sine() Funktion, die nur für 0 und PI funktioniert, dann ist das in Ordnung. Aber das wäre meiner Meinung nach eine große Verschwendung einer Iteration.

Es kann sein, dass Ihre Funktion so komplex ist, dass sie debe über mehrere Iterationen durchgeführt werden. Dann ist Ihr Ansatz zwei richtig und die Tests sollten in der nächste Iteration, in der Sie die zusätzliche Funktionalität hinzufügen. Andernfalls sollten Sie einen Weg finden, alle Tests für diese Iteration schnell hinzuzufügen. Dann müssen Sie sich keine Gedanken über den häufigen Wechsel zwischen echtem Code und Testcode machen.

1 Stimmen

Beachten Sie, dass es sich hierbei um mehr oder weniger zufällige Werte handelt, mit denen sich die Randfälle nicht besonders gut testen lassen. Da die meisten Sinusfunktionen eine Reihe von Symmetrie-Regeln für Werte von 0 bis pi/4 Radiant verwenden, sollte der Test auf den Symmetrie-Regeln basieren, nicht auf "alle 20 Grad".

0 Stimmen

Das "alle 20 Grad" war nur, damit ich die Antwort nicht mit Unsinn vollschreibe :-) Im Original war alle 1 Grad angegeben, und man könnte ganz einfach Zehntelgrade oder sogar eine höhere Auflösung wählen, wenn man das möchte. Aber das ist irrelevant, da der Fragesteller es so wollte. war nicht eine Sinusfunktion. Der Punkt, den ich versuchte zu machen, ist im letzten Absatz - schreiben Sie alle Ihre Tests für die aktuelle Iteration vor der Entwicklung; das ist besser als tun es stückweise.

1voto

Greg Hewgill Punkte 882617

Streng nach TDD können Sie zunächst den dümmsten Code implementieren, der funktionieren wird. Um zur ersten Option zu gelangen (den echten Code zu implementieren), fügen Sie weitere Tests hinzu:

assertEqual(tan(x), sin(x)/cos(x))

Wenn Sie mehr implementieren, als für Ihre Tests unbedingt erforderlich ist, werden Ihre Tests Ihre Implementierung nicht vollständig abdecken. Wenn Sie zum Beispiel die gesamte sin() Funktion nur mit den beiden obigen Tests zu testen, könnten Sie sie versehentlich "brechen", indem Sie eine Dreiecksfunktion zurückgeben (die fast wie eine Sinusfunktion aussieht), und Ihre Tests wären nicht in der Lage, den Fehler zu erkennen.

Die andere Sache, über die Sie sich bei numerischen Funktionen Gedanken machen müssen, ist der Begriff der "Gleichheit" und der Umgang mit dem inhärenten Verlust an Genauigkeit bei Fließkommaberechnungen. Das ist es, was ich Gedanken nach dem Lesen des Titels, worum es in deiner Frage gehen sollte :)

0 Stimmen

Aber das ist unpraktisch, irgendwann werde ich TDD aufgeben müssen, um eine Endlosschleife zu vermeiden.

0 Stimmen

Können Sie näher erläutern, was Sie mit "Endlosschleife" meinen?

0 Stimmen

Gibt es unendlich viele verschiedene Funktionen, die für N getestete Argumente die gleichen Ergebnisse liefern wie SINE, wenn N eine natürliche Zahl ist. Wenn Sie also weniger als unendlich viele Tests haben, besteht immer noch die Möglichkeit, dass Ihre SINE-Methode nicht korrekt ist, weil sie gerade dort von der SINE-Definition abweichen kann, wo Sie nicht getestet haben.

1voto

Mathias Punkte 14811

Ich weiß nicht, welche Sprache Sie verwenden, aber wenn ich mit einer numerischen Methode zu tun habe, schreibe ich in der Regel zuerst einen einfachen Test wie den Ihren, um sicherzustellen, dass die Gliederung korrekt ist, und dann gebe ich weitere Werte ein, um Fälle abzudecken, in denen ich vermute, dass etwas schief gehen könnte. In .NET hat NUnit 2.5 eine nette Funktion dafür, genannt [TestCase] , bei dem Sie mehrere Eingabewerte in denselben Test einspeisen können, wie hier:

[TestCase(1,2,Result=3)]   
[TestCase(1,1,Result=2)]     
public int CheckAddition(int a, int b)   
{  
 return a+b;   
}

1voto

Gishu Punkte 130442

Kurze Antwort.

  • Schreiben Sie einen Test nach dem anderen.
  • Wenn es nicht klappt, gehen Sie zuerst auf Grün zurück. Wenn das bedeutet, das Einfachste zu tun, was funktionieren kann, dann tu es. (Option 2)
  • Sobald Sie im grünen Bereich sind, können Sie sich den Code ansehen und wählen. zu bereinigen (Option 1). Oder Sie können sagen, dass der Code immer noch nicht so sehr riecht, und weitere Tests schreiben, die die Gerüche in den Vordergrund stellen.

Eine weitere Frage, die Sie zu haben scheinen, ist, wie viele Tests Sie schreiben sollten. Sie müssen so lange testen, bis die Angst (die Funktion könnte nicht funktionieren) in Langeweile umschlägt. Wenn Sie also alle interessanten Eingabe-Ausgabe-Kombinationen getestet haben, sind Sie fertig.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X