In meinen Unit-Tests gebe ich oft beliebige Werte in meinen Code ein, um zu sehen, was er tut. Wenn ich beispielsweise weiß, dass foo(1, 2, 3)
17 zurückgegeben werden soll, könnte ich Folgendes schreiben:
assertEqual(foo(1, 2, 3), 17)
Diese Zahlen sind rein willkürlich und haben keine weiter gefasste Bedeutung (es handelt sich beispielsweise nicht um Randbedingungen, obwohl ich diese auch teste). Ich würde mich bemühen, gute Namen für diese Nummern zu finden, und so etwas zu schreiben const int TWO = 2;
ist offensichtlich nicht hilfreich. Ist es in Ordnung, die Tests so zu schreiben, oder sollte ich die Zahlen in Konstanten zerlegen?
In sind alle magischen Zahlen die gleiche erstellt? Wir haben gelernt, dass magische Zahlen in Ordnung sind, wenn die Bedeutung aus dem Kontext ersichtlich ist, aber in diesem Fall haben die Zahlen tatsächlich überhaupt keine Bedeutung.
quelle
1, 2, 3
es sich beispielsweise um 3D-Array-Indizes handelt, in denen Sie den Wert zuvor gespeichert haben17
, ist dieser Test meiner Meinung nach fehlerhaft ( vorausgesetzt , Sie haben auch einige negative Tests). Wenn es sich jedoch um das Ergebnis einer Berechnung handelt, sollten Sie sicherstellen, dass jeder, der diesen Test liest, versteht, warum diesfoo(1, 2, 3)
so ist17
, und magische Zahlen erreichen dieses Ziel wahrscheinlich nicht.const int TWO = 2;
ist noch schlimmer als nur mit2
. Es entspricht dem Wortlaut der Regel mit der Absicht, ihren Geist zu verletzen.foo
, würde es nichts bedeuten, und so die Parameter. Aber in Wirklichkeit bin ich mir ziemlich sicher, dass die Funktion diesen Namen nicht hat und die Parameter keine Namenbar1
habenbar2
, undbar3
. Machen Sie ein realistischeres Beispiel , in dem die Namen haben eine Bedeutung, dann macht es viel mehr Sinn zu diskutieren , wenn die Testdatenwerte einen Namen brauchen, auch.Antworten:
Wann haben Sie wirklich Zahlen, die überhaupt keine Bedeutung haben?
Wenn die Zahlen eine Bedeutung haben, sollten Sie sie normalerweise lokalen Variablen der Testmethode zuweisen, um den Code lesbarer und selbsterklärender zu machen. Die Namen der Variablen sollten mindestens die Bedeutung der Variablen widerspiegeln, nicht unbedingt ihren Wert.
Beispiel:
Beachten Sie, dass die erste Variable nicht benannt ist
HUNDRED_DOLLARS_ZERO_CENT
, sondernstartBalance
um die Bedeutung der Variablen zu kennzeichnen, aber nicht, dass ihr Wert in irgendeiner Weise speziell ist.quelle
0.05f
einen extrahierenint
. :)const
Variablen formalisierten .calculateCompoundInterest
? In diesem Fall ist die zusätzliche Eingabe ein Beweis dafür, dass Sie die Dokumentation für die zu testende Funktion gelesen oder zumindest die von Ihrer IDE angegebenen Namen kopiert haben. Ich bin nicht sicher, wie viel dies dem Leser über die Absicht des Codes sagt, aber wenn Sie die Parameter in der falschen Reihenfolge übergeben, können sie zumindest sagen, was beabsichtigt war.Wenn Sie willkürliche Zahlen verwenden, nur um zu sehen, was sie tun, suchen Sie wahrscheinlich nach zufällig generierten Testdaten oder eigenschaftsbasierten Tests.
Beispielsweise ist Hypothesis eine coole Python-Bibliothek für diese Art von Tests und basiert auf QuickCheck .
Die Idee ist, sich nicht auf Ihre eigenen Werte zu beschränken, sondern zufällige Werte auszuwählen, mit denen überprüft werden kann, ob Ihre Funktion (en) ihren Spezifikationen entsprechen. Ein wichtiger Hinweis ist, dass sich diese Systeme im Allgemeinen alle fehlgeschlagenen Eingaben merken und sicherstellen, dass diese Eingaben in Zukunft immer getestet werden.
Punkt 3 kann für manche Leute verwirrend sein. Dies bedeutet nicht, dass Sie die genaue Antwort angeben - dies ist offensichtlich nicht für willkürliche Eingaben möglich. Stattdessen behaupten Sie etwas über eine Eigenschaft des Ergebnisses. Sie könnten beispielsweise behaupten, dass etwas nach dem Anhängen an eine Liste nicht mehr leer ist oder dass ein selbstausgleichender binärer Suchbaum tatsächlich ausgeglichen ist (unter Verwendung aller Kriterien, die für eine bestimmte Datenstruktur gelten).
Insgesamt ist es wahrscheinlich ziemlich schlecht, selbst willkürliche Zahlen zu wählen - das erhöht nicht wirklich den Wert und verwirrt jeden, der es liest. Es ist gut, eine Menge zufälliger Testdaten automatisch zu generieren und diese effektiv zu nutzen. Die Suche nach einer Hypothese oder einer QuickCheck-ähnlichen Bibliothek für die Sprache Ihrer Wahl ist wahrscheinlich eine bessere Möglichkeit, Ihre Ziele zu erreichen und gleichzeitig für andere verständlich zu bleiben.
quelle
foo
berechnet wird) ...? Wenn Sie zu 100% sicher wären, dass Ihr Code die richtige Antwort liefert, würden Sie diesen Code einfach in das Programm einfügen und nicht testen. Wenn nicht, müssen Sie den Test testen, und ich denke, jeder sieht, wohin das führt.d
Tagen sollte die Berechnung and
Tagen + 1 Monat einen bekannten höheren monatlichen Prozentsatz haben) usw.Der Name Ihres Komponententests sollte den größten Teil des Kontexts enthalten. Nicht aus den Werten der Konstanten. Der Name / die Dokumentation für einen Test sollte den entsprechenden Kontext und die Erklärung der im Test enthaltenen magischen Zahlen enthalten.
Wenn dies nicht ausreicht, sollte ein kleiner Teil der Dokumentation in der Lage sein, dies bereitzustellen (ob über den Variablennamen oder eine Dokumentzeichenfolge). Beachten Sie, dass die Funktion selbst Parameter enthält, die hoffentlich aussagekräftige Namen haben. Diese in Ihren Test zu kopieren, um die Argumente zu benennen, ist ziemlich sinnlos.
Und zuletzt, wenn Ihre Unittests so kompliziert sind, dass dies schwierig / unpraktisch ist, haben Sie wahrscheinlich zu komplizierte Funktionen und könnten überlegen, warum dies der Fall ist.
Je schlampiger Sie Tests schreiben, desto schlechter wird Ihr tatsächlicher Code. Wenn Sie der Meinung sind, dass Sie Ihre Testwerte benennen müssen, um den Test klarer zu machen, wird dringend empfohlen, dass Ihre tatsächliche Methode eine bessere Benennung und / oder Dokumentation benötigt. Wenn Sie die Notwendigkeit finden, Konstanten in Tests zu benennen, würde ich untersuchen, warum Sie dies benötigen - wahrscheinlich ist das Problem nicht der Test selbst, sondern die Implementierung
quelle
Dies hängt stark von der Funktion ab, die Sie testen. Ich kenne viele Fälle, in denen die einzelnen Zahlen für sich genommen keine besondere Bedeutung haben, der Testfall aber als Ganzes durchdacht aufgebaut ist und daher eine besondere Bedeutung hat. Das sollte man irgendwie dokumentieren. Wenn es sich beispielsweise
foo
tatsächlich um eine Methode handelt,testForTriangle
die entscheidet, ob die drei Zahlen gültige Längen der Kanten eines Dreiecks sind, sehen Ihre Tests möglicherweise folgendermaßen aus:und so weiter. Sie können dies verbessern und die Kommentare in einen Nachrichtenparameter umwandeln, der
assertEqual
angezeigt wird, wenn der Test fehlschlägt. Sie können dies dann weiter verbessern und in einen datengesteuerten Test umgestalten (sofern Ihr Test-Framework dies unterstützt). Trotzdem tun Sie sich selbst einen Gefallen, wenn Sie dem Code einen Vermerk hinzufügen, warum Sie diese Zahlen gewählt haben und welches der verschiedenen Verhaltensweisen Sie im Einzelfall testen.Natürlich könnten für andere Funktionen die einzelnen Werte für die Parameter von größerer Bedeutung sein.
foo
Daher ist es wahrscheinlich nicht die beste Idee , einen bedeutungslosen Funktionsnamen zu verwenden, wenn Sie nach dem Umgang mit der Bedeutung von Parametern fragen.quelle
Warum wollen wir benannte Konstanten anstelle von Zahlen verwenden?
Wenn Sie mehrere Komponententests mit jeweils 3 Zahlen (startBalance, interest, years) schreiben, packe ich die Werte einfach als lokale Variablen in den Komponententest. Der kleinste Bereich, in den sie gehören.
Wenn Sie eine Sprache verwenden, die benannte Parameter erlaubt, ist dies natürlich überflüssig. Dort würde ich einfach die Rohwerte in den Methodenaufruf packen. Ich kann mir kein Refactoring vorstellen, das diese Aussage prägnanter macht:
Oder verwenden Sie ein Test-Framework, mit dem Sie die Testfälle in einem Array- oder Map-Format definieren können:
quelle
Die Zahlen werden verwendet, um eine Methode aufzurufen, so dass die obige Prämisse sicherlich falsch ist. Es ist dir vielleicht egal, wie die Zahlen lauten, aber das ist nicht der springende Punkt. Ja, Sie könnten ableiten, wofür die Zahlen von einem IDE-Assistenten verwendet werden, aber es wäre weitaus besser, wenn Sie den Werten nur Namen geben würden - selbst wenn sie nur mit den Parametern übereinstimmen.
quelle
assertEqual "Returned value" (makeKindInt 42) (runTest "lvalue_operators")
). In diesem Beispiel42
handelt es sich nur um einen Platzhalterwert, der vom Code im Testskript mit dem Namen erstelltlvalue_operators
und dann überprüft wird, wenn er vom Skript zurückgegeben wird. Es hat überhaupt keine Bedeutung, außer dass derselbe Wert an zwei verschiedenen Stellen vorkommt. Was wäre hier ein passender Name, der tatsächlich eine sinnvolle Bedeutung hat?Wenn Sie eine reine Funktion an einem Satz von Eingängen testen möchten, bei denen es sich nicht um Randbedingungen handelt, möchten Sie sie mit ziemlicher Sicherheit an einer ganzen Reihe von Eingängen testen, bei denen es sich nicht um Randbedingungen handelt (und handelt). Und für mich bedeutet das, dass es eine Wertetabelle geben sollte, mit der die Funktion aufgerufen werden kann, und eine Schleife:
Mit Tools wie den in Dannnnos Antwort vorgeschlagenen können Sie die zu testende Wertetabelle erstellen .
bar
,,baz
undblurf
sollten durch aussagekräftige Namen ersetzt werden, wie in Philipps Antwort beschrieben .(Allgemeines Prinzip: Zahlen sind nicht immer "magische Zahlen", die Namen benötigen, sondern möglicherweise Daten . Wenn es sinnvoll wäre, Ihre Zahlen in ein Array, vielleicht ein Array von Datensätzen, zu schreiben, dann sind sie wahrscheinlich Daten Wenn Sie dagegen den Verdacht haben, Daten in Ihren Händen zu haben, ziehen Sie in Betracht, diese in ein Array aufzunehmen und weitere Daten zu erfassen.)
quelle
Tests unterscheiden sich vom Seriencode, und zumindest bei in Spock geschriebenen Komponententests, die kurz und sachlich sind, habe ich keine Probleme mit der Verwendung magischer Konstanten.
Wenn ein Test 5 Zeilen lang ist und dem vorgegebenen / when / then-Schema folgt, würde das Extrahieren solcher Werte in Konstanten den Code nur länger und schwerer lesbar machen. Wenn die Logik lautet "Wenn ich einen Benutzer namens Smith hinzufüge, wird der Benutzer Smith in der Benutzerliste angezeigt", hat es keinen Sinn, "Smith" in eine Konstante zu extrahieren.
Dies gilt natürlich, wenn Sie die Werte, die im Block "given" (setup) verwendet werden, problemlos mit denen in den Blöcken "when" und "then" abgleichen können. Wenn Ihr Testaufbau (im Code) von dem Ort getrennt ist, an dem die Daten verwendet werden, ist es möglicherweise besser, Konstanten zu verwenden. Da die Tests jedoch am besten in sich abgeschlossen sind, befindet sich das Setup in der Regel in der Nähe des Verwendungsorts und der erste Fall trifft zu, was bedeutet, dass magische Konstanten in diesem Fall durchaus akzeptabel sind.
quelle
Lassen Sie uns zunächst zustimmen, dass „Komponententest“ häufig verwendet wird, um alle automatisierten Tests abzudecken, die ein Programmierer schreibt, und dass es sinnlos ist zu diskutieren, wie jeder Test genannt werden sollte.
Ich habe an einem System gearbeitet, bei dem die Software viele Eingaben machte und eine „Lösung“ ausarbeitete, die einige Einschränkungen erfüllen und gleichzeitig andere Zahlen optimieren musste. Es gab keine richtigen Antworten, daher musste die Software nur eine vernünftige Antwort geben.
Dazu wurden viele Zufallszahlen verwendet, um einen Startpunkt zu erhalten, und anschließend ein „Bergsteiger“, um das Ergebnis zu verbessern. Dies wurde viele Male durchgeführt, um das beste Ergebnis zu erzielen. Ein Zufallszahlengenerator kann gesetzt werden, sodass er immer die gleichen Zahlen in der gleichen Reihenfolge ausgibt. Wenn der Test also einen Startwert festlegt, wissen wir, dass das Ergebnis bei jedem Lauf das gleiche ist.
Wir hatten viele Tests, die das oben Genannte ausführten, und prüften, ob die Ergebnisse gleich waren. Dies zeigte uns, dass wir nicht geändert hatten, was dieser Teil des Systems versehentlich beim Refactoring usw. tat. Es sagte uns nichts über die Richtigkeit von was dieser Teil des Systems tat.
Die Wartung dieser Tests war kostspielig, da jede Änderung des Optimierungscodes die Tests zum Erliegen bringen würde. Sie fanden jedoch auch einige Fehler im viel umfangreicheren Code, der die Daten vorverarbeitet und die Ergebnisse nachverarbeitet hat.
Als wir die Datenbank „verspotteten“, konnten Sie diese Tests als „Unit-Tests“ bezeichnen, aber die „Unit“ war ziemlich groß.
Wenn Sie an einem System ohne Tests arbeiten, führen Sie häufig die oben genannten Schritte aus, damit Sie bestätigen können, dass Ihr Refactoring die Ausgabe nicht ändert. Hoffentlich werden bessere Tests für neuen Code geschrieben!
quelle
Ich denke, in diesem Fall sollten die Zahlen als willkürliche Zahlen und nicht als magische Zahlen bezeichnet werden. Kommentieren Sie die Zeile einfach als "willkürlichen Testfall".
Sicher, einige magische Zahlen können auch willkürlich sein, wie für eindeutige "Handle" -Werte (die natürlich durch benannte Konstanten ersetzt werden sollten), aber auch vorberechnete Konstanten wie "Fluggeschwindigkeit eines unbeladenen europäischen Sperlings in Furlong pro vierzehn Tage". wobei der numerische Wert ohne Kommentare oder hilfreichen Kontext eingefügt wird.
quelle
Ich werde es nicht wagen, ein definitives Ja / Nein zu sagen, aber hier sind einige Fragen, die Sie sich stellen sollten, wenn Sie entscheiden, ob es in Ordnung ist oder nicht.
Wenn die Zahlen nichts bedeuten, warum gibt es sie überhaupt? Können sie durch etwas anderes ersetzt werden? Können Sie die Überprüfung auf der Grundlage von Methodenaufrufen und Flows anstelle von Wertbehauptungen durchführen? Stellen Sie sich so etwas wie Mockitos
verify()
Methode vor, die prüft, ob bestimmte Methodenaufrufe zum Verspotten von Objekten durchgeführt wurden, anstatt tatsächlich einen Wert zu bestätigen.Wenn die Zahlen haben , sollten sie Variablen zugewiesen werden, die entsprechend benannt sind.
Schreiben Sie die Nummer
2
wieTWO
es in bestimmten Kontexten hilfreich sein könnte, und nicht so sehr in anderen Kontexten.assertEquals(TWO, half_of(FOUR))
Sinnvoll für jemanden, der den Code liest. Es ist sofort klar was Sie testen.assertEquals(numCustomersInBank(BANK_1), TWO)
, dann bedeutet dies nicht machen , dass viel Sinn. Warum nichtBANK_1
enthalten zwei Kunden? Wofür testen wir?quelle