Wie schreibe ich Tests, die für Visualisierungssoftware sinnvoll sind?

8

Ich habe eine ziemlich große Software, die bestimmte Dateitypen verwendet und sie visualisiert / eine Vielzahl von Schaltflächen zur Bearbeitung des gezeichneten Bildes erstellt. Ich habe das Gefühl, dass ich Fehler / Codeteile finde, die nicht einmal pro Woche funktionieren, aber ich habe Probleme zu verstehen, wie ich Tests für diese Software schreiben kann.

Ich verstehe, wie wichtig Tests für Projekte wie Bibliotheken und APIs sind. Sie schreiben einfach Tests, die diese Funktionen verwenden.

Aber was ist mit Visualisierungssoftware? Aufgrund der visuellen Elemente scheint ein anderer Ansatz erforderlich zu sein.

Muss ich ein Testprogramm oder ein Testkabel schreiben, das jede Operation ausführt und manuell aufruft, sofern ich sie für die Daten verwenden kann?

Welchen Ansatz sollte ich verwenden, um mit dem Schreiben von Tests zu beginnen, um zu überprüfen, ob ich die Fehler behoben habe, und um mich zu benachrichtigen, wenn der Code erneut beschädigt wird?


Es gibt eine ähnliche , aber keine doppelte Frage in Bezug auf, wenn Sie Unit - Tests sollten. Da ich Fehler entdecke, möchte ich Tests schreiben, um zu verhindern, dass sich die Software erneut zurückbildet.

Need4Sleep
quelle

Antworten:

8

Es gibt einige Dinge, die Sie tun können, um das Testen solcher Software zu vereinfachen. Versuchen Sie zunächst, so viel wie möglich in Ebenen zu abstrahieren, die nicht visuell sind. Auf diese Weise können Sie nur Standard-Unit-Tests für diese unteren Ebenen schreiben. Wenn Sie beispielsweise eine Schaltfläche haben, die eine bestimmte Berechnung ausführt, stellen Sie sicher, dass Sie diese Berechnung in einem Komponententest mit einem regulären Funktionsaufruf durchführen können.

Der andere Vorschlag zum Testen grafikintensiver Programme besteht darin, eine Ausgabe zu erstellen, die ein Tester leicht manuell überprüfen kann. Ein Minecraft-Beispiel ist:

Minecraft-Testausgabe

Ich habe auch an Projekten gearbeitet, bei denen eine Reihe von Tests etwas auf dem Bildschirm wiedergaben, und dann den Tester aufgefordert, manuell zu überprüfen, ob es mit der Beschreibung übereinstimmt. So stellen Sie sicher, dass Sie Testfälle später nicht vergessen.

Das Testen ist oft sehr schwierig, wenn Sie ursprünglich nicht mit Blick auf das Testen entworfen haben. Arbeiten Sie einfach daran, Ihren fragilsten Code zuerst zu testen. Wenn Sie einen Fehler finden, führen Sie einen Test durch, der diesen Fehler abfängt, wenn er erneut auftritt. Das führt oft dazu, dass Sie verwandte Tests schreiben, während Sie gerade dabei sind.

Karl Bielefeldt
quelle
2
+1 und wenn die Testdaten ein statisches Bild darstellen, überprüfen Sie das Ergebnis beim ersten Mal manuell und speichern Sie den Screenshot für den programmatischen Vergleich danach
Steven A. Lowe
6

Alles hat eine Schnittstelle. Wenn ich meinen Testhut aufsetze, verwende ich eine bestimmte Weltanschauung, um einen Test zu schreiben:

  • Wenn etwas existiert, kann es gemessen werden.
  • Wenn es nicht gemessen werden kann, spielt es keine Rolle. Wenn es wichtig ist, habe ich noch keinen Weg gefunden, es zu messen.
  • Anforderungen schreiben messbare Eigenschaften vor oder sind nutzlos.
  • Ein System erfüllt eine Anforderung, wenn es von einem nicht erwarteten Zustand in den durch die Anforderung vorgeschriebenen erwarteten Zustand übergeht.
  • Ein System besteht aus interagierenden Komponenten, die Subsysteme sein können. Ein System ist korrekt, wenn alle Komponenten korrekt sind und die Interaktion zwischen den Komponenten korrekt ist.

In Ihrem Fall besteht Ihr System aus drei Hauptteilen:

  • eine Art von Daten oder Bildern, die aus Dateien initialisiert werden können
  • ein Mechanismus zum Anzeigen der Daten
  • ein Mechanismus zum Ändern der Daten

Das klingt für mich übrigens sehr nach der ursprünglichen Model-View-Controller-Architektur. Im Idealfall weisen diese drei Elemente eine lose Kopplung auf - das heißt, Sie definieren klare Grenzen zwischen ihnen mit genau definierten (und damit gut testbaren) Schnittstellen.

Eine komplexe Interaktion mit der Software kann in kleine Schritte übersetzt werden, die in Bezug auf die Elemente des Systems, das wir testen, formuliert werden können. Zum Beispiel:

Ich lade eine Datei mit einigen Daten. Es wird ein Diagramm angezeigt. Wenn ich einen Schieberegler in die Benutzeroberfläche ziehe, wird das Diagramm wackelig.

Dies scheint einfach manuell und automatisiert zu testen zu sein. Aber lassen Sie uns diese Geschichte in unser System übersetzen:

  • Die Benutzeroberfläche bietet einen Mechanismus zum Öffnen einer Datei: Der Controller ist korrekt.
  • Wenn ich eine Datei öffne, gibt der Controller einen entsprechenden Befehl an das Modell aus: Die Interaktion zwischen Controller und Modell ist korrekt.
  • Bei einer gegebenen Testdatei analysiert das Modell diese in die erwartete Datenstruktur: Das Modell ist korrekt.
  • Bei einer Testdatenstruktur rendert die Ansicht die erwartete Ausgabe: Die Ansicht ist korrekt. Einige Testdatenstrukturen sind normale Diagramme, andere wackelige Diagramme.
  • Die Interaktion Ansicht - Modell ist korrekt
  • Die Benutzeroberfläche bietet einen Schieberegler, mit dem das Diagramm wackelig wird: Der Controller ist korrekt.
  • Wenn der Schieberegler auf einen bestimmten Wert eingestellt ist, gibt der Controller den erwarteten Befehl an das Modell aus: Die Interaktion zwischen Controller und Modell ist korrekt.
  • Wenn ein Testbefehl bezüglich Wobbeligkeit empfangen wird, transformiert das Modell eine Testdatenstruktur in die erwartete Ergebnisdatenstruktur.

Nach Komponenten gruppiert, erhalten wir die folgenden zu testenden Eigenschaften:

  • Modell:
    • analysiert Dateien
    • reagiert auf den Befehl zum Öffnen von Dateien
    • bietet Zugriff auf Daten
    • reagiert auf wackeligen Befehl
  • Aussicht:
    • rendert Daten
  • Regler:
    • Bietet einen Workflow zum Öffnen von Dateien
    • gibt den Befehl zum Öffnen der Datei aus
    • Bietet einen wackeligen Workflow
    • gibt einen wackeligen Befehl aus
  • das ganze System:
    • Die Verbindung zwischen den Komponenten ist korrekt.

Wenn wir das Problem des Testens nicht in kleinere Untertests zerlegen, wird das Testen sehr schwierig und sehr fragil. Die obige Geschichte könnte auch implementiert werden als "Wenn ich eine bestimmte Datei lade und den Schieberegler auf einen bestimmten Wert setze, wird ein bestimmtes Bild gerendert". Dies ist fragil, da es kaputt geht, wenn sich ein Element im System ändert.

  • Es bricht ab, wenn ich die Steuerelemente auf Wackeligkeit ändere (z. B. Ziehpunkte in der Grafik anstelle eines Schiebereglers in einem Bedienfeld).
  • Es bricht ab, wenn ich das Ausgabeformat ändere (z. B. unterscheidet sich die gerenderte Bitmap, weil ich die Standardfarbe des Diagramms geändert habe oder weil ich Anti-Aliasing hinzugefügt habe, um das Diagramm glatter aussehen zu lassen. Beachten Sie dies in beiden Fällen).

Granulare Tests haben auch den großen Vorteil, dass ich das System weiterentwickeln kann, ohne befürchten zu müssen, dass eine Funktion beschädigt wird. Da alle erforderlichen Verhaltensweisen von einer vollständigen Testsuite gemessen werden, werden mich die Tests benachrichtigen, falls etwas kaputt geht. Da sie körnig sind, weisen sie mich auf den Problembereich hin. Wenn ich beispielsweise versehentlich die Schnittstelle einer Komponente ändere, schlagen nur die Tests dieser Schnittstelle fehl und kein anderer Test, der diese Schnittstelle indirekt verwendet.

Wenn das Testen einfach sein soll, erfordert dies ein geeignetes Design. Zum Beispiel ist es problematisch, wenn ich Komponenten in einem System fest verdrahtete: Wenn ich die Interaktion einer Komponente mit anderen Komponenten in einem System testen möchte, muss ich diese anderen Komponenten durch Teststubs ersetzen, mit denen ich protokollieren, überprüfen kann, und choreografiere diese Interaktion. Mit anderen Worten, ich benötige einen Mechanismus zur Abhängigkeitsinjektion, und statische Abhängigkeiten sollten vermieden werden. Beim Testen einer Benutzeroberfläche ist es eine große Hilfe, wenn diese Benutzeroberfläche skriptfähig ist.


Natürlich ist das meiste davon nur eine Fantasie einer idealen Welt, in der alles entkoppelt und leicht zu testen ist und fliegende Einhörner Liebe und Frieden verbreiten ;-) Während alles grundsätzlich testbar ist, ist es oft unerschwinglich schwierig, dies zu tun, und Sie haben es besser Nutzung Ihrer Zeit. Systeme können jedoch auf Testbarkeit ausgelegt werden, und in der Regel verfügen sogar testunabhängige Systeme über interne APIs oder Verträge, die getestet werden können (wenn nicht, wette ich, dass Ihre Architektur Mist ist und Sie einen großen Schlammball geschrieben haben). Nach meiner Erfahrung bewirken bereits geringe Mengen (automatisierter) Tests eine spürbare Qualitätssteigerung.

amon
quelle
2

Beginnen Sie mit einer bekannten Datei, die ein erwartetes Bild erzeugt. Überprüfen Sie jedes Pixel. Jeder sollte einen erwarteten Wert für eine bekannte, handgefertigte Testdatei haben. Sie sollten ein erwartetes Ausgabebild haben, mit dem Sie es vergleichen können. Alles, was "aus" ist, weist auf einen Fehler in Ihrem Code hin.

Erweitern Sie Ihre Testdatei, damit sich das Ausgabebild ändert und alle Funktionen Ihrer Software erfüllt.

Scripting wäre praktisch für diese Art von Black-Box-Tests. Ein einfaches Skript, das den neuesten Build Ihrer Software für die bekannte Eingabe und erwartete Ausgabe ausführt.

Unit-Tests hingegen sollten White-Box-Tests sein, bei denen Sie den kleinstmöglichen Teil der Software, normalerweise eine Funktion oder was auch immer, verwenden und prüfen, ob sie sich wie erwartet verhält. Sie können sehen, welche Pixelfarbe zurückgegeben wird oder was auch immer. In diesem Fall verhält sich Ihr Code wie eine Bibliothek mit APIs für alle anderen Abschnitte Ihres Codes.

Wenn alles in einer .c-Datei mit allen Funktionen enthalten ist main(), haben Sie größere Probleme als beim Testen.

Philip
quelle