Soweit ich weiß, besteht der Sinn von Unit-Tests darin, Code-Units isoliert zu testen . Das bedeutet, dass:
- Sie sollten nicht durch eine unabhängige Codeänderung an einer anderen Stelle in der Codebasis unterbrochen werden.
- Im Gegensatz zu Integrationstests (die in Haufen brechen können) sollte nur ein Unit-Test durch einen Fehler in der getesteten Unit unterbrochen werden.
All dies impliziert, dass jede äußere Abhängigkeit einer getesteten Einheit verspottet werden sollte. Und ich meine alle äußeren Abhängigkeiten , nicht nur die "äußeren Schichten" wie Netzwerk, Dateisystem, Datenbank usw.
Dies führt zu der logischen Schlussfolgerung, dass praktisch jeder Komponententest verspotten muss . Auf der anderen Seite zeigt eine schnelle Google-Suche über das Verspotten Unmengen von Artikeln, die behaupten, dass "Verspotten ein Codegeruch ist" und größtenteils (wenn auch nicht vollständig) vermieden werden sollten.
Nun zu den Fragen.
- Wie sollten Komponententests richtig geschrieben werden?
- Wo genau liegt die Grenze zwischen ihnen und den Integrationstests?
Update 1
Bitte beachten Sie folgenden Pseudocode:
class Person {
constructor(calculator) {}
calculate(a, b) {
const sum = this.calculator.add(a, b);
// do some other stuff with the `sum`
}
}
Kann ein Test, der die Person.calculate
Methode testet , ohne die Calculator
Abhängigkeit zu verspotten (vorausgesetzt, es Calculator
handelt sich um eine Lightweight-Klasse, die nicht auf "die Außenwelt" zugreift), als Komponententest betrachtet werden?
quelle
Antworten:
Martin Fowler über Unit Test
Was Kent Beck in Test Driven Development mit gutem Beispiel schrieb
Jegliche Behauptung, dass "der Punkt von Einheitentests ist", hängt stark davon ab, welche Definition von "Einheitentest" in Betracht gezogen wird.
Wenn Sie der Ansicht sind, dass Ihr Programm aus vielen kleinen Einheiten besteht, die voneinander abhängen, und wenn Sie sich auf einen Stil beschränken, der jede Einheit einzeln testet, sind viele Test-Doubles eine unvermeidliche Schlussfolgerung.
Die widersprüchlichen Ratschläge, die Sie sehen, stammen von Personen, die unter anderen Annahmen arbeiten.
Wenn Sie beispielsweise Tests schreiben, um Entwickler während des Umgestaltungsprozesses zu unterstützen, und eine Einheit in zwei Einheiten aufteilen, ist dies eine Umgestaltung, die unterstützt werden sollte. Vielleicht braucht diese Art von Test einen anderen Namen? Oder vielleicht brauchen wir ein anderes Verständnis von "Einheit".
Vielleicht möchten Sie vergleichen:
Ich denke, das ist die falsche Frage. Es ist wieder ein Streit um Etiketten , wenn ich glaube, dass es uns eigentlich um Eigenschaften geht .
Wenn ich Änderungen am Code vornehme, kümmert es mich nicht, Tests zu isolieren. Ich weiß bereits, dass "der Fehler" in meinem aktuellen Stapel nicht überprüfter Änderungen liegt. Wenn ich die Tests häufig durchführe, beschränke ich die Tiefe dieses Stapels und finde den Fehler trivial (im Extremfall werden die Tests nach jeder Bearbeitung ausgeführt - die maximale Tiefe des Stapels beträgt eins). Das Durchführen der Tests ist jedoch nicht das Ziel, sondern eine Unterbrechung. Daher ist es sinnvoll, die Auswirkungen der Unterbrechung zu verringern. Eine Möglichkeit, die Unterbrechung zu reduzieren, besteht darin, sicherzustellen, dass die Tests schnell sind ( Gary Bernhardt schlägt 300 ms vor , aber ich habe unter meinen Umständen nicht herausgefunden, wie das geht).
Wenn das Aufrufen
Calculator::add
die zum Ausführen des Tests erforderliche Zeit (oder eine der anderen wichtigen Eigenschaften für diesen Anwendungsfall) nicht wesentlich verlängert, würde ich kein Test-Double verwenden - es bietet keine Vorteile, die die Kosten überwiegen .Beachten Sie hier die beiden Annahmen: einen Menschen als Teil der Kostenbewertung und den kurzen Stapel nicht überprüfter Änderungen in der Nutzenbewertung. Unter Umständen, unter denen diese Bedingungen nicht zutreffen, ändert sich der Wert von "Isolation" erheblich.
Siehe auch Hot Lava von Harry Percival.
quelle
Indem Sie die Nebenwirkungen in Ihrem Code minimieren.
Nehmen Sie Ihren Beispielcode, wenn Sie
calculator
zum Beispiel mit einer Web-API sprechen, dann erstellen Sie entweder fragile Tests, die darauf beruhen, mit dieser Web-API interagieren zu können, oder Sie erstellen eine Kopie davon. Wenn es sich jedoch um einen deterministischen, zustandsfreien Satz von Berechnungsfunktionen handelt, dann verspotten Sie ihn nicht (und sollten es auch nicht tun). Wenn Sie dies tun, riskieren Sie, dass sich Ihr Mock anders als der echte Code verhält, was zu Fehlern in Ihren Tests führt.Mocks sollten nur für Code benötigt werden, der das Dateisystem, Datenbanken, URL-Endpunkte usw. liest / schreibt. Das hängt von der Umgebung ab, in der Sie ausgeführt werden. oder die in hohem Maße staatlich und nicht deterministisch sind. Wenn Sie also diese Teile des Codes auf ein Minimum beschränken und sie hinter Abstraktionen verbergen, sind sie leicht zu verspotten und der Rest Ihres Codes vermeidet die Notwendigkeit von Verspottungen.
Für die Codepunkte, die Nebenwirkungen haben, lohnt es sich, Tests zu schreiben, die verspotten, und Tests, die dies nicht tun. Letztere benötigen jedoch Vorsicht, da sie von Natur aus zerbrechlich und möglicherweise langsam sind. Vielleicht möchten Sie sie also nur über Nacht auf einem CI-Server ausführen, anstatt jedes Mal, wenn Sie Ihren Code speichern und erstellen. Die vorherigen Tests sollten jedoch so oft wie möglich durchgeführt werden. Ob jeder Test dann ein Unit- oder ein Integrationstest ist, wird akademisch und vermeidet "Flammenkriege" über das, was ein Unit-Test ist und was nicht.
quelle
R.equals
. B. ). Da es sich zum größten Teil um reine Funktionen handelt, werden sie in den Tests in der Regel nicht verfälscht.Diese Fragen sind in ihrer Schwierigkeit sehr unterschiedlich. Nehmen wir zuerst Frage 2.
Unit-Tests und Integrationstests sind klar voneinander getrennt. Ein Unit-Test testet eine Unit (Methode oder Klasse) und verwendet andere Units nur so oft wie nötig, um dieses Ziel zu erreichen. Es kann notwendig sein, sich zu verspotten, aber es ist nicht der Sinn des Tests. Ein Integrationstest testet die Interaktion zwischen verschiedenen tatsächlichen Einheiten. Dieser Unterschied ist der ganze Grund, warum wir sowohl Unit- als auch Integrationstests benötigen. Wenn einer die Arbeit des anderen gut genug erledigt hätte, hätten wir das nicht getan, aber es hat sich herausgestellt, dass es in der Regel effizienter ist, zwei spezialisierte Tools anstelle eines allgemeinen Tools zu verwenden .
Nun zu der wichtigen Frage: Wie solltest du einen Unit-Test durchführen? Wie oben erwähnt, sollten Unit-Tests Hilfskonstruktionen nur so weit wie nötig aufbauen . Oft ist es einfacher, eine Scheindatenbank zu verwenden als Ihre echte Datenbank oder sogar eine echte Datenbank. Das Verspotten an sich hat jedoch keinen Wert. Wenn es häufig vorkommt, ist es in der Tat einfacher, tatsächliche Komponenten einer anderen Ebene als Eingabe für einen Unit-Test auf mittlerer Ebene zu verwenden. Wenn ja, zögern Sie nicht, sie zu verwenden.
Viele Praktiker befürchten, dass ein Defekt in Einheit A an mehreren Stellen zu Testfehlern führt, wenn Einheitstest B Klassen wiederverwendet, die bereits mit Einheitstest A getestet wurden. Ich halte dies kein Problem: eine Testsuite hat erfolgreich 100% , um Ihnen die Sicherheit Sie geben müssen, so ist es kein großes Problem , zu viele Ausfälle zu haben - immerhin Sie tun einen Defekt haben. Das einzige kritische Problem wäre, wenn ein Defekt zu wenige Ausfälle auslöste .
Machen Sie sich deshalb nicht zum Spott. Es ist ein Mittel, kein Ziel. Wenn Sie also den zusätzlichen Aufwand vermeiden können, sollten Sie dies tun.
quelle
The only critical problem would be if a defect triggered too few failures.
Dies ist einer der Schwachpunkte des Spottens. Wir müssen das erwartete Verhalten "programmieren", damit dies fehlschlägt und unsere Tests als "falsch positiv" enden. Das Verspotten ist jedoch eine sehr nützliche Technik, um Determinismus zu erreichen (die wichtigste Testbedingung). Ich benutze sie wenn möglich in allen meinen Projekten. Sie zeigen mir auch, wenn die Integration zu komplex oder die Abhängigkeit zu eng ist.OK, um Ihre Fragen direkt zu beantworten:
Wie Sie sagen, sollten Sie Abhängigkeiten verspotten und nur die betreffende Einheit testen.
Ein Integrationstest ist ein Komponententest, bei dem Ihre Abhängigkeiten nicht verspottet werden.
Nein. Sie müssen die Taschenrechnerabhängigkeit in diesen Code einfügen und haben die Wahl zwischen einer verspotteten oder einer echten Version. Wenn Sie einen verspotteten Test verwenden, handelt es sich um einen Unit-Test, wenn Sie einen echten Test verwenden, handelt es sich um einen Integrationstest.
Allerdings eine Einschränkung. Interessiert es Sie wirklich, wie die Leute denken, dass Ihre Tests genannt werden sollten?
Ihre eigentliche Frage scheint jedoch folgende zu sein:
Ich denke, das Problem dabei ist, dass viele Leute Mocks verwenden, um die Abhängigkeiten vollständig wiederherzustellen. Zum Beispiel könnte ich den Rechner in Ihrem Beispiel als verspotten
Ich würde sowas nicht machen:
Ich würde das argumentieren, das wäre "mein Mock testen" oder "die Implementierung testen". Ich würde sagen " Schreibe keine Mocks! * So".
Andere Leute würden mit mir nicht einverstanden sein, wir würden massive Flammenkriege auf unseren Blogs über den besten Weg zu Mock beginnen, was wirklich keinen Sinn ergeben würde, wenn Sie nicht den gesamten Hintergrund der verschiedenen Ansätze verstehen und wirklich nicht viel Wert bieten an jemanden, der nur gute Tests schreiben will.
quelle
MockCalc
in umbenennenStubCalc
und es einen Stich nennen, keinen Schein. martinfowler.com/articles/…Meine Faustregel ist, dass richtige Unit-Tests:
Wenn es Dienstprogrammklassen aus Frameworks gibt, die den Komponententest erschweren, kann es sogar nützlich sein, sehr einfache "Wrapper" -Schnittstellen und -Klassen zu erstellen, um das Verspotten dieser Abhängigkeiten zu erleichtern. Diese Umhüllungen würden dann nicht unbedingt Unit-Tests unterzogen.
Ich habe festgestellt, dass diese Unterscheidung am nützlichsten ist:
Hier gibt es eine Grauzone. Wenn Sie beispielsweise eine Anwendung in einem Docker-Container ausführen und die Integrationstests als letzte Phase eines Builds ausführen und den Container anschließend zerstören können, ist es in Ordnung, diese Tests als "Komponententests" einzuschließen? Wenn dies Ihre brennende Debatte ist, sind Sie an einem ziemlich guten Ort.
Nein. Einige einzelne Testfälle betreffen Fehlerbedingungen, z. B. die Übergabe
null
als Parameter und die Überprüfung , ob eine Ausnahme vorliegt . Viele solcher Tests erfordern keine Verspottungen. Implementierungen, die keine Nebenwirkungen haben, z. B. Zeichenfolgenverarbeitung oder mathematische Funktionen, erfordern möglicherweise keine Mocks, da Sie einfach die Ausgabe überprüfen. Aber die meisten Klassen, die es wert sind, besucht zu werden, werden meines Erachtens mindestens einen Schein im Testcode erfordern. (Je weniger, desto besser.)Das von Ihnen erwähnte Problem des "Codegeruchs" tritt auf, wenn Sie eine übermäßig komplizierte Klasse haben, die eine lange Liste von Scheinabhängigkeiten erfordert, um Ihre Tests zu schreiben. Dies ist ein Hinweis darauf, dass Sie die Implementierung überarbeiten und die Dinge aufteilen müssen, damit jede Klasse eine kleinere Grundfläche und eine klarere Verantwortung hat und daher einfacher zu testen ist. Dies wird die Qualität auf lange Sicht verbessern.
Ich denke nicht, dass dies eine vernünftige Erwartung ist, weil es gegen die Wiederverwendung funktioniert. Möglicherweise verfügen Sie über eine
private
Methode, die von mehrerenpublic
von Ihrer Schnittstelle veröffentlichten Methoden aufgerufen wird . Ein in diese eine Methode eingeführter Fehler kann dann zu mehreren Testfehlern führen. Dies bedeutet nicht, dass Sie in jedepublic
Methode denselben Code kopieren sollten .quelle
Ich bin mir nicht sicher, wie diese Regel nützlich ist. Wenn eine Änderung in einer Klasse / Methode / was auch immer das Verhalten einer anderen Klasse im Produktionscode stören kann, dann sind die Dinge in Wirklichkeit Kollaborateure und nicht unabhängig voneinander. Wenn Ihre Tests nicht funktionieren und Ihr Produktionscode nicht, sind Ihre Tests verdächtig.
Ich würde diese Regel auch mit Argwohn betrachten. Wenn Sie wirklich gut genug sind, um Ihren Code zu strukturieren und Ihre Tests so zu schreiben, dass ein Fehler genau einen Unit-Test-Fehler verursacht, dann sagen Sie, Sie haben bereits alle potenziellen Fehler identifiziert, auch wenn sich die Codebasis dahingehend entwickelt, dass Sie Fälle verwenden habe nicht erwartet.
Ich denke nicht, dass das ein wichtiger Unterschied ist. Was ist überhaupt eine Codeeinheit?
Versuchen Einspeisepunkten zu finden , an denen Sie Tests schreiben können , die nur ‚Sinn machen‘ im Hinblick auf die Problemdomäne / Geschäftsregeln , dass das Niveau des Codes mit zu tun hat . Häufig sind diese Tests von Natur aus etwas "funktional" - geben Sie einen Input ein und prüfen Sie, ob der Output den Erwartungen entspricht. Wenn die Tests ein gewünschtes Verhalten des Systems zum Ausdruck bringen, bleiben sie häufig recht stabil, auch wenn sich der Produktionscode weiterentwickelt und überarbeitet wird.
Lesen Sie nicht zu viel in das Wort "Einheit" und versuchen Sie, Ihre realen Produktionsklassen in Tests zu verwenden, ohne sich zu viele Sorgen zu machen, wenn Sie mehr als eine von ihnen in einen Test einbeziehen. Wenn einer von ihnen schwer zu bedienen ist (weil viel Initialisierung erforderlich ist oder ein echter Datenbank- / E-Mail-Server usw. betroffen sein muss), lassen Sie Ihre Gedanken sich auf Spott / Täuschung konzentrieren.
quelle
Person:tellStory()
Methode die Details einer Person in eine Zeichenfolge einbezieht, dann wird das zurückgegeben, dann ist die "Geschichte" wahrscheinlich eine Einheit. Wenn ich eine private Hilfsmethode erstelle, mit der ein Teil des Codes verworfen wird, dann glaube ich nicht, dass ich eine neue Einheit eingeführt habe - ich muss das nicht separat testen.Erstens einige Definitionen:
Ein Komponententest testet Einheiten isoliert von anderen Einheiten, aber was dies bedeutet, wird von keiner maßgeblichen Quelle konkret definiert. Definieren wir es also etwas besser: Wenn E / A-Grenzen überschritten werden (ob es sich bei dieser E / A um Netzwerk-, Datenträger-, Bildschirm, oder UI-Eingabe), gibt es einen semi-objektiven Ort, an dem wir eine Linie zeichnen können. Wenn der Code von der E / A abhängt, überschreitet er eine Einheitengrenze und muss daher die für diese E / A verantwortliche Einheit verspotten.
Unter dieser Definition sehe ich keinen zwingenden Grund, Dinge wie reine Funktionen zu verspotten, was bedeutet, dass Unit-Tests sich für reine Funktionen oder Funktionen ohne Nebenwirkungen eignen.
Wenn Sie Einheiten mit Effekten testen möchten, sollten Sie sich über die Einheiten lustig machen, die für die Effekte verantwortlich sind. Vielleicht sollten Sie stattdessen einen Integrationstest in Betracht ziehen. Die kurze Antwort lautet also: "Wenn Sie sich lustig machen müssen, fragen Sie sich, ob Sie wirklich einen Integrationstest benötigen." Aber hier gibt es eine bessere, längere Antwort, und das Kaninchenloch geht viel tiefer. Mocks sind vielleicht mein Lieblingscode-Geruch, weil es so viel zu lernen gibt.
Code riecht
Dazu wenden wir uns an Wikipedia:
Es geht später weiter ...
Mit anderen Worten, nicht alle Codegerüche sind schlecht. Stattdessen sind sie häufige Anzeichen dafür, dass etwas möglicherweise nicht in seiner optimalen Form ausgedrückt wird, und der Geruch kann eine Gelegenheit zur Verbesserung des fraglichen Codes anzeigen.
Im Falle des Verspottens zeigt der Geruch an, dass die Einheiten, die anscheinend Verspottungen fordern, von den zu verspottenden Einheiten abhängen . Dies könnte ein Hinweis darauf sein, dass wir das Problem nicht in atomar lösbare Teile zerlegt haben und dass dies auf einen Designfehler in der Software hindeuten könnte.
Die Essenz jeder Softwareentwicklung besteht darin, ein großes Problem in kleinere, unabhängige Teile zu zerlegen (Zerlegung) und die Lösungen zu einer Anwendung zusammenzufassen, die das große Problem löst (Komposition).
Eine Verspottung ist erforderlich, wenn die Einheiten, mit denen das große Problem in kleinere Teile zerlegt wird, voneinander abhängen. Anders ausgedrückt ist eine Verspottung erforderlich, wenn unsere angenommenen atomaren Kompositionseinheiten nicht wirklich atomar sind und unsere Zerlegungsstrategie es nicht geschafft hat, das größere Problem in kleinere, unabhängige Probleme zu zerlegen, die gelöst werden müssen.
Es ist nicht so, dass das Verspotten eines Codes von Natur aus falsch ist - manchmal ist es sehr nützlich. Was es zu einem Codegeruch macht, ist, dass es auf eine problematische Kopplungsquelle in Ihrer Anwendung hinweisen kann. Manchmal ist es viel produktiver, diese Kopplungsquelle zu entfernen, als einen Schein zu schreiben.
Es gibt viele Arten der Kopplung, und einige sind besser als andere. Wenn Sie verstehen, dass Mocks ein Codegeruch sind, können Sie frühzeitig im Lebenszyklus des Anwendungsdesigns die schlimmsten Arten identifizieren und vermeiden , bevor sich der Geruch zu etwas Schlimmerem entwickelt.
quelle
Verspottung sollte nur als letztes Mittel eingesetzt werden, auch bei Unit-Tests.
Eine Methode ist keine Einheit, und selbst eine Klasse ist keine Einheit. Eine Einheit ist eine logische Trennung von Code, die unabhängig von Ihrer Bezeichnung Sinn macht. Ein wichtiges Element für gut getesteten Code ist die freie Umgestaltung, und ein Teil der freien Umgestaltung bedeutet, dass Sie Ihre Tests nicht ändern müssen, um dies zu tun. Je mehr Sie verspotten, desto mehr müssen Sie Ihre Tests ändern, wenn Sie umgestalten. Wenn Sie die Methode als Einheit betrachten, müssen Sie Ihre Tests jedes Mal ändern, wenn Sie umgestalten. Wenn Sie die Klasse als Einheit betrachten, müssen Sie Ihre Tests jedes Mal ändern, wenn Sie eine Klasse in mehrere Klassen aufteilen möchten. Wenn Sie Ihre Tests umgestalten müssen, um Ihren Code umzugestalten, entscheiden sich die Leute dafür, ihren Code nicht umzugestalten, was fast das Schlimmste ist, was einem Projekt passieren kann. Es ist wichtig, dass Sie eine Klasse in mehrere Klassen aufteilen können, ohne Ihre Tests überarbeiten zu müssen. Andernfalls erhalten Sie übergroße 500-Zeilen-Spaghetti-Klassen. Wenn Sie Methoden oder Klassen als Einheiten mit Unit-Tests behandeln, führen Sie wahrscheinlich keine objektorientierte Programmierung durch, sondern eine Art mutierte funktionale Programmierung mit Objekten.
Das Isolieren Ihres Codes für einen Komponententest bedeutet nicht, dass Sie alles außerhalb des Codes verspotten. Wenn ja, müssten Sie sich über den Mathematikunterricht Ihrer Sprache lustig machen, und absolut niemand hält das für eine gute Idee. Interne Abhängigkeiten sollten nicht anders behandelt werden als externe Abhängigkeiten. Sie vertrauen darauf, dass sie gut getestet sind und so arbeiten, wie sie es sollen. Der einzige wirkliche Unterschied ist, dass wenn Ihre internen Abhängigkeiten Ihre Module beschädigen, Sie stoppen können, was Sie tun, um es zu beheben, anstatt ein Problem auf GitHub zu posten und entweder in eine Codebasis zu graben, die Sie nicht verstehen, um es zu beheben oder auf das Beste hoffen.
Das Isolieren Ihres Codes bedeutet nur, dass Sie Ihre internen Abhängigkeiten wie Black Boxes behandeln und nicht Dinge testen, die in ihnen geschehen. Wenn Sie Modul B haben, das Eingaben von 1, 2 oder 3 akzeptiert, und Sie Modul A haben, das es aufruft, müssen Sie Ihre Tests für Modul A nicht für jede dieser Optionen ausführen, sondern nur eine auswählen und diese verwenden. Das bedeutet, dass Ihre Tests für Modul A die verschiedenen Arten testen sollten, wie Sie die Antworten von Modul B behandeln, und nicht die Dinge, die Sie in dieses Modul übertragen.
Wenn Ihr Controller also ein komplexes Objekt an eine Abhängigkeit übergibt und diese Abhängigkeit mehrere mögliche Aktionen ausführt, wird es möglicherweise in der Datenbank gespeichert und eine Reihe von Fehlern zurückgegeben. Tatsächlich prüft Ihr Controller jedoch nur, ob es zurückgegeben wird Wenn ein Fehler vorliegt oder nicht und diese Informationen weitergegeben werden, ist alles, was Sie in Ihrem Controller testen, ein Test, um festzustellen, ob ein Fehler vorliegt, und ein Test, um festzustellen, ob kein Fehler vorliegt. Sie testen nicht, ob etwas in der Datenbank gespeichert wurde oder welche Art von Fehler der Fehler ist, da dies ein Integrationstest wäre. Sie müssen die Abhängigkeit nicht verspotten, um dies zu tun. Sie haben den Code isoliert.
quelle