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.

10voto

S.Lott Punkte 371691

Zu diesem Zeitpunkt, sollte ich:

  1. einen echten Code implementieren, der über die beiden einfachen Tests hinaus funktioniert?

  2. mehr dummen Code implementieren, der nur für die beiden einfachen Tests funktioniert?

Weder noch. Ich bin mir nicht sicher, woher Sie den Ansatz "nur einen Test nach dem anderen schreiben" haben, aber es ist auf jeden Fall ein langsamer Weg.

Es geht darum, klare Tests zu schreiben und diese klaren Tests für den Entwurf Ihres Programms zu nutzen.

Schreiben Sie also genügend Tests, um eine Sinusfunktion tatsächlich zu validieren. Zwei Tests sind eindeutig unzureichend.

Im Falle einer kontinuierlichen Funktion müssen Sie eventuell eine Tabelle mit bekannten guten Werten bereitstellen. Warum warten?

Die Prüfung kontinuierlicher Funktionen ist jedoch mit einigen Problemen verbunden. Man kann nicht einem stummen TDD-Verfahren folgen.

Sie können nicht testen alle Fließkommawerte zwischen 0 und 2*pi. Sie können nicht ein paar zufällige Werte testen.

Im Fall von kontinuierlichen Funktionen funktioniert ein "strenges, unreflektiertes TDD" nicht. Das Problem hier ist, dass Sie wissen, dass Ihre Implementierung der Sinusfunktion auf einer Reihe von Symmetrien basieren wird. Sie müssen auf der Grundlage dieser Symmetrie-Regeln, die Sie verwenden, testen. Bugs verstecken sich in Rissen und Ecken. Rand- und Eckfälle sind Teil der Implementierung, und wenn Sie unbedacht TDD folgen, können Sie das nicht testen.

Bei kontinuierlichen Funktionen müssen Sie jedoch die Rand- und Eckfälle der Implementierung testen.

Das bedeutet nicht, dass TDD kaputt oder unzureichend ist. Es bedeutet, dass sklavische Hingabe an einen "Test zuerst" nicht funktionieren kann, ohne darüber nachzudenken, was Ihr eigentliches Ziel ist.

1 Stimmen

Während Ihre Antwort klar genug ist, um zu lehren, wie man eine bekannte Funktion implementiert, wird es schwieriger, neue Funktionen zu implementieren, die anwendungsspezifisch sind und für die es nur wenige bekannte gute Werte gibt. Ich kritisiere nicht, ich stimme Ihnen vollkommen zu.

0 Stimmen

@Jader Dias: wenn Sie nur wenige bekannte gute Werte haben, nichts über diesen Ansatz ändert. Schreiben Sie nicht einfach einen kleinen Test nach dem anderen. Schreiben Sie so viele Tests, wie Sie brauchen, und implementieren Sie sie dann.

0 Stimmen

Ich weiß nicht, wie es mit "nur einen Test zu einer Zeit schreiben" aussieht, aber "einen Test zu einer Zeit bestehen lassen" ist eine der Prämissen von TDD, die eine vollständige Codeabdeckung gewährleistet.

6voto

Yishai Punkte 87548

In einer Art strengem Baby-Schritt TDD könnten Sie die dumme Methode implementieren, um wieder auf grün zu kommen, und dann die Duplizierung, die dem dummen Code innewohnt, refaktorisieren (das Testen auf den Eingabewert ist eine Art Duplizierung zwischen dem Test und dem Code), indem Sie einen echten Algorithmus erstellen. Das Schwierige daran, ein Gefühl für TDD mit einem solchen Algorithmus zu bekommen, ist, dass Ihre Akzeptanztests wirklich direkt neben Ihnen sitzen (der Tisch, den S. Lott vorschlägt), so dass Sie sie irgendwie die ganze Zeit im Auge behalten. Bei typischerem TDD ist die Einheit so weit vom Ganzen entfernt, dass die Akzeptanztests nicht einfach dort eingefügt werden können, also fängt man nicht an, über Tests für alle Szenarien nachzudenken, weil nicht alle Szenarien offensichtlich sind.

Normalerweise hat man nach ein oder zwei Fällen einen echten Algorithmus. Das Wichtige an TDD ist, dass es den Entwurf vorantreibt, nicht den Algorithmus. Sobald Sie genügend Fälle haben, um die Designanforderungen zu erfüllen, sinkt der Wert von TDD erheblich. Dann wandeln sich die Tests mehr in die Abdeckung von Eckfällen um, um sicherzustellen, dass Ihr Algorithmus in allen erdenklichen Aspekten korrekt ist. Wenn Sie sich also sicher sind, wie Sie den Algorithmus entwickeln können, sollten Sie es tun. Die Babyschritte, von denen Sie sprechen, sind nur dann angebracht, wenn Sie unsicher sind. Mit solchen kleinen Schritten beginnen Sie, die Grenzen dessen abzustecken, was Ihr Code abdecken muss, auch wenn Ihre Implementierung noch nicht wirklich real ist. Aber wie ich schon sagte, ist das eher für den Fall gedacht, dass Sie unsicher sind, wie Sie den Algorithmus aufbauen sollen.

0 Stimmen

Ich stimme mit Ihrem Kommentar überein. Ich würde argumentieren, dass mathematische Funktionen (die bekannte Algorithmen haben) nicht gut für TDD geeignet sind. TDD beinhaltet ein gewisses Maß an "explorativem Programmieren", das in den meisten Programmiersprachen sehr verbreitet ist, aber nicht in allen.

0 Stimmen

Ich stimme völlig mit Ihrer Behauptung überein, dass der Hauptwert von TDD darin besteht, das Schnittstellendesign zu steuern. Es ist so, als würde man ein Schloss und einen Schlüssel gleichzeitig bauen, damit sie passen, wenn beide fertig sind.

6voto

rwong Punkte 5926

Schreiben Sie Tests zur Überprüfung von Identitäten.

Denken Sie bei dem Beispiel sin(x) an die Doppelwinkelformel und die Halbwinkelformel.

Schlagen Sie ein Lehrbuch zur Signalverarbeitung auf. Suchen Sie die relevanten Kapitel und implementieren Sie jedes einzelne dieser Theoreme/Kolloquien als Testcode für Ihre Funktion. Für die meisten signalverarbeitenden Funktionen gibt es Identitäten, die für die Eingänge und die Ausgänge aufrechterhalten werden müssen. Schreiben Sie Tests, die diese Identitäten überprüfen, unabhängig davon, wie die Eingaben aussehen.

Denken Sie dann an die Eingaben.

  • Unterteilen Sie den Umsetzungsprozess in verschiedene Phasen. Jede Phase sollte ein Ziel haben. Die Tests für jede Phase dienen der Überprüfung dieses Ziels. (Anmerkung 1)
    1. Das Ziel der ersten Stufe ist es, "ungefähr richtig" zu sein. Für das Beispiel sin(x) wäre dies eine naive Implementierung mit binärer Suche und einigen mathematischen Identitäten.
    2. Das Ziel der zweiten Stufe ist es, "genau genug" zu sein. Sie werden verschiedene Methoden zur Berechnung derselben Funktion ausprobieren und sehen, welche zu einem besseren Ergebnis führt.
    3. Das Ziel der dritten Stufe ist es, "effizient" zu sein.

(Anmerkung 1) Es muss funktionieren, es muss korrekt sein, es muss schnell sein, es muss billig sein. - zugeschrieben von Alan Kay

0 Stimmen

(Ich bin weder für noch gegen TDD. Aber, verzeihen Sie mir meine Arroganz, ich würde die Tests für unvollständig (und die Qualität der Bibliothek für fragwürdig) halten, wenn Sie die Bibliothek nicht gegen alle bekannten Identitäten, die für diese Funktionen gelten, verifizieren).

0 Stimmen

Ich glaube, S. Lott. hat diese Antwort viel früher gegeben als ich. Seine "Symmetrie-Regeln" bedeuten wahrscheinlich dasselbe wie mathematische Identitäten. Also, bitte geben Sie die Credits stattdessen an S. Lott.

2voto

Samuel Carrijo Punkte 16743

Ich glaube, dass der Schritt zur ersten Option dann erfolgt, wenn Sie feststellen, dass Ihr Code zu viele "ifs" enthält, "nur um die Tests zu bestehen". Das wäre noch nicht der Fall, nur mit 0 und pi.

Sie werden merken, dass der Code anfängt zu stinken, und werden bereit sein, ihn so schnell wie möglich zu überarbeiten. Ich bin nicht sicher, ob das ist, was reine TDD sagt, aber IMHO Sie tun es in der Refactor-Phase (Test fehlschlagen, Test bestehen, Refactor-Zyklus). Ich meine, es sei denn, Ihre fehlgeschlagenen Tests verlangen eine andere Implementierung.

0 Stimmen

Numerische Funktionen beruhen eher auf anderen mathematischen Funktionen als auf if-then-else-Anweisungen. Die Frage ist, wann die Tests ausreichend sind.

1 Stimmen

Wenn Sie alles getestet haben, was Sie können. Wenn Sie nicht alle Zeit der Welt haben, setzen Sie Prioritäten, erstellen Sie einige "Kategorien" (wie Testgrenzen, genaue Ergebnisse usw.) für das Testen und testen Sie ein paar Beispiele (oder vielleicht nur eines) für jede Kategorie

2voto

TrueWill Punkte 24357

Beachten Sie, dass Sie (in NUnit) auch Folgendes tun können

Assert.That(2.1 + 1.2, Is.EqualTo(3.3).Within(0.0005);

wenn es um die Gleichheit von Fließkommazahlen geht.

Ein Ratschlag, den ich gelesen habe, lautete, die magischen Zahlen aus den Implementierungen herauszufiltern.

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