Ich verstehe den Wert des automatisierten Testens und setze es überall dort ein, wo das Problem genau genug spezifiziert ist, um mir gute Testfälle einfallen zu lassen. Ich habe jedoch bemerkt, dass einige Leute hier und auf StackOverflow das Testen nur einer Einheit betonen , nicht ihrer Abhängigkeiten. Hier sehe ich den Nutzen nicht.
Das Verspotten / Stubben, um Testabhängigkeiten zu vermeiden, erhöht die Komplexität der Tests. Es fügt Ihrem Produktionscode künstliche Flexibilität / Entkopplungsanforderungen hinzu, um das Verspotten zu unterstützen. (Ich bin mit niemandem einverstanden, der meint, dies fördere gutes Design. Das Schreiben von zusätzlichem Code, das Einführen von Abhängigkeiten-Injection-Frameworks oder das Hinzufügen von Komplexität zu Ihrer Codebasis, um Dinge flexibler / steckbarer / erweiterbarer / entkoppelter zu machen, ohne einen echten Anwendungsfall, ist Überentwicklung, nicht gutes Design.)
Zweitens bedeutet das Testen von Abhängigkeiten, dass kritischer Low-Level-Code, der überall verwendet wird, mit anderen Eingaben getestet wird als denen, an die derjenige, der die Tests geschrieben hat, explizit gedacht hat. Ich habe viele Fehler in der Low-Level-Funktionalität gefunden, indem ich Unit-Tests mit High-Level-Funktionalität durchgeführt habe, ohne die Low-Level-Funktionalität, von der sie abhing, zu verfälschen. Im Idealfall wurden diese bei den Unit-Tests für die Low-Level-Funktionalität gefunden, es kommt jedoch immer zu Ausfällen.
Was ist die andere Seite davon? Ist es wirklich wichtig, dass ein Unit-Test nicht auch seine Abhängigkeiten testet? Wenn ja warum?
Bearbeiten: Ich kann den Wert des Verspottens von externen Abhängigkeiten wie Datenbanken, Netzwerken, Webdiensten usw. verstehen . (Dank an Anna Lear, die mich dazu motiviert hat, dies zu klären.) Ich bezog mich auf interne Abhängigkeiten , dh andere Klassen, statische Funktionen usw. das hat keine direkten externen Abhängigkeiten.
Antworten:
Es ist eine Frage der Definition. Ein Test mit Abhängigkeiten ist ein Integrationstest, kein Komponententest. Sie sollten auch eine Integrationstestsuite haben. Der Unterschied besteht darin, dass die Integrationstestsuite möglicherweise in einem anderen Testframework ausgeführt wird und möglicherweise nicht als Teil des Builds, da sie länger dauert.
Für unser Produkt: Unsere Komponententests werden mit jedem Build in Sekunden ausgeführt. Eine Teilmenge unserer Integrationstests wird mit jedem Check-in ausgeführt und dauert 10 Minuten. Unsere vollständige Integrationssuite wird jede Nacht in 4 Stunden ausgeführt.
quelle
Testen mit all den Abhängigkeiten ist immer noch wichtig, aber es geht mehr um Integrationstests, wie Jeffrey Faust sagte.
Einer der wichtigsten Aspekte von Unit-Tests ist es, Ihre Tests vertrauenswürdig zu machen. Wenn Sie nicht glauben, dass ein bestandener Test wirklich bedeutet, dass die Dinge gut sind und dass ein nicht bestandener Test ein Problem im Produktionscode darstellt, sind Ihre Tests bei weitem nicht so nützlich, wie sie sein können.
Um Ihre Tests vertrauenswürdig zu machen, müssen Sie einige Dinge tun, aber ich werde mich für diese Antwort auf eine konzentrieren. Sie müssen sicherstellen, dass sie einfach auszuführen sind, damit alle Entwickler sie problemlos ausführen können, bevor sie den Code einchecken. "Einfach auszuführen" bedeutet, dass Ihre Tests schnell ausgeführt werden und keine umfangreiche Konfiguration oder Einrichtung erforderlich ist, um sie zum Laufen zu bringen. Im Idealfall sollte jeder in der Lage sein, die neueste Version des Codes zu überprüfen, die Tests sofort auszuführen und zu sehen, ob sie bestanden wurden.
Durch das Abstrahieren von Abhängigkeiten von anderen Dingen (Dateisystem, Datenbank, Webservices usw.) können Sie die erforderliche Konfiguration vermeiden und sind weniger anfällig für Situationen, in denen Sie versucht sind, "Oh, Tests sind fehlgeschlagen, weil ich nicht" zu sagen. Ich habe die Netzwerkfreigabe nicht eingerichtet. Na ja. Ich werde sie später ausführen. "
Wenn Sie testen möchten, was Sie mit einigen Daten tun, sollten sich Ihre Unit-Tests für diesen Geschäftslogikcode nicht darum kümmern, wie Sie diese Daten erhalten. Es ist fantastisch, die Kernlogik Ihrer Anwendung testen zu können, ohne auf die Unterstützung von Dingen wie Datenbanken angewiesen zu sein. Wenn Sie es nicht tun, verpassen Sie es.
PS Ich sollte hinzufügen, dass es definitiv möglich ist, im Namen der Testbarkeit zu überdenken. Das Testen Ihrer Anwendung trägt dazu bei, dies zu vermeiden. In jedem Fall machen schlechte Implementierungen eines Ansatzes den Ansatz jedoch nicht weniger gültig. Alles kann missbraucht und übertrieben werden, wenn man nicht immer fragt: "Warum mache ich das?" während der Entwicklung.
In Bezug auf interne Abhängigkeiten wird es etwas matschig. Ich denke gerne darüber nach, dass ich meine Klasse so gut wie möglich davor schützen möchte, sich aus den falschen Gründen zu ändern. Wenn ich ein Setup wie dieses habe ...
Es ist mir im Allgemeinen egal, wie
SomeClass
erstellt wird. Ich möchte es nur benutzen. Wenn SomeClass sich ändert und nun Parameter für den Konstruktor benötigt, ist das nicht mein Problem. Ich sollte MyClass nicht ändern müssen, um das zu berücksichtigen.Nun, das berührt nur den Designteil. Bei Unit-Tests möchte ich mich auch vor anderen Klassen schützen. Wenn ich MyClass teste, mag ich es zu wissen, dass es keine externen Abhängigkeiten gibt, dass SomeClass zu irgendeinem Zeitpunkt keine Datenbankverbindung oder keinen anderen externen Link eingeführt hat.
Ein noch größeres Problem ist, dass ich auch weiß, dass einige Ergebnisse meiner Methoden von der Ausgabe einer Methode in SomeClass abhängen. Ohne SomeClass zu verspotten / auszublenden, habe ich möglicherweise keine Möglichkeit, diesen Input in der Nachfrage zu variieren. Wenn ich Glück habe, kann ich meine Umgebung im Test möglicherweise so zusammenstellen, dass die richtige Antwort von SomeClass ausgelöst wird. Auf diese Weise werden meine Tests jedoch komplexer und spröder.
Wenn ich MyClass so umschreibe, dass eine Instanz von SomeClass im Konstruktor akzeptiert wird, kann ich eine gefälschte Instanz von SomeClass erstellen, die den gewünschten Wert zurückgibt (entweder über ein spöttisches Framework oder mit einem manuellen Mock). In diesem Fall muss ich in der Regel keine Schnittstelle einführen. Ob Sie dies tun oder nicht, ist in vielerlei Hinsicht eine persönliche Entscheidung, die von der Sprache Ihrer Wahl bestimmt wird (z. B. sind Benutzeroberflächen in C # wahrscheinlicher, aber in Ruby benötigen Sie definitiv keine).
quelle
Berücksichtigen Sie neben dem Thema Unit vs. Integrationstest Folgendes.
Das Klassen-Widget ist abhängig von den Klassen Thingamajig und WhatsIt.
Ein Komponententest für Widget schlägt fehl.
In welcher Klasse liegt das Problem?
Wenn Sie mit "Starten Sie den Debugger" geantwortet haben oder "Lesen Sie den Code durch, bis ich ihn finde", verstehen Sie, wie wichtig es ist, nur die Einheit und nicht die Abhängigkeiten zu testen.
quelle
Stellen Sie sich vor, Programmieren ist wie Kochen. Dann ist Unit Testing dasselbe, als ob Sie sicherstellen, dass Ihre Zutaten frisch, lecker usw. sind. Beim Integrationstest wird sichergestellt, dass Ihre Mahlzeit köstlich ist.
Letztendlich ist es das Wichtigste, sicherzustellen, dass Ihre Mahlzeit köstlich ist (oder dass Ihr System funktioniert), das ultimative Ziel. Aber wenn Ihre Zutaten, ich meine, Einheiten, funktionieren, haben Sie eine billigere Möglichkeit, dies herauszufinden.
In der Tat ist es wahrscheinlicher, dass Sie ein funktionierendes System haben, wenn Sie sicherstellen können, dass Ihre Einheiten / Methoden funktionieren. Ich betone eher "wahrscheinlich" als "sicher". Sie brauchen immer noch Ihre Integrationstests, genauso wie Sie immer noch jemanden brauchen, der das gekochte Essen probiert und Ihnen mitteilt, dass das Endprodukt gut ist. Sie werden es leichter haben, mit frischen Zutaten dorthin zu gelangen, das ist alles.
quelle
Wenn Sie die Einheiten vollständig isolieren, können Sie ALLE möglichen Variationen von Daten und Situationen testen, die von dieser Einheit übermittelt werden können. Da es vom Rest isoliert ist, können Sie Abweichungen ignorieren, die sich nicht direkt auf das zu testende Gerät auswirken. Dies wiederum verringert die Komplexität der Tests erheblich.
Durch das Testen mit verschiedenen integrierten Abhängigkeitsebenen können Sie bestimmte Szenarien testen, die in den Komponententests möglicherweise nicht getestet wurden.
Beides ist wichtig. Wenn Sie nur einen Komponententest durchführen, treten bei der Integration von Komponenten immer komplexere, subtile Fehler auf. Wenn Sie nur Integrationstests durchführen, testen Sie ein System, ohne die Gewissheit zu haben, dass die einzelnen Maschinenteile nicht getestet wurden. Zu glauben, dass Sie einen besseren vollständigen Test nur durch Integrationstests erzielen können, ist nahezu unmöglich, da die Anzahl der Einträge mit der Anzahl der hinzugefügten Komponenten SEHR schnell zunimmt (faktoriell denken) und die Erstellung eines Tests mit ausreichender Abdeckung sehr schnell unmöglich wird.
Kurz gesagt, die drei Ebenen des "Komponententests", die ich normalerweise in fast allen meinen Projekten verwende:
Die Abhängigkeitsinjektion ist ein sehr effizientes Mittel, um dies zu erreichen, da es ermöglicht, Komponenten für Komponententests sehr einfach zu isolieren, ohne das System komplexer zu machen. Ihr Geschäftsszenario rechtfertigt möglicherweise nicht die Verwendung eines Injektionsmechanismus, Ihr Testszenario wird dies jedoch beinahe tun. Das reicht für mich aus, um dann unverzichtbar zu machen. Sie können sie auch verwenden, um die verschiedenen Abstraktionsebenen unabhängig voneinander durch Teilintegrationstests zu testen.
quelle
Einheit. Bedeutet Singular.
Das Testen von 2 Dingen bedeutet, dass Sie die beiden Dinge und alle funktionalen Abhängigkeiten haben.
Wenn Sie eine dritte Sache hinzufügen, erhöhen Sie die funktionalen Abhängigkeiten in den Tests darüber hinaus linear. Zusammenhänge zwischen Dingen wachsen schneller als die Anzahl der Dinge.
Es gibt n (n-1) / 2 mögliche Abhängigkeiten unter n getesteten Gegenständen.
Das ist der große Grund.
Einfachheit hat Wert.
quelle
Erinnerst du dich, wie du zuerst gelernt hast, Rekursion zu machen? Mein Professor sagte "Angenommen, Sie haben eine Methode, die x tut" (zB löst Fibbonacci für jedes x). Msgstr "Um nach x aufzulösen, müssen Sie diese Methode für x - 1 und x - 2 aufrufen". In gleicher Weise können Sie durch Ausblenden von Abhängigkeiten so tun, als ob sie existieren, und testen, ob die aktuelle Einheit das tut, was sie tun soll. Die Annahme ist natürlich, dass Sie die Abhängigkeiten so rigoros testen.
Dies ist im Wesentlichen die SRP bei der Arbeit. Die Konzentration auf eine einzige Verantwortung, auch für Ihre Tests, beseitigt die Menge an mentalem Jonglieren, die Sie tun müssen.
quelle
(Dies ist eine kleine Antwort. Danke @TimWilliscroft für den Hinweis.)
Fehler sind leichter zu lokalisieren, wenn:
Das funktioniert gut auf Papier. Wie in der Beschreibung des OP dargestellt (Abhängigkeiten sind fehlerhaft), ist es jedoch schwierig, den Ort des Fehlers zu bestimmen, wenn die Abhängigkeiten nicht getestet werden.
quelle
Viele gute Antworten. Ich möchte noch ein paar weitere Punkte hinzufügen:
Mit Unit-Tests können Sie Ihren Code auch testen, wenn keine Abhängigkeiten vorhanden sind . ZB Sie oder Ihr Team haben die anderen Ebenen noch nicht geschrieben, oder Sie warten auf eine Schnittstelle, die von einem anderen Unternehmen bereitgestellt wurde.
Unit-Tests bedeuten auch, dass Sie keine vollständige Umgebung auf Ihrem Entwicklungscomputer haben müssen (z. B. eine Datenbank, einen Webserver usw.). Ich würde allen Entwicklern dringend empfehlen, eine solche Umgebung zu haben, wie klein sie auch sein mag , um Bugs usw. zu reproduzieren. Wenn es jedoch aus irgendeinem Grund nicht möglich ist, die Produktionsumgebung nachzuahmen, erhalten Sie durch Unit-Tests zumindest ein gewisses Niveau Vertrauen in Ihren Code, bevor er in ein größeres Testsystem eingeht.
quelle
Zu den Designaspekten: Ich glaube, dass auch kleine Projekte davon profitieren, den Code testbar zu machen. Sie müssen nicht unbedingt so etwas wie Guice einführen (eine einfache Factory-Klasse wird es oft tun), aber die Trennung des Konstruktionsprozesses von der Programmierlogik führt zu
quelle
Äh ... es gibt gute Punkte zum Unit- und Integrationstest, die in diesen Antworten hier geschrieben sind!
Ich vermisse die kostenbezogenen und praktischen Ansichten hier. Das heißt, ich sehe klar den Vorteil von sehr isolierten / atomaren Einheitentests (möglicherweise sehr unabhängig voneinander und mit der Option, sie parallel und ohne Abhängigkeiten wie Datenbanken, Dateisystem usw. auszuführen) und Integrationstests (höherer Ebene). es geht aber auch um kosten (zeit, geld, ...) und risiken .
Es gibt also andere Faktoren, die viel wichtiger sind (wie "Was soll getestet werden?" ), Bevor Sie aus meiner Erfahrung heraus über "Wie soll getestet werden ?" Nachdenken ...
Zahlt mein Kunde (implizit) für den zusätzlichen Aufwand für das Schreiben und die Wartung von Tests? Ist ein eher testbasierter Ansatz (schreiben Sie zuerst die Tests, bevor Sie den Code schreiben) in meiner Umgebung wirklich kosteneffizient (Analyse von Codefehlerrisiken / -kosten, Personen, Designspezifikationen, Einrichten der Testumgebung)? Code ist immer fehlerhaft, aber könnte es kostengünstiger sein, das Testen auf die Produktionsnutzung zu verlagern (im schlimmsten Fall!)?
Es hängt auch stark davon ab, wie Ihre Codequalität (Standards) ist oder welche Frameworks, IDEs, Designprinzipien usw. Sie und Ihr Team befolgen und wie erfahren sie sind. Ein gut geschriebener, leicht verständlicher, gut genug dokumentierter (idealerweise selbstdokumentierender), modularer, ... Code führt wahrscheinlich weniger Fehler ein als das Gegenteil. Der tatsächliche "Bedarf", Druck oder die Gesamtkosten für Wartung / Bugfixes / Risiken für umfangreiche Tests sind daher möglicherweise nicht hoch.
Lassen Sie es uns auf die Spitze treiben, als ein Mitarbeiter in meinem Team vorschlug, dass wir versuchen müssen, unseren reinen Java EE-Modellschichtcode mit einer gewünschten 100% -igen Abdeckung für alle Klassen in der Datenbank zu testen und die Datenbank zu verspotten. Oder der Manager, der möchte, dass die Integrationstests mit 100% aller möglichen realen Anwendungsfälle und Web-UI-Workflows abgedeckt werden, weil wir nicht riskieren möchten, dass ein Anwendungsfall fehlschlägt. Aber wir haben ein knappes Budget von ungefähr 1 Million Euro, einen ziemlich engen Plan, um alles zu programmieren. Eine Kundenumgebung, in der potenzielle Anwendungsfehler keine große Gefahr für Menschen oder Unternehmen darstellen. Unsere App wird intern mit (einigen) wichtigen Komponententests, Integrationstests, wichtigen Kundentests mit festgelegten Testplänen, einer Testphase usw. getestet. Wir entwickeln keine App für eine Kernfabrik oder die pharmazeutische Produktion!
Ich selbst versuche, wenn möglich einen Test zu schreiben und entwickle ihn, während ich meinen Code teste. Aber ich mache es oft von oben nach unten (Integrationstests) und versuche, den richtigen Punkt zu finden, an dem die "App-Ebene" für die wichtigen Tests zugeschnitten wird (oft in der Modellebene). (weil es oft um die "schichten" geht)
Darüber hinaus bleibt der Code für den Unit- und Integrationstest nicht ohne negative Auswirkungen auf Zeit, Geld, Wartung, Codierung usw.. Er ist cool und sollte angewendet werden, jedoch mit Vorsicht und unter Berücksichtigung der Auswirkungen beim Start oder nach 5 Jahren vieler entwickelter Tests Code.
Ich würde also sagen, es kommt sehr auf Vertrauen und Kosten / Risiko / Nutzen-Bewertung an ... wie im echten Leben, wo man nicht mit viel oder 100% Sicherheitsmechanismen herumlaufen kann und will.
Das Kind kann und sollte irgendwo hochklettern und kann hinfallen und sich verletzen. Das Auto funktioniert möglicherweise nicht mehr, weil ich den falschen Kraftstoff eingefüllt habe (ungültige Eingabe :)). Der Toast kann verbrannt werden, wenn der Zeitknopf nach 3 Jahren nicht mehr funktioniert. Aber ich möchte niemals auf der Autobahn fahren und mein abgenommenes Lenkrad in meinen Händen halten :)
quelle