it-swarm-eu.dev

(Warum) ist es wichtig, dass ein Unit-Test keine Abhängigkeiten testet?

Ich verstehe den Wert automatisierter Tests und verwende sie überall dort, wo das Problem so genau spezifiziert ist, dass ich gute Testfälle entwickeln kann. Ich habe jedoch festgestellt, dass einige Leute hier und auf StackOverflow das Testen von nur einer Einheit betonen, nicht deren 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 sagt, dass dies gutes Design fördert. Das Schreiben von zusätzlichem Code, das Einführen von Abhängigkeitsinjektions-Frameworks oder das Hinzufügen von Komplexität zu Ihrer Codebasis, um die Dinge flexibler/steckbarer/erweiterbarer/entkoppelter zu machen, ohne dass ein wirklicher Anwendungsfall vorliegt, ist Überentwicklung, nicht gutes Design.)

Zweitens bedeutet das Testen von Abhängigkeiten, dass kritischer Code auf niedriger Ebene, der überall verwendet wird, mit anderen Eingaben als denen getestet wird, an die die Tests explizit gedacht haben. Ich habe viele Fehler in der Low-Level-Funktionalität gefunden, indem ich Unit-Tests für die High-Level-Funktionalität ausgeführt habe, ohne die Low-Level-Funktionalität zu verspotten, von der sie abhing. Idealerweise wurden diese durch die Komponententests für die Low-Level-Funktionalität gefunden, aber es treten immer Fehlfälle auf.

Was ist die andere Seite davon? Ist es wirklich wichtig, dass ein Komponententest nicht auch seine Abhängigkeiten testet? Wenn ja warum?

Bearbeiten: Ich kann den Wert des Verspottens externer Abhängigkeiten wie Datenbanken, Netzwerke, Webdienste usw. verstehen. (Vielen Dank an Anna Lear, die mich motiviert hat, dies zu klären .) Ich bezog mich auf interne Abhängigkeiten , dh andere Klassen, statische Funktionen usw., die keine direkten externen Abhängigkeiten haben.

107
dsimcha

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 wahrscheinlich nicht Teil des Builds ist, da sie länger dauert.

Für unser Produkt: Unsere Komponententests werden mit jedem Build in Sekunden ausgeführt. Eine Teilmenge unserer Integrationstests wird bei jedem Check-in 10 Minuten lang ausgeführt. Unsere vollständige Integrationssuite wird jede Nacht ausgeführt und dauert 4 Stunden.

118
Jeffrey Faust

Das Testen mit allen vorhandenen Abhängigkeiten ist immer noch wichtig, aber es liegt eher im Bereich des Integrationstests, wie Jeffrey Faust sagte.

Einer der wichtigsten Aspekte beim Testen von Einheiten ist es, Ihre Tests vertrauenswürdig zu machen. Wenn Sie nicht darauf vertrauen, dass ein bestehender Test wirklich bedeutet, dass die Dinge gut sind, und dass ein nicht bestandener Test wirklich ein Problem im Produktionscode bedeutet, 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 nur eine konzentrieren. Sie müssen sicherstellen, dass sie einfach auszuführen sind, damit alle Entwickler sie problemlos ausführen können, bevor Sie Code einchecken. "Einfach auszuführen" bedeutet, dass Ihre Tests schnell ausgeführt werden nd es gibt keine umfangreiche Konfiguration oder Setup benötigt, um sie gehen zu lassen. Im Idealfall sollte jeder in der Lage sein, die neueste Version des Codes zu überprüfen, die Tests sofort auszuführen und zu sehen, wie sie bestanden werden.

Wenn Sie Abhängigkeiten von anderen Dingen (Dateisystem, Datenbank, Webdienste usw.) entfernen, können Sie Konfigurationsanforderungen vermeiden und sind weniger anfällig für Situationen, in denen Sie versucht sind zu sagen: "Oh, Tests sind fehlgeschlagen, weil ich es nicht tue." 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 Komponententests für diesen Geschäftslogikcode nicht darum kümmern wie Sie erhalten diese Daten. Es ist fantastisch, die Kernlogik Ihrer Anwendung testen zu können, ohne von unterstützenden Dingen wie Datenbanken abhängig zu sein. Wenn Sie es nicht tun, verpassen Sie es.

P.S. Ich sollte hinzufügen, dass es definitiv möglich ist, im Namen der Testbarkeit zu überarbeiten. Durch Testen Ihrer Anwendung können Sie dies abmildern. In jedem Fall machen schlechte Implementierungen eines Ansatzes den Ansatz nicht weniger gültig. Alles kann missbraucht und übertrieben werden, wenn man nicht immer wieder fragt: "Warum mache ich das?" während der Entwicklung.


public class MyClass 
{
    private SomeClass someClass;
    public MyClass()
    {
        someClass = new SomeClass();
    }

    // use someClass in some way
}

Es ist mir im Allgemeinen egal, wie SomeClass erstellt wird. Ich möchte es nur benutzen. Wenn SomeClass sich ändert und jetzt Parameter für den Konstruktor benötigt ... ist das nicht mein Problem. Ich sollte MyClass nicht ändern müssen, um dies zu berücksichtigen.

Das berührt nur den Designteil. Bei Unit-Tests möchte ich mich auch vor anderen Klassen schützen. Wenn ich MyClass teste, möchte ich wissen, dass es keine externen Abhängigkeiten gibt, dass SomeClass irgendwann keine Datenbankverbindung oder einen anderen externen Link eingeführt hat.

Ein noch größeres Problem ist jedoch, dass ich auch weiß, dass einige der Ergebnisse meiner Methoden von der Ausgabe einer Methode auf SomeClass abhängen. Ohne SomeClass zu verspotten/auszublenden, habe ich möglicherweise keine Möglichkeit, diese Eingabe in der Nachfrage zu variieren. Wenn ich Glück habe, kann ich meine Umgebung innerhalb des Tests möglicherweise so zusammenstellen, dass die richtige Reaktion von SomeClass ausgelöst wird. Auf diese Weise werden meine Tests jedoch komplexer und spröder.

Durch Umschreiben von MyClass, um eine Instanz von SomeClass im Konstruktor zu akzeptieren, kann ich eine gefälschte Instanz von SomeClass erstellen, die den gewünschten Wert zurückgibt (entweder über ein Mocking-Framework oder mit einem manuellen Mock). Ich habe im Allgemeinen nicht habe, um in diesem Fall eine Schnittstelle einzuführen. Ob Sie dies tun oder nicht, ist in vielerlei Hinsicht eine persönliche Entscheidung, die möglicherweise von der Sprache Ihrer Wahl bestimmt wird (z. B. sind Schnittstellen in C # wahrscheinlicher, aber in Ruby benötigen Sie definitiv keine).

39
Adam Lear

Beachten Sie neben dem Problem zwischen Einheit und Integrationstest Folgendes.

Das Klassen-Widget hat Abhängigkeiten von den Klassen Thingamajig und WhatsIt.

Ein Komponententest für Widget schlägt fehl.

In welcher Klasse liegt das Problem?

Wenn Sie mit "Debugger starten" oder "Code lesen, bis ich ihn finde" geantwortet haben, wissen Sie, wie wichtig es ist, nur die Einheit zu testen, nicht die Abhängigkeiten.

22
Brook

Stellen Sie sich vor, Programmieren ist wie Kochen. Dann ist Unit-Test dasselbe wie sicherzustellen, dass Ihre Zutaten frisch, lecker usw. sind. Während Integrationstests bedeuten, dass Ihre Mahlzeit köstlich ist.

Letztendlich ist es das Wichtigste, sicherzustellen, dass Ihre Mahlzeit köstlich ist (oder dass Ihr System funktioniert). Aber wenn Ihre Zutaten, ich meine Einheiten, funktionieren, haben Sie eine billigere Möglichkeit, dies herauszufinden.

Wenn Sie sicherstellen können, dass Ihre Einheiten/Methoden funktionieren, ist es wahrscheinlicher, dass Sie über ein funktionierendes System verfügen. Ich betone "wahrscheinlicher" im Gegensatz zu "sicher". Sie benötigen immer noch Ihre Integrationstests, genauso wie Sie immer noch jemanden brauchen, der die von Ihnen gekochte Mahlzeit probiert und Ihnen sagt, dass das Endprodukt gut ist. Mit frischen Zutaten fällt es Ihnen leichter, dorthin zu gelangen, das ist alles.

15
Julio

Durch Testen der Einheiten durch vollständige Isolierung können ALLE möglichen Variationen von Daten und Situationen getestet werden, die diese Einheit einreichen kann. Da es vom Rest isoliert ist, können Sie Variationen ignorieren, die keine direkten Auswirkungen auf das zu testende Gerät haben. Dies wiederum verringert die Komplexität der Tests erheblich.

Durch Testen mit verschiedenen integrierten Abhängigkeitsstufen 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 das Vertrauen, dass die einzelnen Teile der Maschine nicht getestet wurden. Es ist fast unmöglich zu glauben, dass Sie einen besseren vollständigen Test erzielen können, wenn Sie nur Integrationstests durchführen. Je mehr Komponenten Sie hinzufügen, desto schneller wächst die Anzahl der Eintragskombinationen (denken Sie faktoriell), und es wird sehr schnell unmöglich, einen Test mit ausreichender Abdeckung zu erstellen.

Kurz gesagt, die drei Ebenen des "Unit-Tests", die ich normalerweise in fast allen meinen Projekten verwende:

  • Unit-Tests, isoliert mit verspotteten oder gestoppelten Abhängigkeiten, um die Hölle einer einzelnen Komponente zu testen. Im Idealfall würde man eine vollständige Abdeckung versuchen.
  • Integrationstests zum Testen der subtileren Fehler. Sorgfältig gestaltete Stichproben, die Grenzfälle und typische Bedingungen aufzeigen. Eine vollständige Abdeckung ist häufig nicht möglich. Der Aufwand konzentriert sich darauf, was zum Ausfall des Systems führen würde.
  • Spezifikationstests, um verschiedene reale Daten in das System einzufügen, die Daten zu instrumentieren (falls möglich) und die Ausgabe zu beobachten, um sicherzustellen, dass sie den Spezifikationen und Geschäftsregeln entspricht.

Die Abhängigkeitsinjektion ist ein sehr effizientes Mittel, um dies zu erreichen, da Komponenten für Komponententests sehr einfach isoliert werden können, ohne das System komplexer zu gestalten. Ihr Geschäftsszenario rechtfertigt möglicherweise nicht die Verwendung eines Injektionsmechanismus, Ihr Testszenario jedoch fast. Dies reicht für mich aus, um dann unverzichtbar zu machen. Sie können sie auch verwenden, um die verschiedenen Abstraktionsebenen durch teilweise Integrationstests unabhängig voneinander zu testen.

6
Newtopian

Was ist die andere Seite davon? Ist es wirklich wichtig, dass ein Komponententest nicht auch seine Abhängigkeiten testet? Wenn ja warum?

Einheit. Bedeutet Singular.

Das Testen von 2 Dingen bedeutet, dass Sie die beiden Dinge nd alle funktionalen Abhängigkeiten haben.

Wenn Sie eine dritte Sache hinzufügen, erhöhen Sie die funktionalen Abhängigkeiten in den Tests über die lineare hinaus. Die Verbindungen zwischen Dingen wachsen schneller als die Anzahl der Dinge.

Es gibt n (n-1)/2 mögliche Abhängigkeiten zwischen n getesteten Elementen.

Das ist der große Grund.

Einfachheit hat Wert.

4
S.Lott

Erinnerst du dich, wie du zuerst gelernt hast, Rekursion zu machen? Mein Professor sagte: "Angenommen, Sie haben eine Methode, die x ausführt" (z. B. löst Fibbonacci für jedes x). "Um nach x zu lösen, müssen Sie diese Methode für x-1 und x-2 aufrufen". Aus dem gleichen Grund können Sie durch Auslöschen von Abhängigkeiten so tun, als ob sie existieren, und testen, ob die aktuelle Einheit das tut, was sie tun sollte. Die Annahme ist natürlich, dass Sie die Abhängigkeiten genauso streng testen.

Dies ist im Wesentlichen die SRP bei der Arbeit. Wenn Sie sich auch für Ihre Tests auf eine einzige Verantwortung konzentrieren, entfällt die Menge an mentalem Jonglieren, die Sie ausführen müssen.

2
Michael Brown

(Dies ist eine kleine Antwort. Danke @TimWilliscroft für den Hinweis.)

Fehler sind einfacher zu lokalisieren wenn:

  • Sowohl die zu testende Einheit als auch jede ihrer Abhängigkeiten werden unabhängig voneinander getestet.
  • Jeder Test schlägt nur dann fehl, wenn der vom Test abgedeckte Code fehlerhaft ist.

Das funktioniert gut auf Papier. Wie in der Beschreibung von OP dargestellt (Abhängigkeiten sind fehlerhaft), ist es jedoch schwierig, den Ort des Fehlers zu bestimmen, wenn die Abhängigkeiten nicht getestet werden.

2
rwong

Viele gute Antworten. Ich möchte noch ein paar andere Punkte hinzufügen:

Mit Unit-Tests können Sie Ihren Code auch testen, wenn Abhängigkeiten nicht vorhanden sind . Z.B. Sie oder Ihr Team haben die anderen Ebenen noch nicht geschrieben, oder Sie warten auf eine Schnittstelle, die von einem anderen Unternehmen bereitgestellt wird.

Unit-Tests bedeuten auch, dass auf Ihrem Entwicklungscomputer keine vollständige Umgebung vorhanden sein muss (z. B. eine Datenbank, ein Webserver usw.). Ich würde dringend empfehlen, dass alle Entwickler do eine solche Umgebung haben, wie reduziert sie auch sein mag, um Fehler usw. zu reproduzieren. Wenn es jedoch aus irgendeinem Grund nicht möglich ist, die Produktionsumgebung nachzuahmen, dann Einheit Das Testen gibt Ihnen zumindest ein gewisses Maß an Vertrauen in Ihren Code, bevor er in ein größeres Testsystem eingeht.

2
Steve

Äh ... in diesen Antworten finden Sie gute Punkte zu Unit- und Integrationstests!

Ich vermisse die kostenbezogene und praktische Ansichten hier. Das heißt, ich sehe deutlich den Vorteil von sehr isolierten/atomaren Komponententests (möglicherweise sehr unabhängig voneinander und mit der Option, sie parallel und ohne Abhängigkeiten wie Datenbanken, Dateisystem usw. auszuführen) und (übergeordneten) Integrationstests. aber ... es ist auch eine Frage der Kosten (Zeit, Geld, ...) und Risiken.

Es gibt also andere Faktoren, die viel wichtiger sind (wie "was zu testen ist" ), bevor Sie an denken. "wie man testet" aus meiner Erfahrung ...

Zahlt mein Kunde (implizit) für die zusätzliche Menge an Schreib- und Wartungstests? Ist ein stärker testgetriebener Ansatz (zuerst Tests schreiben, bevor Sie den Code schreiben) in meiner Umgebung wirklich kosteneffizient (Codefehlerrisiko-/Kostenanalyse, Personen, Entwurfsspezifikationen, Testumgebung einrichten)? Code ist immer fehlerhaft, aber könnte es kostengünstiger sein, die Tests auf die Produktionsnutzung umzustellen (im schlimmsten Fall!)?

Es hängt auch stark davon ab, wie Ihre Codequalität (Standards) ist oder welche Frameworks, IDE, Designprinzipien usw. Sie und Ihr Team befolgen und wie erfahren sie sind. Gut geschrieben, leicht verständlich, gut genug dokumentiert ( idealerweise selbstdokumentierend), modular, ... Code führt wahrscheinlich weniger Fehler als das Gegenteil ein. Daher ist der tatsächliche "Bedarf", der Druck oder die Gesamtkosten für Wartung/Fehlerbehebung/Risiken für umfangreiche Tests möglicherweise nicht hoch.

Nehmen wir es auf das Äußerste, wo ein Mitarbeiter in meinem Team vorgeschlagen hat, dass wir versuchen müssen, unseren reinen Java EE-Modellschichtcode mit einer gewünschten 100% igen Abdeckung für alle Klassen innerhalb und zu testen Verspotten der Datenbank. 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. Wir haben jedoch ein knappes Budget von ungefähr 1 Million Euro, ein ziemlich enger Plan, um alles zu codieren. Eine Kundenumgebung, in der potenzielle App-Fehler keine große Gefahr für Menschen oder Unternehmen darstellen. Unsere App wird intern mit (einigen) wichtigen Komponententests und Integrationstests getestet , wichtige Kundentests mit entworfenen Testplänen, einer Testphase usw. Wir entwickeln keine App für eine Kernfabrik oder die pharmazeutische Produktion! (Wir haben nur eine bestimmte Datenbank, die für jeden Entwickler einfach zu testen ist und eng mit der Webanwendung verbunden ist oder Modellebene)

Ich selbst versuche, wenn möglich, einen Test zu schreiben und während des Unit-Tests meinen Code zu entwickeln. Aber ich mache es oft von oben nach unten (Integrationstests) und versuche, den guten Punkt zu finden, an dem der "App-Layer-Schnitt" für die wichtigen Tests (oft in der Model-Layer) vorgenommen werden kann. (weil es oft viel um die "Schichten" geht)

Darüber hinaus kommt der Code für Einheiten- und Integrationstests nicht ohne negative Auswirkungen auf Zeit, Geld, Wartung, Codierung usw. aus. Er ist cool und sollte angewendet werden, aber mit Sorgfalt und unter Berücksichtigung der Auswirkungen beim Starten oder nach 5 Jahren vieler entwickelter Tests Code.

Also würde ich es wirklich sagen hängt sehr von Vertrauen und Kosten/Risiko/Nutzen-Bewertung ab ... wie im wirklichen Leben, wo man nicht mit viel oder 100 herumlaufen kann und will % Sicherheitsmechanismen.

Das Kind kann und sollte irgendwo hochklettern und kann herunterfallen und sich selbst 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 die Zeitschaltfläche nach 3 Jahren nicht mehr funktioniert. Aber ich möchte niemals auf der Autobahn fahren und mein abgenommenes Lenkrad in meinen Händen halten :)

0

Zu den Designaspekten: Ich glaube, dass auch kleine Projekte davon profitieren, den Code testbar zu machen. Sie müssen nicht unbedingt etwas wie Guice einführen (eine einfache Factory-Klasse reicht oft aus), aber eine Trennung des Konstruktionsprozesses von der Programmierlogik führt dazu

  • klare Dokumentation der Abhängigkeiten jeder Klasse über ihre Schnittstelle (sehr hilfreich für Leute, die neu im Team sind)
  • die Klassen werden viel klarer und einfacher zu verwalten (sobald Sie die Erstellung des hässlichen Objektdiagramms in eine separate Klasse eingefügt haben).
  • lose Kupplung (erleichtert das Wechseln erheblich)
0
Oliver Weiler