Orthogonalität von Komponententests im Vergleich zu Konzision von Komponententests

14

Ich schreibe Unit-Tests für ein Lenksystem für ein Videospiel. Das System hat verschiedene Verhaltensweisen (vermeiden Sie diesen Bereich aufgrund von Grund A, vermeiden Sie diesen Bereich aufgrund von Grund B, indem Sie jeweils ein wenig Kontext zu einer Karte der Region hinzufügen. Eine separate Funktion analysiert dann die Karte und erzeugt eine gewünschte Bewegung.

Ich habe Probleme bei der Entscheidung, wie ich die Unit-Tests für das Verhalten schreiben soll. Wie TDD vorschlägt, interessiert mich nur, wie sich das Verhalten auf die gewünschte Bewegung auswirkt. Zum Beispiel sollte das Vermeiden von Grund A zu einer Abkehr von der vorgeschlagenen schlechten Position führen. Es ist mir eigentlich egal, wie oder warum das Verhalten der Karte Kontext hinzufügt, nur dass die gewünschte Bewegung von der Position entfernt ist.

Meine Tests für jedes Verhalten richten das Verhalten ein, lassen es in die Karte schreiben und führen dann die Kartenanalysefunktion aus, um die gewünschte Bewegung zu ermitteln. Wenn diese Bewegung meinen Spezifikationen entspricht, bin ich glücklich.

Jetzt hängen meine Tests jedoch davon ab, dass sowohl das Verhalten als auch die Funktion zum Parsen der Karte ordnungsgemäß funktionieren. Wenn die Parsing-Funktion fehlschlägt, würde ich Hunderte von fehlgeschlagenen Tests anstelle von ein paar bekommen. Viele Leitfäden für das Schreiben von Tests halten dies für eine schlechte Idee.

Wenn ich jedoch direkt anhand der Ausgabe des Verhaltens teste, indem ich die Map verspotte, dann bin ich doch sicher zu eng mit der Implementierung verbunden? Wenn ich mit einem etwas anderen Verhalten die gleiche gewünschte Bewegung von der Karte erhalten kann, sollten die Tests dennoch erfolgreich sein.

Jetzt leide ich unter kognitiver Dissonanz. Wie lassen sich diese Tests am besten strukturieren?

Tenpn
quelle
Ich bin mir nicht sicher, ob es eine magische Antwort gibt. Grundsätzlich testen Sie Dinge, die brechen können. Im Idealfall können Sie also auf magische Weise erkennen, welche Tests auf niedriger Ebene bereits durch Tests auf hoher Ebene ausreichend abgedeckt waren, sodass sie keine eigenen Tests auf niedrigerer Ebene benötigten. Eine andere Sichtweise ist: Wie viel Zeit vergeht von dem Zeitpunkt, an dem ein Test fehlschlägt bis ein Entwickler das Problem behebt? Sie möchten, dass diese Zeit niedrig ist. Wenn Sie keine Einheiten, sondern nur perfekte Funktionstests (vollständige Abdeckung auf hoher Ebene) durchführen würden, wäre diese Zeit immer noch zu hoch. Versuchen Sie, dies als heuristische Anleitung zu verwenden.
Calphool

Antworten:

10

In der idealen Welt hätten Sie in der Tat eine Reihe von perfekt orthogonalen Einheitentests, alle auf derselben Abstraktionsebene.

In der realen Welt gibt es normalerweise Tests auf vielen verschiedenen Ebenen der App. Daher üben die Tests auf höheren Ebenen häufig Funktionen aus, die bereits von speziellen Tests auf niedrigeren Ebenen getestet wurden. (Viele Leute ziehen es vor, solche Tests auf höherer Ebene als Subsystem- / Integrationstests zu bezeichnen, anstatt Unit-Tests. Trotzdem können sie auf demselben Unit-Test-Framework ausgeführt werden. Aus technischer Sicht gibt es also keinen großen Unterschied.)

Ich denke nicht, dass das eine schlechte Sache ist. Es geht darum, Ihren Code so testen zu lassen, dass er am besten zu Ihrem Projekt und Ihrer Situation passt, und sich nicht an "den idealen Weg" zu halten.

Péter Török
quelle
Ich denke, der Hauptpunkt des Autors war, ob Sie Stubs / Mocks oder echte Implementierungen für diese übergeordneten Tests verwenden sollten
SiberianGuy
2

Diese Art von Tests ist der Grund, warum Mocks erfunden wurden. Die Hauptidee: Sie schreiben ein Mock für Ihr Objekt (Karte, Verhalten, Charakter, ...) und schreiben dann Tests, die dieses Mock anstelle des tatsächlichen Objekts verwenden. Die Leute nennen manchmal Spottstummel, und ich glaube, es gibt andere Wörter für beide.

In Ihrem Fall würden Sie immer dann einen Mock für die Karte schreiben, wenn Sie das Verhalten testen müssen, und andere Mocks für das Verhalten, wenn Sie die Karte testen möchten. Ihre Verspottungen wären idealerweise viel einfacher als die tatsächlichen Verhaltensweisen, die Sie verspotten, und enthalten nur Methoden oder Variablen, die Sie tatsächlich für diesen Test benötigen. Möglicherweise müssen Sie für jeden Test ein anderes Mock schreiben, oder Sie können einige Mocks wiederverwenden. Unabhängig davon sollten sie für den Test geeignet sein und nicht versuchen, dem tatsächlichen Verhalten so gut wie möglich zu entsprechen.

Wenn Sie einige Beispiele für Karten oder Verhaltensweisen einfügen, könnte möglicherweise jemand Beispiele für die Mocks bereitstellen, die Sie schreiben könnten. Nicht ich, da ich noch nie ein fortgeschritteneres Videospiel als Pong programmiert habe und schon damals einem Buch gefolgt bin, aber vielleicht jemand, der sowohl mit Unit-Tests als auch mit der Entwicklung von Spielen vertraut ist.

Trysis
quelle
0

Ich denke, Sie versuchen, Dinge zu testen, die viel höher sind als eine Einheit.

Die meisten Verhaltenstests würden eine gewisse Intelligenz erfordern, um festzustellen, ob sie sich richtig verhalten. Dies kann mit automatisierten Tests nicht einfach durchgeführt werden.

Oenone
quelle
Aus diesem Grund habe ich die Prägnanz erwähnt. Ich kann sehr einfach kleine Komponententests schreiben, um einzelne Codezeilen innerhalb eines Verhaltens zu testen, und das habe ich getan, aber es kommt darauf an, die Ausgabe der Map-Parsing-Funktion zu lesen. Keiner meiner Verhaltenstests ist groß, jeder testet nur einen Teil der Funktionalität des Verhaltens.
10.08.11