2010-03-20 8 views
15

Ich bin mehrmals auf dieses Dilemma gestoßen. Sollte meine Unit-Tests die Funktionalität der Methode duplizieren, die sie testen, um ihre Integrität zu überprüfen? ODER Sollten Komponententests versuchen, die Methode mit zahlreichen manuell erstellten Instanzen von Eingängen und erwarteten Ausgaben zu testen?Sollte ein Unit-Test die Funktionalität replizieren oder Test ausgeben?

Ich stelle vor allem die Frage für Situationen, in denen die Methode, die Sie testen, einigermaßen einfach ist und seine ordnungsgemäße Funktionsweise kann durch einen Blick auf den Code für eine Minute überprüft werden.

Vereinfachtes Beispiel (in Ruby):

def concat_strings(str1, str2) 
    return str1 + " AND " + str2 
end 

Vereinfachte Funktionalität replizierenden Test für das obige Verfahren:

def test_concat_strings 
    10.times do 
    str1 = random_string_generator 
    str2 = random_string_generator 
    assert_equal (str1 + " AND " + str2), concat_strings(str1, str2) 
    end 
end 

Ich verstehe, dass die meiste Zeit die Methode, die Sie Tests sind nicht einfach sein genug, um es auf diese Weise zu rechtfertigen. Aber meine Frage bleibt; ist dies unter bestimmten Umständen eine gültige Methode (warum oder warum nicht)?

+0

Ich bin nicht vertraut mit dem ersten Szenario - wo ein Unit-Test die Funktionalität einer Methode dupliziert, können Sie ein wenig erarbeiten? – RobS

+0

In Test Driven Development erstelle ich oft einen Test, um zu überprüfen, ob eine (noch zu schreibende) Methode wie erwartet funktioniert. In einfachen Szenarien bin ich oft versucht, diese Spezifikationen programmatisch so zu schreiben, wie ich es am besten kann, um aus den Eingabewerten die gewünschte Ausgabe zu erzielen. Manchmal habe ich das Gefühl, dass es wahrscheinlicher ist, dass ich einen Fehler bei der manuellen Berechnung und Eingabe der erwarteten Ergebnisse begehen würde, als nur ein wenig Code zu schreiben, um den Job zu erledigen. –

+0

große Frage - Ich denke, das ist eine Ausnahme zu der Regel von OnceAndOnlyOnce .. es sei denn, ich weiß nicht, wie dies richtig zu refactor. – Gishu

Antwort

2

Es ist eine umstrittene Haltung, aber ich glaube, dass unit testing using Derived Values ist der Verwendung von beliebig hart codierten Ein- und Ausgängen weit überlegen.

Das Problem ist, dass, wenn ein Algorithmus sogar ein wenig komplex wird, die Beziehung zwischen Eingabe und Ausgabe unklar wird, wenn sie durch fest codierte Werte dargestellt wird. Der Komponententest endet als Postulat. Es kann technisch funktionieren, aber schmerzt Test Wartbarkeit weil es zu Obscure Tests führt.

Die Verwendung von Derived Values, um mit dem Ergebnis zu testen, etabliert eine viel klarere Beziehung zwischen Testeingang und erwartetem Ausgang. Das Argument, dass dies nichts testet, ist einfach nicht wahr, da jeder Testfall nur einen Teil eines Pfades durch das SUT ausübt, so dass kein einzelner Testfall den gesamten getesteten Algorithmus reproduziert, sondern die Kombination von Tests wird dies tun.

Ein zusätzlicher Vorteil ist, dass Sie use fewer unit tests to cover the desired functionality, und sogar sie kommunikativer zur gleichen Zeit machen können. Das Endergebnis ist eine bessere und wartbarere Einheitentests.

2

Beim Komponententest sollten Sie auf jeden Fall manuell Testfälle erstellen (also Eingabe, Ausgabe und welche Nebenwirkungen Sie erwarten - das sind Erwartungen an Ihre Mock-Objekte). Sie erstellen diese Testfälle so, dass sie alle Funktionen Ihrer Klasse abdecken (z. B. alle Methoden sind abgedeckt, alle Zweige aller if-Anweisungen usw.). Denken Sie eher daran, die Dokumentation Ihrer Klasse zu erstellen, indem Sie alle möglichen Verwendungen zeigen.

Die Implementierung der Klasse ist keine gute Idee, da Sie nicht nur eine offensichtliche Code/Funktionalität-Duplizierung erhalten, sondern wahrscheinlich auch die gleichen Fehler in dieser neuen Implementierung einführen werden.

1

um die Funktionalität einer Methode zu testen, würde ich wo immer möglich Input-und Output-Paare verwenden. Andernfalls könnten Sie die & kopieren die Funktionalität sowie die Fehler in seiner Implementierung einfügen. was testest du dann? Sie würden testen, ob sich die Funktionalität (einschließlich aller Fehler) im Laufe der Zeit nicht geändert hat. aber Sie würden nicht die Korrektheit der Implementierung testen.

Testen, ob sich die Funktionalität im Laufe der Zeit nicht geändert hat, könnte (vorübergehend) beim Refactoring nützlich sein. Aber wie oft refaktorieren Sie solche kleinen Methoden?

auch Komponententests können als Dokumentation und als Spezifikation der Eingaben und erwarteten Ausgaben einer Methode angesehen werden. beide sollten so einfach wie möglich sein, damit andere sie leicht lesen und verstehen können. Sobald Sie zusätzlichen Code/Logik in einen Test einführen, wird es schwieriger zu lesen.

Ihr Test sieht tatsächlich wie ein fuzz test aus. Fuzz-Tests können sehr nützlich sein, aber in Unit-Tests sollte die Zufälligkeit aufgrund der Reproduzierbarkeit vermieden werden.

8

Testen der Funktionalität mit der gleichen Implementierung, testet nichts. Wenn einer einen Fehler hat, wird der andere auch.

Testen durch Vergleich mit einer alternativen Implementierung ist jedoch ein gültiger Ansatz. Sie könnten beispielsweise eine iterative (schnelle) Methode zur Berechnung von Fibonacci-Zahlen testen, indem Sie sie mit einer trivialen rekursiven, jedoch langsamen Implementierung der gleichen Methode vergleichen.

Eine Variante davon verwendet eine Implementierung, die nur für Sonderfälle funktioniert. In diesem Fall können Sie es natürlich nur für solche speziellen Fälle verwenden.

Bei der Auswahl von Eingabewerten ist die Verwendung von Zufallswerten meistens nicht sehr effektiv. Ich würde jederzeit sorgfältig ausgewählte Werte bevorzugen. In dem Beispiel, das Sie angegeben haben, kommen Nullwerte und extrem lange Werte in Frage, die bei Verkettung nicht in einen String passen.

Wenn Sie zufällige Werte verwenden, stellen Sie sicher, dass Sie den genauen Lauf mit den gleichen Zufallswerten neu erstellen können, indem Sie beispielsweise den Startwert protokollieren und diesen Wert zur Startzeit festlegen können.

1

Ein Unit-Test sollte Ihren Code, nicht etwas als Teil der Sprache, die Sie verwenden.

Wenn die Logik des Codes Strings auf eine spezielle Art verketten soll, sollten Sie das testen - andernfalls müssen Sie sich auf Ihre Sprache/Ihr Framework verlassen.

Schließlich sollten Sie Ihre Komponententests erstellen, um zuerst "mit Bedeutung" zu versagen. Mit anderen Worten, sollten nicht zufällige Werte verwendet werden (es sei denn, Sie Ihren Zufallszahlengenerator zu test nicht die gleiche Menge von Zufallswerten zurückkehren!)

0

Verwenden Sie niemals zufällige Daten für die Eingabe. Wenn Ihr Test einen Fehler meldet, wie werden Sie ihn jemals reproduzieren können? Und verwenden Sie nicht die gleiche Funktion, um das erwartete Ergebnis zu erzeugen. Wenn Sie einen Fehler in Ihrer Methode haben, werden Sie wahrscheinlich denselben Fehler in Ihren Test einbauen. Berechnen Sie die erwarteten Ergebnisse mit einer anderen Methode.

Fest codierte Werte sind vollkommen in Ordnung, und stellen Sie sicher, dass die Eingänge ausgewählt sind, um alle normalen und Randfälle darzustellen. Testen Sie zumindest die erwarteten Eingaben sowie Eingaben im falschen Format oder in der falschen Größe (zB: Nullwerte).

Es ist wirklich ganz einfach - ein Komponententest muss testen, ob die Funktion funktioniert oder nicht. Das bedeutet, dass Sie eine Reihe von bekannten Eingängen angeben müssen, die bekannte Ausgänge haben und dagegen testen. Es gibt keinen universellen richtigen Weg, dies zu tun. Die Verwendung des gleichen Algorithmus für die Methode und die Überprüfung beweist jedoch nichts anderes, als dass Sie mit dem Kopieren/Einfügen vertraut sind.

0

Ja. Es stört mich auch .. obwohl ich sagen würde, dass es häufiger mit nicht-trivialen Berechnungen ist. Um die Aktualisierung den Tests zu vermeiden, wenn die Code-Änderungen, einige Programmierer eine ISX = X Test schreiben, die immer unabhängig von der SUT-Funktionalität

Sie haben keine

  • über Duplizierung erfolgreich zu. Ihr Test kann angeben, was die erwartete Ausgabe ist, nicht wie Sie sie abgeleitet haben. Obwohl dies in einigen nicht-trivialen Fällen dazu führt, dass Ihr Test besser lesbar ist, wie Sie den erwarteten Wert abgeleitet haben - Test als Spezifikation. Sie sollten nicht diese Vervielfältigung

    def doubler(x); x * 2; end 
    
    def test_doubler() 
        input, expected = 10, doubler(10) 
    
        assert_equal expected, doubler(10) 
    end 
    

    Jetzt Refactoring weg, wenn ich Verdoppler (x) ändern nicht eine tripler, der obige Test sein wird nicht. def doubler(x); x * 3; end

    Allerdings würde diese:

    def test_doubler() 
        assert_equal(20, doubler(10)) 
    end 
    
    • Zufälligkeit in Unit-Tests - nicht.

    Anstelle von Zufallsdatensätzen wählen statische repräsentative Datenpunkte für das Testen und die Verwendung eine xUnit RowTest/Testcase den Test mit diff Dateneingaben auszuführen. Wenn n Eingabesätze für das Gerät identisch sind, wählen Sie 1. Der Test im OP könnte als explorativer Test/oder zur Ermittlung zusätzlicher repräsentativer Input-Sets verwendet werden. Unit-Tests müssen wiederholbar sein (siehe q # 61400) - Die Verwendung von Zufallswerten verhindert dieses Ziel.

+0

Ich fragte nicht, ob ich die exakt gleiche Methode wiederverwenden sollte, sondern lediglich einige oder alle Funktionen des SUT neu implementieren sollte, um den erwarteten Wert für den Test verständlich zu generieren. –

+0

@Daniel - wie ich im zweiten Absatz erwähne, für die Lesbarkeit in den Tests müssten Sie die Logik duplizieren. z.B. 'EXPECTED_TOTAL_PRICE = lineitems.inject {| sum, item | sum + = (item.price * item.quantity)} * (1-DISCOUNT_RATE) ' – Gishu

+0

Einverstanden. Ich denke, ich habe dein erstes Beispiel falsch interpretiert als einen Vorschlag, was zu tun ist, anstatt was nicht zu tun :-) –

Verwandte Themen