Wie schreibe ich „gute“ Komponententests?

61

Ausgelöst von diesem Thread denke ich (wieder) darüber nach, endlich Unit-Tests in meinen Projekten zu verwenden. Ein paar Plakate dort sagen so etwas wie "Tests sind cool, wenn sie gute Tests sind". Meine Frage jetzt: Was sind "gute" Tests?

In meinen Anwendungen ist der Hauptteil häufig eine Art numerische Analyse, die von großen Mengen beobachteter Daten abhängt und zu einer Anpassungsfunktion führt, mit der diese Daten modelliert werden können. Ich fand es besonders schwierig, Tests für diese Methoden zu erstellen, da die Anzahl der möglichen Eingaben und Ergebnisse zu groß ist, um jeden Fall zu testen, und die Methoden selbst oft recht langwierig sind und nicht ohne weiteres überarbeitet werden können, ohne die Leistung zu beeinträchtigen. Ich interessiere mich besonders für "gute" Tests für diese Art von Methode.

Jens
quelle
8
Jeder gute Unit-Test sollte nur eines testen - wenn er fehlschlägt, sollten Sie genau wissen, was schief gelaufen ist.
Gablin
2
Bei großen Datenmengen empfiehlt es sich, generische Tests zu schreiben, die Datendateien als Eingabe verwenden können. Datendateien sollten normalerweise sowohl Eingaben als auch das erwartete Ergebnis enthalten. Mit xunit Test Frameworks können Sie Testfälle im laufenden Betrieb generieren - einen für jede Datenprobe.
Froderik
2
@gablin "Wenn es fehlschlägt, sollten Sie genau wissen, was schief gelaufen ist" würde vorschlagen, dass Tests mit mehreren möglichen Fehlerursachen in Ordnung sind, solange Sie die Ursache aus der Testausgabe ermitteln können ...?
user253751
Niemand scheint erwähnt zu haben, dass Unit-Tests testen können, wie lange die Operation dauert. Sie können Ihren Code im Hinblick auf die Leistung umgestalten, indem Sie sicherstellen, dass Sie beim Komponententest anhand der Zeit und der Ergebnisse feststellen können, ob der Test erfolgreich war oder fehlgeschlagen ist.
CJ Dennis

Antworten:

52

Die Kunst des Einheitentests sagt Folgendes über Einheitentests aus:

Ein Komponententest sollte folgende Eigenschaften haben:

  • Es sollte automatisiert und wiederholbar sein.
  • Die Implementierung sollte einfach sein.
  • Sobald es geschrieben ist, sollte es für die zukünftige Verwendung bleiben.
  • Jeder sollte es ausführen können.
  • Es soll auf Knopfdruck laufen.
  • Es sollte schnell laufen.

und später wird hinzugefügt, dass es vollständig automatisiert, vertrauenswürdig, lesbar und wartbar sein sollte.

Ich würde Ihnen wärmstens empfehlen, dieses Buch zu lesen, wenn Sie es noch nicht getan haben.

Meiner Meinung nach sind all diese sehr wichtig, aber die letzten drei (vertrauenswürdig, lesbar und wartbar) sind besonders wichtig, als ob Ihre Tests diese drei Eigenschaften haben, und Ihr Code hat sie normalerweise auch.

Andy Lowry
quelle
1
+1 für eine umfassende Liste für Unit- Tests (keine Integration oder Funktionstests)
Gary Rowe
1
+1 für den Link. Interessantes Material ist dort zu finden.
Joris Meys
1
"Schnell rennen" hat große Auswirkungen. Dies ist ein Grund, warum Komponententests unabhängig von externen Ressourcen wie Datenbank, Dateisystem, Webservice usw. ausgeführt werden sollten. Dies führt wiederum zu Spott / Stubs.
Michael Ostern
1
Bedeutet It should run at the push of a buttondies, dass für einen Komponententest weder Container (App-Server) ausgeführt werden müssen (für das zu testende Gerät), noch eine Ressourcenverbindung (z. B. DB, externe Webdienste usw.)? Ich bin nur verwirrt, welche Teile einer Anwendung Unit-getestet werden sollen und welche nicht. Mir wurde gesagt, dass Unit-Tests nicht von der DB-Verbindung und der Ausführung von Containern abhängen sollten, sondern stattdessen möglicherweise Mockups verwenden sollten.
Amphibient
42

Ein guter Komponententest spiegelt nicht die Funktion wider, die er testet.

Nehmen wir als stark vereinfachtes Beispiel an, Sie haben eine Funktion, die einen Durchschnitt von zwei ints zurückgibt. Der umfassendste Test würde die Funktion aufrufen und prüfen, ob ein Ergebnis tatsächlich ein Durchschnitt ist. Das macht überhaupt keinen Sinn: Sie spiegeln (replizieren) die Funktionalität, die Sie testen. Wenn Sie in der Hauptfunktion einen Fehler gemacht haben, werden Sie im Test den gleichen Fehler machen.

Mit anderen Worten, wenn Sie feststellen, dass Sie die Hauptfunktionen im Komponententest replizieren, ist dies wahrscheinlich ein Zeichen dafür, dass Sie Ihre Zeit verschwenden.

Mojuba
quelle
21
+1 Was Sie in diesem Fall tun würden, ist ein Test mit fest codierten Argumenten und ein Abgleich mit Ihrer bekannten Antwort.
Michael K
Ich habe diesen Geruch schon einmal gesehen.
Paul Butcher
Können Sie ein Beispiel für einen guten Komponententest für die Funktion geben, die Durchschnittswerte zurückgibt?
VLAS
2
@VLAS testet vordefinierte Werte, z. B. stellen Sie sicher, dass avg (1, 3) == 2 ist, und überprüfen Sie vor allem Flankenfälle wie INT_MAX, Nullen, negative Werte usw. Wenn ein Fehler in der Funktion gefunden und behoben wurde, fügen Sie einen weiteren hinzu Testen Sie, um sicherzustellen, dass dieser Fehler nie wieder auftritt.
Mojuba
Interessant. Wie schlagen Sie vor, die richtigen Antworten auf diese Testeingaben zu erhalten und möglicherweise nicht denselben Fehler zu machen wie den Code, der dem Test unterzogen wurde?
Timo
10

Gute Komponententests sind im Wesentlichen die Angaben in ausführbarer Form:

  1. Beschreiben Sie das Verhalten des Codes in Bezug auf Anwendungsfälle
  2. technische Eckfälle abdecken (was passiert, wenn null bestanden wird) - Wenn für einen Eckfall kein Test vorliegt, ist das Verhalten undefiniert.
  3. brechen, wenn der getestete Code von der Spezifikation abweicht

Ich habe festgestellt, dass Test-Driven-Development für Bibliotheksroutinen sehr gut geeignet ist, da Sie im Wesentlichen zuerst die API und dann die eigentliche Implementierung schreiben.


quelle
7

Bei TDD testet "gut" die Testfunktionen , die der Kunde wünscht . Features müssen nicht unbedingt Funktionen entsprechen, und Testszenarien sollten vom Entwickler nicht im luftleeren Raum erstellt werden

In Ihrem Fall - ich vermute - besteht das "Merkmal" darin, dass die Anpassungsfunktion die Eingabedaten innerhalb einer bestimmten Fehlertoleranz modelliert. Da ich keine Ahnung habe, was Sie wirklich tun, denke ich mir etwas aus. hoffentlich ist es anal.

Beispielgeschichte:

Als [X-Wing-Pilot] möchte ich [nicht mehr als 0,0001% Anpassungsfehler], damit [der Zielcomputer den Auspuff des Todessterns treffen kann, wenn er sich mit voller Geschwindigkeit durch eine Boxschlucht bewegt]

Sie sprechen also mit den Piloten (und mit dem Zielcomputer, falls dieser empfindungsfähig ist). Sprechen Sie zuerst über das, was "normal" ist, und dann über das Anormale. Sie erfahren, worauf es in diesem Szenario wirklich ankommt, was gemeinsam ist, was unwahrscheinlich ist und was nur möglich ist.

Angenommen, Sie haben normalerweise ein halbes Sekundenfenster über sieben Telemetriedatenkanäle: Geschwindigkeit, Neigung, Wanken, Gieren, Zielvektor, Zielgröße und Zielgeschwindigkeit. Diese Werte sind konstant oder ändern sich linear. Normalerweise haben Sie weniger Kanäle und / oder die Werte ändern sich schnell. Also zusammen kommen Sie mit einigen Tests auf wie:

//Scenario 1 - can you hit the side of a barn?
Given:
    all 7 channels with no dropouts for the full half-second window,
When:
    speed is zero
    and target velocity is zero
    and all other values are constant,
Then:
    the error coefficient must be zero

//Scenario 2 - can you hit a turtle?
Given:
    all 7 channels with no dropouts for the full half-second window,
When:
    speed is zero
    and target velocity is less than c
    and all other values are constant,
Then:
    the error coefficient must be less than 0.0000000001/ns

...

//Scenario 42 - death blossom
Given:
    all 7 channels with 30% dropout and a 0.05 second sampling window
When:
    speed is zero
    and position is within enemy cluster
    and all targets are stationary
Then:
    the error coefficient must be less than 0.000001/ns for each target

Jetzt haben Sie vielleicht bemerkt, dass es für die in der Geschichte beschriebene Situation kein Szenario gibt. Es stellte sich heraus, dass das Ziel in der ursprünglichen Geschichte nach einem Gespräch mit dem Kunden und anderen Interessengruppen nur ein hypothetisches Beispiel war. Die wirklichen Tests ergaben sich aus der anschließenden Diskussion. Das kann passieren. Die Geschichte sollte umgeschrieben werden, muss es aber nicht sein [da die Geschichte nur ein Platzhalter für ein Gespräch mit dem Kunden ist].

Steven A. Lowe
quelle
5

Erstellen Sie Tests für Eckfälle, z. B. ein Testset, das nur die Mindestanzahl von Eingaben (1 oder 0 möglich) und einige Standardfälle enthält. Diese Komponententests sind kein Ersatz für gründliche Abnahmetests und sollten es auch nicht sein.

user281377
quelle
5

Ich habe viele Fälle erlebt, in denen sich die Leute sehr viel Mühe geben, Tests für Code zu schreiben, der selten eingegeben wird, und keine Tests für Code, der häufig eingegeben wird.

Bevor Sie sich hinsetzen, um Tests zu schreiben, sollten Sie sich eine Art Anrufdiagramm ansehen, um sicherzustellen, dass Sie eine angemessene Abdeckung planen.

Außerdem glaube ich nicht daran, Tests zu schreiben, nur um zu sagen "Ja, das testen wir". Wenn ich eine Bibliothek verwende, die abgelegt wird und unveränderlich bleibt, werde ich keinen Tag damit verschwenden, Tests zu schreiben, um sicherzustellen, dass die Innereien einer API, die sich niemals ändern wird, wie erwartet funktionieren, auch wenn bestimmte Teile davon punkten hoch in einem Call-Diagramm. Tests, die diese Bibliothek (meinen eigenen Code) verwenden, weisen darauf hin.

Tim Post
quelle
Aber was passiert zu einem späteren Zeitpunkt, wenn die Bibliothek eine neuere Version mit einer Fehlerbehebung hat?
@ Thorbjørn Ravn Andersen - Es kommt auf die Bibliothek an, was sich geändert hat und wie sie getestet wurde. Ich werde keine Tests für Code schreiben, von denen ich weiß, dass er funktioniert, wenn ich ihn abgelegt habe, und niemals anfassen. Also, wenn es nach dem Update funktioniert, geht es aus dem Kopf :) Natürlich gibt es Ausnahmen.
Tim Post
Wenn Sie von Ihrer Bibliothek abhängig sind, können Sie zumindest Tests schreiben, die zeigen, was die Bibliothek tatsächlich tun soll.
... und wenn sich das ändert, testen Sie die Dinge, die die Bibliothek verbrauchen ... tl; dr; Ich muss die Innereien von Code von Drittanbietern nicht testen. Die Antwort wurde jedoch aus Gründen der Übersichtlichkeit aktualisiert.
Tim Post
4

Nicht ganz so TDD, aber nachdem Sie sich mit der Qualitätssicherung befasst haben, können Sie Ihre Tests verbessern, indem Sie Testfälle einrichten, um alle Fehler zu reproduzieren, die während des Qualitätssicherungsprozesses auftreten. Dies kann besonders nützlich sein, wenn Sie längerfristigen Support in Anspruch nehmen und an einen Ort gelangen, an dem Sie das Risiko eingehen, dass andere versehentlich alte Bugs wieder einführen. Es ist besonders wertvoll, einen Test zu haben, um dies zu erfassen.

Glenatron
quelle
3

Ich versuche bei jedem Test nur eines zu testen. Ich versuche, jedem Test einen Namen wie shouldDoSomething () zu geben. Ich versuche das Verhalten zu testen, nicht die Implementierung. Ich teste nur öffentliche Methoden.

Normalerweise habe ich einen oder mehrere Tests für den Erfolg und dann vielleicht eine Handvoll Tests für das Scheitern nach öffentlicher Methode.

Ich benutze viel Mock-ups. Ein gutes Mock-Framework wie PowerMock wäre wahrscheinlich sehr hilfreich. Obwohl ich noch keine benutze.

Wenn Klasse A eine andere Klasse B verwendet, würde ich eine Schnittstelle X hinzufügen, damit A B nicht direkt verwendet. Dann würde ich ein XMockup-Modell erstellen und es in meinen Tests anstelle von B verwenden. Es hilft wirklich, die Testausführung zu beschleunigen, die Testkomplexität zu reduzieren und die Anzahl der Tests zu verringern, die ich für A schreibe, da ich nicht mit den Besonderheiten von B fertig werden muss. Ich kann beispielsweise testen, dass A X.someMethod () aufruft. anstelle eines Nebeneffekts beim Aufrufen von B.someMethod ().

Halten Sie auch Ihren Testcode sauber.

Wenn Sie eine API wie eine Datenbankebene verwenden, verspotten Sie diese und aktivieren Sie das Modell, um bei jeder möglichen Gelegenheit auf Befehl eine Ausnahme auszulösen. Ich führe dann die Tests ein, ohne zu werfen, und das in einer Schleife, wobei jedes Mal eine Ausnahme bei der nächsten Gelegenheit geworfen wird, bis der Test erneut erfolgreich ist. Ein bisschen wie die für Symbian verfügbaren Speichertests.

Roger CS Wernersson
quelle
2

Ich sehe, dass Andry Lowry Roy Osheroves Einheitentestmetriken bereits veröffentlicht hat. aber es scheint, dass niemand das (kostenlose) Set vorgestellt hat, das Onkel Bob in Clean Code (132-133) gibt. Er benutzt das Akronym FIRST (hier mit meinen Zusammenfassungen):

  • Schnell (sie sollten schnell rennen, damit es den Leuten nichts ausmacht, sie zu rennen)
  • Unabhängig (Tests sollten nicht gegenseitig auf- oder abbauen)
  • Wiederholbar (sollte auf allen Umgebungen / Plattformen laufen)
  • Selbstvalidierung (vollautomatisch; die Ausgabe sollte entweder "bestanden" oder "nicht bestanden" sein, keine Protokolldatei)
  • Zum richtigen Zeitpunkt (Zeitpunkt des Schreibens - kurz vor dem Schreiben des von ihnen getesteten Produktionscodes)
Kazark
quelle