it-swarm-eu.dev

Wie schreibe ich "gute" Unit-Tests?

Ausgelöst durch dieser Thread denke ich (wieder) darüber nach, endlich Unit-Tests in meinen Projekten zu verwenden. Auf einigen Plakaten steht so etwas wie "Tests sind cool, wenn sie gute Tests sind". Meine Frage jetzt: Was sind "gute" Tests?

In meinen Anwendungen besteht der Hauptteil häufig in einer Art numerischer 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 nur jeden Fall zu testen, und die Methoden selbst oft recht lang sind und nicht ohne Leistungseinbußen überarbeitet werden können. Ich interessiere mich besonders für "gute" Tests für diese Art von Methode.

63
Jens

Die Kunst des Unit-Testens hat Folgendes zu Unit-Tests zu sagen:

Ein Unit-Test sollte folgende Eigenschaften haben:

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

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

Ich würde dringend 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), insbesondere, wenn Ihre Tests diese drei Eigenschaften haben, dann hat Ihr Code sie normalerweise auch.

52
Andy Lowry

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

Stellen Sie sich als stark vereinfachtes Beispiel vor, Sie haben eine Funktion, die durchschnittlich zwei Ints zurückgibt. Der umfassendste Test würde die Funktion aufrufen und prüfen, ob ein Ergebnis tatsächlich ein Durchschnitt ist. Dies macht überhaupt keinen Sinn: Sie spiegeln (replizieren) die Funktionalität, die Sie testen. Wenn Sie in der Hauptfunktion einen Fehler gemacht haben, machen Sie im Test denselben Fehler.

Mit anderen Worten, wenn Sie feststellen, dass Sie die Hauptfunktionalität im Komponententest replizieren, ist dies ein wahrscheinliches Zeichen dafür, dass Sie Ihre Zeit verschwenden.

43
mojuba

Gute Unit-Tests sind im Wesentlichen die Spezifikation in lauffähiger Form:

  1. beschreiben Sie das Verhalten des Codes entsprechend den Anwendungsfällen
  2. technische Eckfälle abdecken (was passiert, wenn Null übergeben wird) - Wenn für einen Eckfall kein Test vorhanden ist, 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.

10
user1249

für TDD testet "gut" Testfunktionen , die der Kunde wünscht ; Features entsprechen nicht unbedingt Funktionen, 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, erfinde ich etwas; hoffentlich ist es analg.

Beispielgeschichte:

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

Sie sprechen also mit den Piloten (und gegebenenfalls mit dem Zielcomputer). Zuerst sprechen Sie über das, was "normal" ist, und dann über das Abnormale. Sie finden heraus, worauf es in diesem Szenario wirklich ankommt, was häufig vorkommt, was unwahrscheinlich ist und was nur möglich ist.

Angenommen, Sie haben normalerweise ein halbes Sekundenfenster über sieben Kanäle mit Telemetriedaten: Geschwindigkeit, Nick, Roll, Gieren, Zielvektor, Zielgröße und Zielgeschwindigkeit. Diese Werte sind konstant oder ändern sich linear. Möglicherweise haben Sie weniger Kanäle und/oder die Werte ändern sich schnell. Also zusammen Sie kommen mit einigen Tests 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 kein Szenario für die in der Geschichte beschriebene besondere Situation gibt. Nach einem Gespräch mit dem Kunden und anderen Stakeholdern stellte sich heraus, dass das Ziel in der ursprünglichen Geschichte nur ein hypothetisches Beispiel war. Die eigentlichen Tests gingen aus der anschließenden Diskussion hervor. Das kann passieren. Die Geschichte sollte neu geschrieben werden, muss es aber nicht sein [da die Geschichte nur ein Platzhalter für ein Gespräch mit dem Kunden ist].

7
Steven A. Lowe

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

5
user281377

Ich habe viele Fälle gesehen, in denen Menschen einen enormen Aufwand betreiben, um 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, wir testen das". 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 nie ändert, wie erwartet funktionieren, selbst wenn bestimmte Teile davon punkten hoch auf einem Anrufdiagramm. Tests, die verbrauchen besagte Bibliothek (mein eigener Code) weisen darauf hin.

5
Tim Post

Nicht ganz so TDD, aber nachdem Sie in die Qualitätssicherung gegangen sind, 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 wertvoll sein, wenn Sie längerfristig Support leisten und an einen Ort gelangen, an dem Sie das Risiko eingehen, dass Menschen versehentlich alte Fehler wieder einführen. Ein Test zur Erfassung ist besonders wertvoll.

4
glenatron

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 Erfolgstests und dann vielleicht eine Handvoll Fehlertests pro öffentlicher Methode.

Ich benutze oft Modelle. 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 Modell-XMockup erstellen und es in meinen Tests anstelle von B verwenden. Es hilft wirklich, die Testausführung zu beschleunigen, die Testkomplexität zu verringern und auch die Anzahl der Tests zu reduzieren, die ich für A schreibe, da ich nicht mit den Besonderheiten von B fertig werden muss. Ich kann zum Beispiel testen, dass A X.someMethod () aufruft. anstelle eines Nebeneffekts beim Aufrufen von B.someMethod ().

Halten Sie Ihren Testcode ebenfalls sauber.

Wenn Sie eine API wie eine Datenbankebene verwenden, würde ich sie verspotten und dem Modell ermöglichen, bei jeder möglichen Gelegenheit auf Befehl eine Ausnahme auszulösen. Ich führe dann die Tests einen aus, ohne zu werfen, und den in einer Schleife, wobei jedes Mal bei der nächsten Gelegenheit eine Ausnahme ausgelöst wird, bis der Test erneut erfolgreich ist. Ein bisschen wie die für Symbian verfügbaren Speichertests.

Ich sehe, dass Andry Lowry bereits Roy Osheroves Unit-Test-Metriken veröffentlicht hat. aber es scheint, dass niemand das (kostenlose) Set präsentiert 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)
  • nabhängig (Tests sollten sich nicht gegenseitig einrichten oder abbauen)
  • Wiederholbar (sollte auf allen Umgebungen/Plattformen ausgeführt werden)
  • Selbstvalidierend (vollautomatisch; die Ausgabe sollte entweder "bestanden" oder "nicht bestanden" sein, keine Protokolldatei)
  • rechtzeitig (wann sie zu schreiben sind - kurz bevor sie den Produktionscode schreiben, den sie testen)
2
Kazark