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.
quelle
Antworten:
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.
quelle
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.
quelle
Antworten auf Ihre Fragen
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.
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.
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.
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).
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.
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.
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.
quelle
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:
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.
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.
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.
quelle
Denken Sie daran, dass jeder Test sowohl Kosten als auch Nutzen hat. Nachteile 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.
quelle
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
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:
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.
Testen Sie alle relevanten Min / Max-Werte oder Größen
Testen Sie alles, was Sie für einen witzigen Sonderfall halten, besonders 'ungerade' Werte.
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:
Ü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.
quelle
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).
quelle
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 :
Auf dieser Grundlage sollte ein Komponententest jedes gewünschte Verhalten Ihres Codes verifizieren . Wo der "Wunsch" mehr oder weniger von den Anforderungen genommen wird.
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.
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.
quelle
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.
quelle
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.
quelle
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.
quelle