Gibt es so etwas wie zu viele Unit-Tests?


139

Ich wurde beauftragt, Komponententests für eine vorhandene Anwendung zu schreiben. Nachdem ich meine erste Datei fertiggestellt habe, habe ich 717 Zeilen Testcode für 419 Zeilen Originalcode.

Wird dieses Verhältnis unüberschaubar, wenn wir die Codeabdeckung erhöhen?

Mein Verständnis von Unit-Tests bestand darin, jede Methode in der Klasse zu testen, um sicherzustellen, dass jede Methode wie erwartet funktioniert. Bei der Pull-Anfrage meinte mein technischer Leiter jedoch, dass ich mich auf Tests auf höherer Ebene konzentrieren sollte. Er schlug vor, 4-5 Anwendungsfälle zu testen, die am häufigsten für die betreffende Klasse verwendet werden, anstatt jede Funktion ausführlich zu testen.

Ich vertraue dem Kommentar meines Technikers. Er hat mehr Erfahrung als ich, und er hat ein besseres Gespür für das Entwerfen von Software. Aber wie schreibt ein Team aus mehreren Personen Tests für einen so zweideutigen Standard? das heißt, woher kenne ich meine Kollegen und teile die gleiche Idee für die "häufigsten Anwendungsfälle"?

Für mich ist eine 100% ige Testabdeckung ein hohes Ziel, aber selbst wenn wir nur 50% erreichen würden, würden wir wissen, dass 100% dieser 50% abgedeckt sind. Andernfalls lässt das Schreiben von Tests für einen Teil jeder Datei viel Spielraum zum Schummeln.


145
Es hängt davon ab, ob. Schreiben Sie ein Tic-Tac-Toe-Spiel oder schreiben Sie Code für die Verwaltung eines Kernreaktors?
Bryan Oakley

11
Bei ausreichend vielen Komponententests könnten Sie exotische Hardware-Implementierungsprobleme wie den Pentium FDIV-Fehler oder Korrelationen in kryptografischen Primitiven erkennen. Es scheint also keine feste Grenze zu geben, über die hinaus keine weiteren Komponententests nützlich sein könnten. Nur ein praktisches Limit, wenn es zu teuer ist.
Nat

5
Durch Tests auf höheren Ebenen erhalten Sie einen besseren Überblick über die tatsächliche Abdeckung. Mit realer Abdeckung meine ich, dass es wahrscheinlicher ist, dass diese während der regulären Nutzung des Systems auftritt. Das ist die Art von Berichterstattung, die Sie zuerst erreichen möchten. In den 50%, die zuletzt erreicht wurden, könnte YAGNI oder toter Code, der einmal entfernt wurde, dazu beitragen, die Gesamtabdeckung zu erhöhen.
Laiv

5
Wenn Sie zu viele Tests erhalten (die Sie im Moment nicht zu haben scheinen), ist das wahrscheinlichste Problem, dass der Code, den Sie testen, zu viel bewirkt. Einzelverantwortung wird also nicht eingehalten. Wenn der Code gut aufgeteilt ist, verursacht das Testen auch keine große Belastung. Wenn der Unterricht viel leistet, viele Nebenwirkungen hat usw. wird es ein Albtraum.
Luc Franken

12
Das SQLite-Testdokument ist unterhaltsam zu lesen: sqlite.org/testing.html . Zitat: "Die SQLite-Bibliothek besteht aus ungefähr 122,9 KSLOC C-Code. Im Vergleich dazu verfügt das Projekt über 745-mal so viel Testcode und Testskripte - 91596.1 KSLOC."
user60561

Antworten:


180

Ja, mit 100% Deckung schreiben Sie einige Tests, die Sie nicht benötigen. Die einzig verlässliche Methode, um festzustellen, welche Tests Sie nicht benötigen, besteht darin, alle zu schreiben und dann etwa 10 Jahre zu warten, um festzustellen, welche niemals fehlgeschlagen sind.

Die Aufrechterhaltung vieler Tests ist normalerweise nicht problematisch. Viele Teams verfügen über automatisierte Integrations- und Systemtests, die über die 100% ige Abdeckung der Testeinheiten hinausgehen.

Sie befinden sich jedoch nicht in einer Testwartungsphase, sondern spielen Aufholjagd. Es ist viel besser, 100% Ihrer Klassen bei einer Testabdeckung von 50% zu haben als 50% Ihrer Klassen bei einer Testabdeckung von 100%, und Ihr Lead scheint Sie dazu zu bringen, Ihre Zeit entsprechend zu verteilen. Nachdem Sie diese Basislinie erreicht haben, müssen Sie im nächsten Schritt in der Regel alle Dateien, die in Zukunft geändert werden, zu 100% verschieben.


11
Danke für deine Antwort. Es hat mir geholfen, meine Frage in die richtige Perspektive zu rücken und das eigentliche Problem anzusprechen - meine Einstellung! +1
user2954463

43
@astra Deine Einstellung ist nicht so schlecht. Es ist gut zu fragen, warum. Um Ihre andere Frage zu beantworten, stellen Sie eine ausgezeichnete Frage: "Woher kenne ich meine Kollegen und ich teile die gleiche Idee für die" häufigsten Anwendungsfälle "? Sie veranlassen sie, sich Ihre Tests anzusehen. Schauen Sie sich ihre an. Sprechen Sie über sie. Sie werden lernen eine Menge und vielleicht auch sie. Code-Überprüfungstests sind selten Zeitverschwendung. Obwohl ich meine eher an einem Terminal als in einem Konferenzraum
mache

18
Ein Test, der in 10 Jahren niemals versagt, garantiert nicht einmal, dass er unnötig ist. Er könnte im 11. Jahr versagen.
Pharap,

24
Pragmatisch könnte man den umgekehrten Ansatz verfolgen. Schreiben Sie die Tests auf, die Ihrer Meinung nach die häufigsten Fälle abdecken. Schreiben Sie dann jedes Mal, wenn Sie auf einen Fehler stoßen, einen Test, um diesen Bereich abzudecken.
Stannius

10
@Pharap Mein einziges Problem bei dieser Antwort ist die implizite Annahme, dass ein Test nur dann einen Mehrwert bringen kann, wenn er fehlschlägt. Ein guter Komponententest bietet auch eine großartige Form lebendiger Dokumentation. Es hat auch einen Mehrwert gebracht, als Sie den Test geschrieben haben, indem Sie gezwungen wurden, über Wiederverwendbarkeit / Zusammensetzbarkeit / Verkapselung nachzudenken. Nach meiner Erfahrung handelt es sich bei ungeprüftem Code in der Regel um unflexible monolithische Bestien.
Kunst

66

Wenn Sie an großen Codebasen gearbeitet haben, die mit Test Driven Development erstellt wurden, wissen Sie bereits, dass es zu viele Komponententests geben kann. In einigen Fällen besteht der größte Teil des Entwicklungsaufwands in der Aktualisierung von Tests mit geringer Qualität, die am besten zur Laufzeit als invariante, vorausgesetzte und nachbedingte Prüfungen in den entsprechenden Klassen implementiert werden (dh als Nebeneffekt eines Tests auf höherer Ebene) ).

Ein weiteres Problem ist die Erstellung von Designs mit schlechter Qualität unter Verwendung von Konstruktionstechniken, die vom Frachtkult bestimmt werden und zu einer Vielzahl von zu testenden Dingen führen (mehr Klassen, Schnittstellen usw.). In diesem Fall scheint die Last darin zu liegen, den Testcode zu aktualisieren, aber das wahre Problem ist das Design mit schlechter Qualität.


16
Upvoted für den Hinweis auf Vorbedingungen, Nachbedingungen und Invarianten sollten als Unit-Tests behandelt werden. Auf diese Weise ist jede Verwendung ein Komponententest, wenn dieser Debug-Code aktiv ist.
Persixty

Dies ist eine großartige Antwort und passt perfekt zu meiner Erfahrung.
Tony Ennis

Und noch ein Problem: Wenn Sie Check-ins mit einem Gatter versehen haben (das sollten Sie wirklich tun!) Und große Mengen von geringer Qualität haben, verlangsamen möglicherweise sogar lange Testläufe alles, ohne dass dies echte Vorteile bringt. Und dann natürlich die lustige Tatsache, dass Sie eine Sache in einer Klasse ändern und Hunderte von Tests fehlschlagen.
Voo

3
Dies ist eine viel bessere Antwort als die akzeptierte! "In einigen Fällen besteht der größte Teil des Entwicklungsaufwands darin, Tests mit geringer Qualität zu aktualisieren" - das habe ich erlebt und es ist scheiße. In mancher Hinsicht mehr als gar keine Tests.
Benjamin Hodgson

36

Antworten auf Ihre Fragen

Gibt es so etwas wie zu viele Unit-Tests?

Sicher ... Sie könnten zum Beispiel mehrere Tests durchführen, die auf den ersten Blick unterschiedlich zu sein scheinen, aber wirklich dasselbe testen (logischerweise von denselben Zeilen "interessanten" zu testenden Anwendungscodes abhängen).

Oder Sie könnten Interna Ihres Codes testen, die niemals nach außen auftauchen (dh nicht Teil eines Schnittstellenvertrags sind), und sich darüber streiten, ob das überhaupt Sinn macht. Zum Beispiel den genauen Wortlaut von internen Logmeldungen oder was auch immer.

Ich wurde beauftragt, Komponententests für eine vorhandene Anwendung zu schreiben. Nachdem ich meine erste Datei fertiggestellt habe, habe ich 717 Zeilen Testcode für 419 Zeilen Originalcode.

Das kommt mir ganz normal vor. Ihre Tests erfordern neben den eigentlichen Tests viele Codezeilen für das Einrichten und Herunterfahren. Das Verhältnis kann sich verbessern oder nicht. Ich selbst bin ziemlich testlastig und investiere oft mehr Zeit und Ort in die Tests als der eigentliche Code.

Wird dieses Verhältnis unüberschaubar, wenn wir die Codeabdeckung erhöhen?

Das Verhältnis berücksichtigt nicht so viel. Es gibt andere Eigenschaften von Tests, die dazu neigen, sie unhandlich zu machen. Wenn Sie regelmäßig eine ganze Reihe von Tests überarbeiten müssen, wenn Sie eher einfache Änderungen an Ihrem Code vornehmen, sollten Sie sich die Gründe genau ansehen. Und das ist nicht, wie viele Zeilen Sie haben, sondern wie Sie sich der Kodierung der Tests nähern.

Mein Verständnis von Unit-Tests bestand darin, jede Methode in der Klasse zu testen, um sicherzustellen, dass jede Methode wie erwartet funktioniert.

Dies gilt im engeren Sinne für "Unit" -Tests. Hier ist "Einheit" so etwas wie eine Methode oder eine Klasse. Der Zweck des "Unit" -Tests besteht darin, nur eine bestimmte Codeeinheit zu testen, nicht das gesamte System. Idealerweise würden Sie den gesamten Rest des Systems entfernen (unter Verwendung von Doubles oder so weiter).

Bei der Pull-Anfrage meinte mein technischer Leiter jedoch, dass ich mich auf Tests auf höherer Ebene konzentrieren sollte.

Dann sind Sie in die Falle gegangen, dass angenommen wurde, dass Menschen Unit-Tests meinten , als sie Unit-Tests sagten . Ich habe viele Programmierer getroffen, die "Unit Test" sagen, aber etwas ganz anderes meinen.

Er schlug vor, 4-5 Anwendungsfälle zu testen, die am häufigsten für die betreffende Klasse verwendet werden, anstatt jede Funktion ausführlich zu testen.

Sicher, nur die Konzentration auf die obersten 80% des wichtigen Codes reduziert auch die Last ... Ich schätze, dass Sie sehr von Ihrem Chef überzeugt sind, aber das scheint mir nicht die optimale Wahl zu sein.

Für mich ist eine 100% ige Testabdeckung ein hohes Ziel, aber selbst wenn wir nur 50% erreichen würden, würden wir wissen, dass 100% dieser 50% abgedeckt sind.

Ich weiß nicht, was "Unit Test Coverage" ist. Ich gehe davon aus, dass Sie "Codeabdeckung" meinen, dh, dass nach dem Ausführen der Testsuite jede Codezeile (= 100%) mindestens einmal ausgeführt wurde.

Dies ist eine nette Ballpark-Metrik, aber bei weitem nicht der beste Standard, nach dem man schießen kann. Nur die Ausführung von Codezeilen ist nicht das ganze Bild; Dies berücksichtigt beispielsweise keine unterschiedlichen Pfade durch komplizierte, verschachtelte Verzweigungen. Es handelt sich eher um eine Metrik, die mit dem Finger auf zu wenig getestete Codeteile zeigt (wenn eine Klasse eine Codeabdeckung von 10% oder 5% aufweist, stimmt etwas nicht). Auf der anderen Seite sagt eine 100% ige Abdeckung nicht aus, ob Sie genug getestet haben oder ob Sie richtig getestet haben.

Integrationstests

Es ärgert mich sehr, wenn heute ständig über Unit- Tests gesprochen wird. Meiner Meinung nach (und meiner Erfahrung nach) eignen sich Unit- Tests hervorragend für Bibliotheken / APIs. In eher geschäftsorientierten Bereichen (wo wir über Anwendungsfälle wie in der vorliegenden Frage sprechen) sind sie nicht unbedingt die beste Option.

Für den allgemeinen Anwendungscode und in einem durchschnittlichen Unternehmen (wo Geld verdienen, Termine einhalten und die Kundenzufriedenheit erfüllen wichtig ist und Sie hauptsächlich Fehler vermeiden möchten, die entweder direkt im Gesicht des Benutzers liegen oder zu echten Katastrophen führen können - das sind wir nicht Hier werden NASA-Raketen gestartet), Integrations- oder Funktionstests sind viel nützlicher.

Diese gehen Hand in Hand mit verhaltensgesteuerter Entwicklung oder funktionsgesteuerter Entwicklung. diese funktionieren per definitionem nicht mit (strengen) Komponententests.

Um es kurz zu machen, wird bei einem Integrations- / Funktionstest der gesamte Anwendungsstapel ausgeführt. In einer webbasierten Anwendung, es ist wie ein Browser klicken durch die Anwendung handeln würde (und nein, natürlich ist es nicht hat , dass simpel zu sein, gibt es sehr leistungsfähigen Frameworks gibt, das zu tun - check out http: // Gurke. io für ein Beispiel).

Oh, um Ihre letzten Fragen zu beantworten: Sie sorgen dafür, dass Ihr gesamtes Team eine hohe Testabdeckung hat, indem Sie sicherstellen, dass ein neues Feature erst programmiert wird, nachdem sein Feature-Test implementiert wurde und fehlgeschlagen ist. Und ja, das bedeutet alles . Dies garantiert Ihnen eine 100% ige (positive) Funktionsabdeckung. Per Definition garantiert es, dass eine Funktion Ihrer Anwendung niemals "verschwindet". Es wird keine 100% ige Codeabdeckung garantiert (wenn Sie beispielsweise negative Funktionen nicht aktiv programmieren, üben Sie Ihre Fehlerbehandlung / Ausnahmebehandlung nicht aus).

Es garantiert Ihnen keine fehlerfreie Anwendung. Natürlich möchten Sie Funktionstests für offensichtliche oder sehr gefährliche Buggysituationen, falsche Benutzereingaben, Hacking (z. B. Umgebungssitzungsverwaltung, Sicherheit usw.) schreiben. Aber selbst die Programmierung der positiven Tests hat einen enormen Vorteil und ist mit modernen, leistungsstarken Frameworks durchaus machbar.

Feature- / Integrationstests haben offensichtlich ihre eigenen Würmer (z. B. Leistung; redundantes Testen von Frameworks von Drittanbietern; da Sie in der Regel keine Doubles verwenden, ist es nach meiner Erfahrung auch schwieriger, sie zu schreiben ...), aber ich d Nehmen Sie eine Anwendung, die zu 100% auf positive Merkmale getestet wurde, über eine Anwendung, die zu 100% auf Code-Coverage-Einheiten getestet wurde (keine Bibliothek!), jeden Tag.


1
Integrationstests sind großartig, aber kein Ersatz für Komponententests, auch nicht für Geschäftsanwendungen. Es gibt mehrere Probleme mit ihnen: a) Sie benötigen per Definition viel Zeit für die Ausführung (dies bedeutet auch, dass inkrementelle Tests so gut wie nutzlos sind), b) es ist unglaublich schwierig, das eigentliche Problem zu lokalisieren (oh, 50 Integrationstests sind gerade fehlgeschlagen, Welche Änderung hat das verursacht?) und c) sie decken wiederholt dieselben Codepfade ab.
Voo

1
a) ist ein Problem, weil es das Ausführen der Tests auf gesperrten Check-ins umständlich macht und es weniger wahrscheinlich macht, dass Programmierer die Tests während der Entwicklung wiederholt ausführen, was zusammen mit b) die Effizienz verringert und die Fähigkeit, Fehler schnell zu diagnostizieren, verringert. c) bedeutet, dass das Ändern einer Kleinigkeit leicht dazu führen kann, dass Dutzende oder Hunderte Ihrer Integrationstests fehlschlagen, was bedeutet, dass Sie viel Zeit damit verbringen, sie zu beheben. Dies bedeutet auch, dass Integrationstests meist nur zufriedene Pfade testen, da das gezielte Schreiben dieser Tests umständlich oder unmöglich ist.
Voo

1
@Voo, alles, was du geschrieben hast, ist wahr, und soweit ich das
beurteilen

Wenn Sie dieser Zusammenfassung zustimmen, verstehe ich nicht, wie Sie zu dem Schluss kommen können, dass Sie Integrationstests den Unit-Tests vorziehen würden. Umfassende Integrationstestsuiten für große Programme benötigen Stunden oder sogar Tage. Sie sind großartig, aber während der eigentlichen Entwicklung nahezu unbrauchbar. Und Ihre Akzeptanztests (die jeder macht, oder?) Werden viele der Probleme aufdecken, die Integrationstests bei Unit-Tests vermissen würden - das Gegenteil ist jedoch nicht der Fall.
Voo

24

Ja, es können zu viele Unit-Tests durchgeführt werden. Wenn Sie beispielsweise eine 100-prozentige Abdeckung mit Unit-Tests und keine Integrationstests haben, liegt ein klares Problem vor.

Einige Szenarien:

  1. Sie überentwickeln Ihre Tests zu einer bestimmten Implementierung. Dann müssen Sie Unit-Tests wegwerfen, wenn Sie überarbeiten, und nicht, wenn Sie die Implementierung ändern (ein sehr häufiger Schmerzpunkt bei der Durchführung von Leistungsoptimierungen).

    Ein ausgewogenes Verhältnis zwischen Komponententests und Integrationstests verringert dieses Problem, ohne dass eine erhebliche Abdeckung verloren geht.

  2. Sie könnten eine angemessene Deckung für jedes Commit mit 20% der von Ihnen durchgeführten Tests haben und die verbleibenden 80% für die Integration oder zumindest separate Testdurchläufe übrig lassen. Die größten negativen Auswirkungen in diesem Szenario sind langsame Änderungen, da Sie lange auf die Ausführung der Tests warten müssen.

  3. Sie ändern den Code zu stark, um ihn testen zu können. Ich habe zum Beispiel viel Missbrauch von IoC an Komponenten gesehen, die niemals modifiziert werden müssen, oder es ist zumindest kostspielig und von geringer Priorität, sie zu generalisieren, aber die Leute investieren viel Zeit, um sie zu generalisieren und zu überarbeiten, damit sie in Einheiten getestet werden können .

Ich stimme insbesondere dem Vorschlag zu, 100% der Dateien mit 50% anstatt mit 100% zu bedecken. Konzentrieren Sie sich zunächst auf die häufigsten positiven Fälle und die gefährlichsten negativen Fälle. Investieren Sie nicht zu viel in die Fehlerbehandlung und in ungewöhnliche Pfade, nicht weil sie nicht wichtig sind, sondern weil Sie nur über eine begrenzte Zeit und ein unendliches Testuniversum verfügen. Sie müssen also auf jeden Fall Prioritäten setzen.


2
Dies ist kein Problem bei den Komponententests, sondern bei der Organisation, weil ihre Prioritäten falsch sind, indem eine bestimmte Anzahl für die Abdeckung von Komponententests verlangt wird, ohne dass die Ressourcen für die Erstellung und Durchführung geeigneter Tests auf anderen Ebenen aufgewendet werden müssen.
4.

2
Stimmen Sie Nummer 3 nachdrücklich zu und erweitern Sie sie auch auf die manuelle Übergabe von Instanzen von Klassen niedrigerer Ebene an Klassen höherer Ebene. Wenn ein High-Level-Ding auf ein Low-Level-Ding angewiesen ist, um etwas zu erledigen, ist das in Ordnung. Wenn es die Details davon vor Anrufern verbirgt, würde ich das gutes Design nennen. Aber wenn Sie dann Low-Level-Ding zu einem Teil der Oberfläche von High-Level-Ding machen und Anrufer dazu bringen, es weiterzuleiten, weil es Ihre Tests hübsch macht, wedelt der Schwanz mit dem Hund. (Wenn Low-Level-Ding an vielen Orten wiederverwendet wird und sich viel ändert, ändert sich das. Meiner Erfahrung nach war das nicht typisch.)
johncip

Ich liebe deine Beschreibung @johncip, das ist definitiv ein häufiges Beispiel dafür, wie eine nette Klasse schrecklich wird, wenn man dem Konstruktor eine Reihe unnötiger erforderlicher Parameter hinzufügt ...
Bruno Guardia

19

Denken Sie daran, dass jeder Test sowohl Kosten als auch Nutzen hat. Nachteile sind:

  • ein Test muss geschrieben werden;
  • Ein Test benötigt (normalerweise nur sehr wenig) Zeit.
  • Ein Test muss mit dem Code gepflegt werden. Tests müssen sich ändern, wenn sich die zu testenden APIs ändern.
  • Sie müssen möglicherweise Ihr Design ändern, um einen Test zu schreiben (obwohl diese Änderungen in der Regel zum Besseren sind).

Wenn die Kosten die Vorteile überwiegen, ist es besser, einen Test nicht zu schreiben. Wenn sich beispielsweise die Funktionalität nur schwer testen lässt, sich die API häufig ändert, die Richtigkeit relativ unwichtig ist und die Wahrscheinlichkeit, dass der Test einen Defekt feststellt, gering ist, sollten Sie ihn wahrscheinlich besser nicht schreiben.

Was Ihr spezielles Verhältnis von Tests zu Code betrifft, so kann dieses Verhältnis garantiert werden, wenn der Code ausreichend logisch dicht ist. Es lohnt sich jedoch wahrscheinlich nicht, während einer typischen Anwendung ein so hohes Verhältnis aufrechtzuerhalten.


12

Ja, es gibt zu viele Unit-Tests.

Während das Testen gut ist, ist jeder Komponententest:

  • Eine potenzielle Wartungslast, die eng mit der API verbunden ist

  • Zeit, die für etwas anderes verwendet werden könnte

  • Ein bisschen Zeit in der Unit Test Suite
  • Möglicherweise wird kein echter Wert hinzugefügt, da es sich tatsächlich um ein Duplikat eines anderen Tests handelt, bei dem die Wahrscheinlichkeit gering ist, dass ein anderer Test erfolgreich ist und dieser Test fehlschlägt.

Es ist ratsam, eine Codeabdeckung von 100% anzustreben, aber dies bedeutet keineswegs eine Reihe von Tests, von denen jeder unabhängig eine Codeabdeckung von 100% an einem bestimmten Einstiegspunkt (Funktion / Methode / Aufruf usw.) bietet.

Obwohl es angesichts der Tatsache, dass es schwierig ist, eine gute Abdeckung zu erzielen und Bugs auszutreiben, wahrscheinlich ist, dass es so etwas wie "die falschen Komponententests" gibt, so viel wie "zu viele Komponententests".

Pragmatik für die meisten Codes bedeutet:

  1. Stellen Sie sicher, dass Sie eine 100% ige Abdeckung der Einstiegspunkte haben (alles wird irgendwie getestet), und streben Sie eine nahezu 100% ige Codeabdeckung der fehlerfreien Pfade an.

  2. Testen Sie alle relevanten Min / Max-Werte oder Größen

  3. Testen Sie alles, was Sie für einen witzigen Sonderfall halten, besonders 'ungerade' Werte.

  4. Wenn Sie einen Fehler finden, fügen Sie einen Komponententest hinzu, der diesen Fehler aufgedeckt hätte, und überlegen Sie, ob ähnliche Fälle hinzugefügt werden sollten.

Für komplexere Algorithmen berücksichtigen Sie auch:

  1. Führen Sie einige Bulk-Tests für mehr Fälle durch.
  2. Vergleich des Ergebnisses mit einer "Brute-Force" -Implementierung und Überprüfung der Invarianten.
  3. Verwenden einer Methode zum Erstellen von zufälligen Testfällen und Überprüfen auf Brute-Force- und Post-Bedingungen, einschließlich Invarianten.

Überprüfen Sie beispielsweise einen Sortieralgorithmus mit zufälligen Eingaben, und überprüfen Sie, ob die Daten am Ende sortiert sind, indem Sie sie scannen.

Ich würde sagen, Ihr Tech-Lead schlägt das Testen mit "minimal nacktem Arsch" vor. Ich biete hochwertige Qualitätsprüfungen an und dazwischen liegt ein Spektrum.

Vielleicht weiß Ihr Vorgesetzter, dass die Komponente, die Sie bauen, in ein größeres Teil eingebettet und die Einheit bei der Integration gründlicher getestet wird.

Die wichtigste Lektion besteht darin, Tests hinzuzufügen, wenn Fehler gefunden werden. Was mich zu meiner besten Lektion über die Entwicklung von Komponententests führt:

Konzentrieren Sie sich auf Einheiten, nicht auf Untereinheiten. Wenn Sie eine Einheit aus Untereinheiten aufbauen, schreiben Sie sehr einfache Tests für die Untereinheiten, bis sie plausibel sind und eine bessere Abdeckung erzielen, indem Sie Untereinheiten durch ihre Kontrolleinheiten testen.

Also, wenn Sie einen Compiler schreiben und eine Symboltabelle schreiben müssen (sagen wir). Bringen Sie die Symboltabelle mit einem Basistest zum Laufen und arbeiten Sie dann an dem Deklarationsparser, der die Tabelle füllt. Fügen Sie der Symboltabelle "Stand-alone" nur dann weitere Tests hinzu, wenn Sie Fehler darin finden. Andernfalls erhöhen Sie die Abdeckung durch Komponententests für den Deklarationsparser und später für den gesamten Compiler.

Das ist das Beste für wenig Geld (ein Test des Ganzen testet mehrere Komponenten) und lässt mehr Kapazität für Re-Design und Verfeinerung übrig, da in Tests, die tendenziell stabiler sind, nur die "äußere" Schnittstelle verwendet wird.

Zusammen mit den Testvoraussetzungen für den Debug-Code und den Post-Bedingungen, einschließlich Invarianten auf allen Ebenen, erhalten Sie maximale Testabdeckung bei minimaler Testimplementierung.


4
Ich würde nicht sagen, dass eine 100% ige Abdeckung pragmatisch ist. 100% Deckung ist ein extrem hoher Standard.
Bryan Oakley

Leider kann auch die randomisierte Methode Fehler übersehen. Es gibt keinen Ersatz für Beweise, auch wenn diese informell sind.
Frank Hileman

@BryanOakley Punkt genommen. Das ist eine Übertreibung. Aber es ist wichtiger, sich dem anzunähern, als dass die Leute Kredit geben. "Ich habe den einfachen Weg getestet, es ist alles gut", wird später immer wieder Probleme bereiten.
Persixty

@FrankHileman Die Frage war nicht "Ist Unit Testing ein guter Ersatz für das sorgfältige Entwerfen von Software, statische Überprüfungslogik und Beweisen von Algorithmen", dann lautet die Antwort "Nein". Weder Methode wird qualitativ hochwertige Software für sich produzieren.
Persixty

3

Erstens ist es nicht unbedingt ein Problem, mehr Testzeilen als Produktionscode zu haben. Testcode ist (oder sollte) linear und leicht zu verstehen - seine notwendige Komplexität ist sehr, sehr gering, unabhängig davon, ob es sich um den Produktionscode handelt oder nicht. Wenn sich die Komplexität der Tests der des Produktionscodes nähert, liegt wahrscheinlich ein Problem vor.

Ja, es ist möglich, dass zu viele Komponententests durchgeführt werden. Ein einfaches Gedankenexperiment zeigt, dass Sie weiterhin Tests hinzufügen können, die keinen zusätzlichen Wert bieten, und dass all diese hinzugefügten Tests zumindest einige Refactorings verhindern können.

Der Ratschlag, nur die häufigsten Fälle zu testen, ist meiner Meinung nach fehlerhaft. Diese Tests können als Rauchtests dienen, um Zeit für Systemtests zu sparen, aber die wirklich wertvollen Tests erfassen Fälle, die im gesamten System schwer auszuüben sind. Beispielsweise kann eine gesteuerte Fehlerinjektion von Speicherzuweisungsfehlern verwendet werden, um Wiederherstellungspfade auszuüben, die ansonsten von völlig unbekannter Qualität sein könnten. Oder übergeben Sie Null als Wert, von dem Sie wissen, dass er als Divisor verwendet wird (oder als negative Zahl mit Quadratwurzel), und stellen Sie sicher, dass Sie keine unbehandelte Ausnahme erhalten.

Die nächsten wertvollen Tests sind diejenigen, die die äußersten Grenzen oder Grenzpunkte ausüben. Zum Beispiel sollte eine Funktion, die (1-basierte) Monate des Jahres akzeptiert, mit 0, 1, 12 und 13 getestet werden, damit Sie wissen, dass sich die gültigen / ungültigen Übergänge an der richtigen Stelle befinden. Es ist übertrieben, auch 2..11 für diese Tests zu verwenden.

Sie befinden sich in einer schwierigen Situation, da Sie Tests für vorhandenen Code schreiben müssen. Es ist einfacher, die Randfälle zu identifizieren, während Sie den Code schreiben (oder kurz davor stehen, ihn zu schreiben).


3

Mein Verständnis von Unit-Tests bestand darin, jede Methode in der Klasse zu testen, um sicherzustellen, dass jede Methode wie erwartet funktioniert.

Dieses Verständnis ist falsch.

Unit-Tests verifizieren das Verhalten des Prüflings .

In diesem Sinne ist eine Einheit nicht unbedingt "eine Methode in einer Klasse". Ich mag die Definition einer Einheit von Roy Osherove in The Art of Unit Testing :

Eine Einheit ist der gesamte Produktionscode, der denselben Änderungsgrund hat.

Auf dieser Grundlage sollte ein Komponententest jedes gewünschte Verhalten Ihres Codes verifizieren . Wo der "Wunsch" mehr oder weniger von den Anforderungen genommen wird.


Bei der Pull-Anfrage meinte mein technischer Leiter jedoch, dass ich mich auf Tests auf höherer Ebene konzentrieren sollte.

Er hat recht, aber anders als er denkt.

Aus Ihrer Frage geht hervor, dass Sie der "engagierte Tester" in diesem Projekt sind.

Das große Missverständnis ist, dass er erwartet, dass Sie Komponententests schreiben (im Gegensatz zu "Testen mit einem Komponententest-Framework"). Das Schreiben von ynit-Tests liegt in der Verantwortung der Entwickler , nicht der Tester (in einer idealen Welt, weiß ich ...). Andererseits haben Sie diese Frage mit TDD markiert, was genau dies impliziert.

Ihre Aufgabe als Tester ist es, Modul- und / oder Anwendungstests zu schreiben (oder manuell auszuführen). Und diese Art von Tests sollte hauptsächlich sicherstellen, dass alle Einheiten reibungslos zusammenarbeiten. Das heißt, Sie müssen Ihre Testfälle so auswählen, dass jede Einheit mindestens einmal ausgeführt wird . Und diese Prüfung ist, dass läuft. Das tatsächliche Ergebnis ist weniger wichtig, da es sich aufgrund zukünftiger Anforderungen ändern kann.

Um noch einmal die Analogie des Kippers zu betonen: Wie viele Tests werden mit einem Auto am Ende der Montagelinie durchgeführt? Genau eines: Es muss von alleine zum Parkplatz fahren ...

Der Punkt hier ist:

Wir müssen uns des Unterschieds zwischen "Komponententests" und "Tests, die mit einem Komponententest-Framework automatisiert werden" bewusst sein.


Für mich ist eine 100% ige Testabdeckung ein hohes Ziel, aber selbst wenn wir nur 50% erreichen würden, würden wir wissen, dass 100% dieser 50% abgedeckt sind.

Unit Tests sind ein Sicherheitsnetz. Sie geben Ihnen das Vertrauen, Ihren Code zu überarbeiten , um technische Schulden zu reduzieren oder neues Verhalten hinzuzufügen, ohne befürchten zu müssen, bereits implementiertes Verhalten zu brechen.

Sie benötigen keine 100% ige Code-Abdeckung.

Sie benötigen jedoch eine 100% ige Verhaltensabdeckung. (Ja, Codeabdeckung und Verhaltensabdeckung korrelieren in gewisser Weise, sind jedoch nicht identisch.)

Wenn Sie weniger als 100% des Verhaltens abdecken, hat eine erfolgreiche Ausführung Ihrer Testsuite keine Auswirkung, da Sie möglicherweise einen Teil des nicht getesteten Verhaltens geändert haben. Und Sie werden am Tag nach der Online-Veröffentlichung von Ihrem Kunden bemerkt ...


Fazit

Wenige Tests sind besser als kein Test. Kein Zweifel!

Aber es gibt nicht so etwas wie zu viele Unit-Tests.

Dies liegt daran, dass jeder Komponententest eine einzelne Erwartung bezüglich des Verhaltens der Codes überprüft . Und Sie können nicht mehr Unit-Tests schreiben, als Sie von Ihrem Code erwartet haben. Und ein Loch in Ihrem Sicherheitsgurt kann zu einer ungewollten Veränderung des Produktionssystems führen.


2

Absolut ja. Ich war SDET für ein großes Softwareunternehmen. Unser kleines Team musste Testcode pflegen, der früher von einem viel größeren Team gehandhabt wurde. Darüber hinaus gab es bei unserem Produkt einige Abhängigkeiten, die ständig neue Entwicklungen mit sich brachten, was für uns eine ständige Wartung der Tests bedeutete. Wir hatten keine Möglichkeit, die Teamgröße zu erhöhen, und mussten deshalb Tausende der weniger wertvollen Tests wegwerfen, wenn sie fehlschlugen. Andernfalls könnten wir mit den Mängeln nie Schritt halten.

Bevor Sie dies als bloßes Managementproblem abtun, sollten Sie berücksichtigen, dass viele Projekte in der realen Welt unter einem Personalabbau leiden, wenn sie sich dem Legacy-Status nähern. Manchmal passiert es sogar gleich nach der ersten Veröffentlichung.


4
"Darüber hinaus gab es bei unserem Produkt einige Abhängigkeiten, die sich ständig ändern, was für uns eine ständige Wartung der Tests bedeutet." - Die Tests, die Sie für erforderlich halten, klingen wie die wertvollen, wenn Ihre Abhängigkeiten ständig unterbrochen werden.
CodeMonkey

2
Das ist kein Problem mit den Tests, sondern mit der Organisation.
4.

2
@CodeMonkey Die Abhängigkeiten wurden nicht getrennt. Sie wurden auf eine Weise aktualisiert, die Änderungen an unserem Produkt erforderte. Ja, die Tests waren wertvoll, aber bei weitem nicht so wertvoll wie andere. Automatisierte Tests sind am wertvollsten, wenn der entsprechende manuelle Test schwierig ist.
Mrog

2
@jwenting Ja, es ist ein organisatorisches Problem, kein Code-Problem. Das ändert aber nichts an der Tatsache, dass es zu viele Tests gab. Ein fehlerhafter Test, der nicht untersucht werden kann, ist unabhängig von der Ursache unbrauchbar.
Mrog

Was ist ein "SDET"?
Peter Mortensen

1

Es ist nicht unbedingt ein Problem, mehr Zeilen Testcode als Produktcode zu haben, vorausgesetzt, Sie überarbeiten Ihren Testcode, um das Kopieren und Einfügen zu vermeiden.

Was ein Problem ist, sind Tests, die Spiegel Ihrer Implementierung sind und keine geschäftliche Bedeutung haben - zum Beispiel Tests, die mit Mocks und Stubs geladen sind und nur behaupten, dass eine Methode eine andere Methode aufruft.

Ein gutes Zitat in dem Papier "Warum die meisten Unit-Tests Makulatur sind" ist, dass Unit-Tests ein "breites, formales, unabhängiges Orakel der Korrektheit und ... einen zuschreibbaren Geschäftswert" haben sollten.


0

Eine Sache, die ich nicht erwähnt habe, ist, dass Ihre Tests schnell und einfach sein müssen, damit Entwickler sie jederzeit ausführen können.

Sie müssen nicht in die Quellcodeverwaltung einchecken und mindestens eine Stunde warten (abhängig von der Größe Ihrer Codebasis), bevor die Tests abgeschlossen sind, um festzustellen, ob Ihre Änderung einen Fehler verursacht hat Ihren eigenen Computer, bevor Sie in die Quellcodeverwaltung einchecken (oder zumindest, bevor Sie Änderungen vornehmen). Idealerweise sollten Sie Ihre Tests mit einem einzigen Skript oder Tastendruck ausführen können.

Und wenn Sie diese Tests lokal ausführen, möchten Sie, dass sie schnell ausgeführt werden - in der Größenordnung von Sekunden. Wenn Sie langsamer arbeiten, werden Sie versucht sein, sie nicht oder nur unzureichend auszuführen.

Es könnte also ein Problem sein, so viele Tests durchzuführen, dass sie alle Minuten dauern, oder ein paar übermäßig komplexe Tests durchzuführen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.