Ich versuche, mit Unit-Tests klarzukommen.
Angenommen, wir haben einen Würfel, der eine Standardanzahl von Seiten von 6 hat (kann aber 4, 5 Seiten usw. haben):
import random
class Die():
def __init__(self, sides=6):
self._sides = sides
def roll(self):
return random.randint(1, self._sides)
Wäre das Folgende ein gültiger / nützlicher Komponententest?
- Testen Sie eine Rolle im Bereich von 1 bis 6 für einen 6-seitigen Würfel
- teste einen Wurf von 0 für einen 6-seitigen Würfel
- Teste einen Wurf von 7 für einen 6-seitigen Würfel
- Testen Sie eine Rolle im Bereich 1-3 für einen dreiseitigen Würfel
- teste einen Wurf von 0 für einen dreiseitigen Würfel
- Testen Sie eine Rolle mit 4 für einen dreiseitigen Würfel
Ich denke nur, dass dies eine Zeitverschwendung ist, da es das Zufallsmodul schon lange genug gibt, aber dann denke ich, wenn das Zufallsmodul aktualisiert wird (sagen wir, ich aktualisiere meine Python-Version), bin ich zumindest darüber informiert.
Muss ich in diesem Fall auch noch andere Variationen von Würfeln testen, z. B. die 3, oder ist es gut, einen anderen initialisierten Würfelzustand abzudecken?
python
unit-testing
tdd
Cybran
quelle
quelle
Antworten:
Sie haben Recht, Ihre Tests sollten nicht bestätigen, dass das
random
Modul seine Aufgabe erfüllt. Ein Unittest sollte nur die Klasse selbst testen, nicht die Interaktion mit anderem Code (der separat getestet werden sollte).Es ist natürlich durchaus möglich, dass Ihr Code
random.randint()
falsch verwendet; oder du callstrandom.randrange(1, self._sides)
stattdessen und dein Würfel wirft nie den höchsten Wert, aber das wäre eine andere Art von Bug, die du nicht mit einem Unittest fangen könntest. In diesem Fall funktioniert Ihrdie
Gerät wie geplant, aber das Design selbst war fehlerhaft.In diesem Fall würde ich die Funktion durch Verspotten ersetzen
randint()
und nur überprüfen, ob sie korrekt aufgerufen wurde . Python 3.3 und höher enthält dasunittest.mock
Modul für diese Art von Tests. Sie können das externemock
Paket jedoch auch auf älteren Versionen installieren , um genau die gleiche Funktionalität zu erhaltenMit dem Verspotten ist Ihr Test jetzt sehr einfach; es gibt eigentlich nur 2 fälle. Der Standardfall für einen 6-seitigen Würfel und der benutzerdefinierte Seitenfall.
Es gibt andere Möglichkeiten, die
randint()
Funktion im globalen Namespace von vorübergehend zu ersetzenDie
, aber dasmock
Modul macht dies am einfachsten. Der@mock.patch
Dekorateur gilt hier für alle Testmethoden im Testfall; Jeder Testmethode wird ein zusätzliches Argument übergeben, die verspotteterandom.randint()
Funktion, damit wir anhand der Verspottung testen können, ob sie tatsächlich korrekt aufgerufen wurde. Dasreturn_value
Argument gibt an, was vom Mock zurückgegeben wird, wenn es aufgerufen wird, sodass wir überprüfen können, ob diedie.roll()
Methode tatsächlich das "zufällige" Ergebnis an uns zurückgegeben hat.Ich habe hier eine andere, nicht zu testende, bewährte Methode von Python verwendet: Importieren Sie die zu testende Klasse als Teil des Tests. Das
_make_one
Verfahren hat den Import und Instanziierung Arbeit in einem Test , so dass das Testmodul noch selbst wird geladen , wenn Sie einen Syntaxfehler oder andere Fehler gemacht, die das ursprüngliche Modul Import verhindern werde.Wenn Sie auf diese Weise einen Fehler im Modulcode selbst gemacht haben, werden die Tests weiterhin ausgeführt. Sie werden nur scheitern und Sie über den Fehler in Ihrem Code informieren.
Die obigen Tests sind im Extremfall einfach. Das Ziel ist hier nicht zu testen
random.randint()
, was zum Beispiel mit den richtigen Argumenten aufgerufen wurde. Stattdessen soll getestet werden, ob das Gerät bei bestimmten Eingaben die richtigen Ergebnisse liefert, wobei diese Eingaben die Ergebnisse anderer nicht getesteter Geräte einschließen . Durch das Verspotten derrandom.randint()
Methode können Sie nur eine weitere Eingabe in Ihren Code steuern.In realen Tests wird der tatsächliche Code in Ihrem zu testenden Gerät komplexer. Die Beziehung zu Eingaben, die an die API übergeben werden, und wie andere Einheiten dann aufgerufen werden, kann immer noch interessant sein. Durch das Verspotten erhalten Sie Zugriff auf Zwischenergebnisse und können die Rückgabewerte für diese Aufrufe festlegen.
In Code, der Benutzer für einen OAuth2-Dienst eines Drittanbieters authentifiziert (eine mehrstufige Interaktion), möchten Sie beispielsweise testen, ob Ihr Code die richtigen Daten an diesen Dienst eines Drittanbieters weiterleitet, und Sie können verschiedene Fehlerantworten ausspotten Der Dienst eines Drittanbieters würde zurückkehren und Sie könnten verschiedene Szenarien simulieren, ohne selbst einen vollständigen OAuth2-Server erstellen zu müssen. Hier ist es wichtig zu testen, ob die Informationen aus einer ersten Antwort korrekt verarbeitet und an einen zweiten Aufruf weitergeleitet wurden. Sie möchten also sicherstellen, dass der gespielte Dienst korrekt aufgerufen wird.
quelle
randint()
, nicht der Code inDie.roll()
.sentinel.die
um beispielsweise ein Sentinel-Objekt zurückzugeben (Sentinel-Objekt stammt vonunittest.mock
zu), und überprüfen Sie dann, ob es das ist, was von Ihrer Roll-Methode zurückgegeben wurde. Dies ermöglicht tatsächlich nur einen Weg, die getestete Methode zu implementieren.sentinel.die
dies auf hervorragende Weise sicherstellen.Martijns Antwort lautet, wie Sie es machen würden, wenn Sie wirklich einen Test durchführen möchten, der zeigt, dass Sie random.randint aufrufen. Auf die Gefahr angesprochen zu werden, dass "das die Frage nicht beantwortet", sollte dies meines Erachtens überhaupt nicht auf die Einheit getestet werden. Randint zu verspotten ist kein Black-Box-Test mehr - Sie zeigen ausdrücklich, dass bestimmte Dinge in der Implementierung vor sich gehen . Black-Box-Tests sind noch nicht einmal eine Option - es gibt keinen Test, den Sie ausführen können, um zu beweisen, dass das Ergebnis niemals unter 1 oder über 6 liegen wird.
Kannst du dich lustig machen
randint
? Ja, du kannst. Aber was beweist du? Dass Sie es mit Argumenten 1 und Seiten bezeichnet haben. Was bedeutet , dass Mittel? Sie sind wieder auf dem ersten Platz - am Ende des Tages müssen Sie formal oder informell nachweisen, dassrandom.randint(1, sides)
ein Würfelwurf korrekt ausgeführt wird.Ich bin alle für Unit-Tests. Sie sind fantastische Gesundheitskontrollen und decken das Vorhandensein von Fehlern auf. Sie können jedoch niemals ihre Abwesenheit beweisen, und es gibt Dinge, die durch Tests überhaupt nicht bestätigt werden können (z. B. dass eine bestimmte Funktion niemals eine Ausnahme auslöst oder immer endet) gewinnen. Für deterministisches Verhalten sind Komponententests sinnvoll, da Sie tatsächlich wissen, welche Antwort Sie erwarten.
quelle
random.randint
mit der aufgerufen wird,1, sides
ist wertlos, wenn dies falsch ist.random.randint()
Werte im Bereich [1, Seiten] (einschließlich)random
korrekt zurückgegeben werden. Es liegt an den Python-Entwicklern, sicherzustellen, dass das Gerät ordnungsgemäß funktioniert.random.randint()
sich so verhalten,random.randrange()
und nennen esrandom.randint(1, sides + 1)
so), sind Sie trotzdem gesunken.Fix zufällige Samen. Vergewissern Sie sich bei 1, 2, 5 und 12-seitigen Würfeln, dass einige Tausend Würfe Ergebnisse einschließlich 1 und N und nicht einschließlich 0 oder N + 1 liefern decken Sie den erwarteten Bereich ab, wechseln Sie zu einem anderen Samen.
Spottwerkzeuge sind cool, aber nur weil man damit etwas machen kann, heißt das noch lange nicht, dass etwas gemacht werden sollte. YAGNI gilt für Testvorrichtungen ebenso wie für Funktionen.
Wenn Sie problemlos mit nicht gespielten Abhängigkeiten testen können, sollten Sie dies so ziemlich immer tun. Auf diese Weise konzentrieren sich Ihre Tests auf die Reduzierung der Fehleranzahl und nicht nur auf die Erhöhung der Testanzahl. Übermäßiges Verspotten kann zu irreführenden Berichterstattungszahlen führen, was wiederum dazu führen kann, dass die eigentlichen Tests auf eine spätere Phase verschoben werden. Vielleicht haben Sie nie Zeit, sich mit ...
quelle
Was ist ein,
Die
wenn Sie darüber nachdenken? - nicht mehr als ein Wrapper herumrandom
. Sie kapseltrandom.randint
und neu etikettiert , es in Bezug auf die Anwendung des eigenen Wortschatzes:Die.Roll
.Ich finde es nicht relevant, eine weitere Abstraktionsebene zwischen
Die
und einzufügen,random
da esDie
sich bereits um diese Indirektionsebene zwischen Ihrer Anwendung und der Plattform handelt.Wenn Sie Dosenwürfelergebnisse wünschen, verspotten
Die
random
Sie einfach, verspotten Sie nicht .Im Allgemeinen teste ich meine Wrapper-Objekte, die mit externen Systemen kommunizieren, nicht nach Einheiten, sondern schreibe Integrationstests für sie. Sie könnten ein paar davon schreiben,
Die
aber wie Sie betont haben, sind sie aufgrund der Zufälligkeit des zugrunde liegenden Objekts nicht aussagekräftig. Außerdem sind hier keine Konfiguration oder Netzwerkkommunikation erforderlich, sodass außer einem Plattformaufruf nicht viel zu testen ist.=> In Anbetracht der Tatsache, dass dies
Die
nur ein paar triviale Codezeilen sind und im Vergleich zu sichrandom
selbst wenig oder gar keine Logik hinzufügen , würde ich das Testen in diesem speziellen Beispiel überspringen.quelle
Das Setzen des Zufallszahlengenerators und das Überprüfen der erwarteten Ergebnisse ist meines Erachtens KEIN gültiger Test. Es wird davon ausgegangen, WIE Ihre Würfel intern funktionieren, was sehr ungezogen ist. Die Entwickler von Python könnten den Zufallszahlengenerator ändern oder den Würfel (HINWEIS: "Würfel" ist Plural, "Würfel" ist Singular. Wenn Ihre Klasse nicht mehrere Würfelwürfe in einem Aufruf implementiert, sollte sie wahrscheinlich "Würfel" heißen) Verwenden Sie einen anderen Zufallsgenerator.
Ebenso wird beim Verspotten der Zufallsfunktion davon ausgegangen, dass die Klassenimplementierung genau wie erwartet funktioniert. Warum könnte dies nicht der Fall sein? Jemand übernimmt möglicherweise die Kontrolle über den Standard-Python-Zufallszahlengenerator. Um dies zu vermeiden, ruft eine zukünftige Version Ihres Chips möglicherweise mehrere Zufallszahlen oder größere Zufallszahlen ab, um mehr Zufallsdaten einzumischen. Ein ähnliches Schema wurde von den Herstellern des FreeBSD-Betriebssystems verwendet, als sie den Verdacht hatten, dass der NSA die in CPUs eingebauten Hardware-Zufallszahlengeneratoren manipuliert.
Wenn ich es wäre, würde ich beispielsweise 6000 Rollen ausführen, sie zählen und sicherstellen, dass jede Zahl von 1 bis 6 500 bis 1500 Mal gewürfelt wird. Ich würde auch überprüfen, dass keine Zahlen außerhalb dieses Bereichs zurückgegeben werden. Ich könnte auch überprüfen, ob bei einem zweiten Satz von 6000 Rollen beim Bestellen von [1..6] in der Reihenfolge der Häufigkeit das Ergebnis anders ist (dies schlägt fehl, wenn die Zahlen zufällig sind!). Wenn Sie gründlich sein möchten, finden Sie möglicherweise die Häufigkeit von Zahlen nach einer 1, nach einer 2 usw .; Stellen Sie jedoch sicher, dass Ihre Stichprobengröße groß genug ist und Sie genügend Varianz haben. Menschen erwarten, dass Zufallszahlen weniger Muster haben als sie tatsächlich haben.
Wiederholen Sie diesen Vorgang für einen 12-seitigen und einen 2-seitigen Würfel (6 wird am häufigsten verwendet, so dass dies am meisten von jedem erwartet wird, der diesen Code schreibt).
Schließlich würde ich testen, um zu sehen, was mit einem einseitigen Würfel, einem 0-seitigen Würfel, einem -1-seitigen Würfel, einem 2,3-seitigen Würfel, einem [1,2,3,4,5,6] -seitigen Würfel und passiert ein "bla" -seitiger Würfel. Natürlich sollten diese alle scheitern; Scheitern sie auf nützliche Weise? Diese sollten wahrscheinlich bei der Erstellung scheitern, nicht beim Rollen.
Oder vielleicht möchten Sie auch anders damit umgehen - vielleicht sollte es akzeptabel sein, einen Würfel mit [1,2,3,4,5,6] zu erstellen - und vielleicht auch "blah"; Dies könnte ein Würfel mit 4 Gesichtern sein, auf jedem Gesicht befindet sich ein Buchstabe. Das Spiel "Boggle" kommt einem in den Sinn, ebenso wie eine magische Acht.
Und schließlich möchten Sie vielleicht Folgendes in Betracht ziehen: http://lh6.ggpht.com/-fAGXwbJbYRM/UJA_31ACOLI/AAAAAAAAAPg/2FxOWzo96KE/s1600-h/random%25255B3%25255D.jpg
quelle
Aufgrund der Gefahr, gegen den Strom zu schwimmen, habe ich dieses Problem vor einigen Jahren mit einer bisher nicht erwähnten Methode gelöst.
Meine Strategie bestand einfach darin, das RNG mit einem zu verspotten, der einen vorhersehbaren Strom von Werten erzeugt, der sich über den gesamten Raum erstreckt. Wenn (say) side = 6 ist und das RNG der Reihe nach Werte von 0 bis 5 erzeugt, kann ich vorhersagen, wie sich meine Klasse verhalten soll, und einen entsprechenden Komponententest durchführen.
Der Grund dafür ist, dass dies die Logik in dieser Klasse allein testet, unter der Annahme, dass der RNG schließlich jeden dieser Werte erzeugt, und ohne den RNG selbst zu testen.
Es ist einfach, deterministisch, reproduzierbar und es erkennt Fehler. Ich würde die gleiche Strategie wieder anwenden.
Die Frage legt nicht fest, wie die Tests aussehen sollten, sondern welche Daten bei Vorhandensein eines RNG für die Tests verwendet werden könnten. Mein Vorschlag ist lediglich, ausführlich zu testen, indem ich mich über das RNG lustig mache. Die Frage, was einen Test wert ist, hängt von Informationen ab, die in der Frage nicht enthalten sind.
quelle
Die Tests, die Sie in Ihrer Frage vorschlagen, erkennen keinen modularen arithmetischen Zähler als Implementierung. Und sie erkennen keine häufigen Implementierungsfehler in wahrscheinlichkeitsverteilungsbezogenem Code wie
return 1 + (random.randint(1,maxint) % sides)
. Oder eine Änderung am Generator, die zu zweidimensionalen Mustern führt.Wenn Sie tatsächlich sicherstellen möchten, dass Sie gleichmäßig verteilte zufällig auftauchende Zahlen generieren, müssen Sie eine Vielzahl von Eigenschaften überprüfen. Um einen einigermaßen guten Job zu machen, könnten Sie http://www.phy.duke.edu/~rgb/General/dieharder.php für Ihre generierten Zahlen ausführen . Oder schreiben Sie eine ähnlich komplexe Unit-Test-Suite.
Das ist nicht die Schuld von Unit-Testing oder TDD, Zufälligkeit ist einfach eine sehr schwer zu überprüfende Eigenschaft. Und ein beliebtes Thema für Beispiele.
quelle
Der einfachste Test für einen Würfelwurf besteht darin, ihn mehrere hunderttausend Mal zu wiederholen und zu bestätigen, dass jedes mögliche Ergebnis ungefähr (1 / Anzahl der Seiten) Mal getroffen wurde. Bei einem 6-seitigen Würfel sollte jeder mögliche Wert in etwa 16,6% der Fälle erreicht werden. Wenn einige um mehr als ein Prozent niedriger sind, haben Sie ein Problem.
Auf diese Weise vermeiden Sie, dass Sie den zugrunde liegenden Mechanismus der Generierung einer Zufallszahl einfach und vor allem ohne Änderung des Tests umgestalten können.
quelle