Wie genau sollten Komponententests geschrieben werden, ohne sich ausgiebig zu lustig zu machen?

80

Soweit ich weiß, besteht der Sinn von Unit-Tests darin, Code-Units isoliert zu testen . Das bedeutet, dass:

  1. Sie sollten nicht durch eine unabhängige Codeänderung an einer anderen Stelle in der Codebasis unterbrochen werden.
  2. 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.

  1. Wie sollten Komponententests richtig geschrieben werden?
  2. 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.calculateMethode testet , ohne die CalculatorAbhängigkeit zu verspotten (vorausgesetzt, es Calculatorhandelt sich um eine Lightweight-Klasse, die nicht auf "die Außenwelt" zugreift), als Komponententest betrachtet werden?

Alexander Lomia
quelle
10
Ein Teil davon ist nur die Designerfahrung, die mit der Zeit einhergeht. Sie lernen, wie Sie Ihre Komponenten so strukturieren, dass sie nicht viele schwer zu verspottende Abhängigkeiten aufweisen. Dies bedeutet, dass die Testbarkeit ein sekundäres Entwurfsziel jeder Software sein muss. Dieses Ziel ist viel einfacher zu erreichen, wenn Tests vor oder zusammen mit dem Code geschrieben werden, z. B. mit TDD und / oder BDD.
amon
33
Legen Sie schnell und zuverlässig ausgeführte Tests in einem Ordner ab. Setzen Sie Tests, die langsam und möglicherweise zerbrechlich sind, in einen anderen ein. Führen Sie die Tests so oft wie möglich im ersten Ordner aus (praktisch jedes Mal, wenn Sie die Eingabe unterbrechen und der Code kompiliert wird, ist dies ideal, aber nicht alle Entwicklungsumgebungen unterstützen dies). Führen Sie die langsameren Tests seltener durch (z. B. bei einer Kaffeepause). Machen Sie sich keine Gedanken über Einheiten- und Integrationsnamen. Ruf sie schnell und langsam an, wenn du willst. Es spielt keine Rolle.
David Arno
6
"Das muss praktisch jeder Unit-Test verspotten" Ja, also? "Eine schnelle Google-Suche über das Verspotten enthüllt Unmengen von Artikeln, die behaupten, dass" Verspotten ein Codegeruch ist "," dass sie falsch sind .
Michael
13
@Michael Es ist keine gute Möglichkeit, sich einem umstrittenen Thema wie diesem zu nähern, wenn man nur "Ja, also" angibt und die gegnerische Ansicht für falsch erklärt. Schreiben Sie vielleicht eine Antwort und erläutern Sie, warum Sie der Meinung sind, dass Spott überall sein sollte, und warum Sie der Meinung sind, dass „Tonnen von Artikeln“ von Natur aus falsch sind?
AJFaraday
6
Da Sie kein Zitat für "Verspotten ist ein Codegeruch" angegeben haben, kann ich nur vermuten, dass Sie das, was Sie lesen, falsch interpretieren. Verspotten ist kein Codegeruch. Die Notwendigkeit, Reflection oder andere Spielereien zu verwenden, um Ihre Mocks zu injizieren, ist ein Code-Geruch. Die Schwierigkeit des Verspottens ist umgekehrt proportional zur Qualität Ihres API-Designs. Wenn Sie einfache, unkomplizierte Komponententests schreiben können, bei denen nur Verspottungen an Konstruktoren weitergegeben werden, machen Sie es richtig.
TKK

Antworten:

59

Der Zweck von Unit-Tests besteht darin, Code-Units isoliert zu testen.

Martin Fowler über Unit Test

Unit-Tests werden in der Software-Entwicklung oft genannt und sind ein Begriff, mit dem ich während meiner gesamten Zeit als Programmierer vertraut war. Wie die meisten Begriffe in der Softwareentwicklung ist sie jedoch sehr schlecht definiert, und ich sehe, dass es häufig zu Verwirrung kommt, wenn die Leute glauben, dass sie strenger definiert sind als sie tatsächlich sind.

Was Kent Beck in Test Driven Development mit gutem Beispiel schrieb

Ich nenne sie "Komponententests", aber sie stimmen nicht sehr gut mit der akzeptierten Definition von Komponententests überein

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:

Kann ein Test, der die Person.calculate-Methode testet, ohne die Calculator-Abhängigkeit zu verspotten (vorausgesetzt, der Calculator ist eine einfache Klasse, die nicht auf "die Außenwelt" zugreift), als Komponententest betrachtet werden?

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::adddie 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.

VoiceOfUnreason
quelle
5
Eine Sache, die Mocking Add macht, ist zu beweisen, dass der Rechner verspottet werden kann , dh dass das Design Person und Rechner nicht koppelt (obwohl dies auch auf andere Weise überprüft werden kann)
jk.
40

Wie genau sollten Komponententests geschrieben werden, ohne sich ausgiebig zu lustig zu machen?

Indem Sie die Nebenwirkungen in Ihrem Code minimieren.

Nehmen Sie Ihren Beispielcode, wenn Sie calculatorzum 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.

David Arno
quelle
8
Dies ist die richtige Antwort sowohl in der Praxis als auch im Hinblick auf das Ausweichen aus einer bedeutungslosen semantischen Debatte.
Jared Smith
Haben Sie ein Beispiel für eine nicht triviale Open Source-Codebasis, die einen solchen Stil verwendet und dennoch eine gute Testabdeckung erhält?
Joeri Sebrechts
4
@JoeriSebrechts jeder einzelne FP? Beispiel
Jared Smith
Nicht ganz das, wonach ich suche, da es sich nur um eine Sammlung voneinander unabhängiger Funktionen handelt, nicht um ein System von Funktionen, die sich gegenseitig aufrufen. Wie können Sie umgehen, wenn Sie komplexe Argumente für eine Funktion erstellen müssen, um sie zu testen, wenn diese Funktion zu den Funktionen der obersten Ebene gehört? ZB die Kernschleife eines Spiels.
Joeri Sebrechts
1
@JoeriSebrechts hmm, entweder verstehe ich falsch, was du willst, oder du hast nicht tief genug in das Beispiel gegraben, das ich gegeben habe. Die Ramda-Funktionen verwenden interne Anrufe überall in ihrer Quelle (z 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.
Jared Smith
31

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.

Kilian Foth
quelle
4
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.
Laiv
1
Wenn eine Einheit, die getestet wird, andere Einheiten verwendet, ist es dann nicht wirklich ein Integrationstest? Denn im Wesentlichen würde diese Einheit die Interaktion zwischen den Einheiten testen, genau wie ein Integrationstest.
Alexander Lomia
11
@AlexanderLomia: Wie würden Sie eine Einheit nennen? Würden Sie "String" auch eine Einheit nennen? Ich würde, aber ich würde nicht davon träumen, mich darüber lustig zu machen.
Bart van Ingen Schenau
5
" 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. " Hier ist die Unebenheit. Das ist Ihre Definition eines Komponententests. Meins ist ganz anders. Die Unterscheidung zwischen ihnen ist also für eine bestimmte Definition nur "klar getrennt", aber die Trennung variiert zwischen den Definitionen.
David Arno
4
@Voo Nachdem ich mit solchen Codebasen gearbeitet habe, kann es ein Ärgernis sein, das ursprüngliche Problem zu finden (insbesondere, wenn die Architektur die zum Debuggen verwendeten Elemente übermalt hat), hatte ich immer noch mehr Probleme mit den Verspottungen, die das verursachten Tests, um weiterzuarbeiten, nachdem der Code, mit dem sie getestet wurden, fehlerhaft war.
James_pic
6

OK, um Ihre Fragen direkt zu beantworten:

Wie sollten Komponententests richtig geschrieben werden?

Wie Sie sagen, sollten Sie Abhängigkeiten verspotten und nur die betreffende Einheit testen.

Wo genau liegt die Grenze zwischen ihnen und Integrationstests?

Ein Integrationstest ist ein Komponententest, bei dem Ihre Abhängigkeiten nicht verspottet werden.

Kann ein Test, der die Person.calculate-Methode testet, ohne den Rechner zu verspotten, als Komponententest betrachtet 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:

Eine schnelle Google-Suche über das Verspotten enthüllt Unmengen von Artikeln, die behaupten, dass "Verspotten ein Codegeruch ist" und größtenteils (wenn auch nicht vollständig) vermieden werden sollten.

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

public class MockCalc : ICalculator
{
     public Add(int a, int b) { return 4; }
}

Ich würde sowas nicht machen:

myMock = Mock<ICalculator>().Add((a,b) => {return a + b;})
myPerson.Calculate()
Assert.WasCalled(myMock.Add());

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.

Ewan
quelle
Vielen Dank für eine ausführliche Antwort. Was die Meinung anderer Leute zu meinen Tests angeht - eigentlich möchte ich vermeiden, Semi-Integrationstests zu schreiben, die im Verlauf des Projekts unzuverlässig werden.
Alexander Lomia
Keine Probleme, ich denke, das Problem ist, dass die Definitionen der beiden Dinge nicht von allen zu 100% akzeptiert werden.
Ewan
Ich würde Ihre Klasse MockCalcin umbenennen StubCalcund es einen Stich nennen, keinen Schein. martinfowler.com/articles/…
BDSL
@bdsl Dieser Artikel ist 15 Jahre alt
Ewan
4
  1. Wie sollen Unit-Tests richtig durchgeführt werden?

Meine Faustregel ist, dass richtige Unit-Tests:

  • Sind gegen Schnittstellen codiert, keine Implementierungen . Das hat viele Vorteile. Zum einen wird sichergestellt, dass Ihre Klassen dem Dependency Inversion Principle von SOLID folgen . Dies ist auch das, was Ihre anderen Klassen tun ( oder? ), Sodass Ihre Tests dasselbe tun sollten. Auf diese Weise können Sie auch mehrere Implementierungen derselben Schnittstelle testen, während Sie einen Großteil des Testcodes wiederverwenden (nur die Initialisierung und einige Zusicherungen würden sich ändern).
  • Sind in sich geschlossen . Wie Sie sagten, können Änderungen an externem Code das Testergebnis nicht beeinflussen. Daher können Komponententests zur Build-Zeit ausgeführt werden. Dies bedeutet, dass Sie Mocks benötigen, um alle Nebenwirkungen zu beseitigen. Wenn Sie jedoch das Prinzip der Abhängigkeitsinversion befolgen, sollte dies relativ einfach sein. Gute Test-Frameworks wie Spock können verwendet werden, um mock-Implementierungen jeder Schnittstelle, die Sie in Ihren Tests verwenden möchten, mit minimalem Programmieraufwand dynamisch bereitzustellen. Dies bedeutet, dass jede Testklasse nur Code von genau einer Implementierungsklasse plus Testframework (und möglicherweise Modellklassen ["Beans"]) ausüben muss .
  • Benötigen Sie keine separate laufende Anwendung . Wenn der Test mit einer Datenbank oder einem Webdienst "kommunizieren" muss, handelt es sich um einen Integrationstest und nicht um einen Komponententest. Ich zeichne die Linie bei Netzwerkverbindungen oder dem Dateisystem. Eine rein speicherinterne SQLite-Datenbank zum Beispiel ist meiner Meinung nach ein faires Spiel für einen Komponententest, wenn Sie ihn wirklich brauchen.

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.

  1. Wo genau liegt die Grenze zwischen ihnen [Komponententests] und Integrationstests?

Ich habe festgestellt, dass diese Unterscheidung am nützlichsten ist:

  • Unit-Tests simulieren "Benutzercode" und überprüfen das Verhalten von Implementierungsklassen anhand des gewünschten Verhaltens und der Semantik von Schnittstellen auf Codeebene .
  • Integrationstests simulieren den Benutzer und überprüfen das Verhalten der ausgeführten Anwendung anhand bestimmter Anwendungsfälle und / oder formaler APIs. Für einen Webdienst wäre der "Benutzer" eine Client-Anwendung.

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.

  1. Stimmt es, dass praktisch jeder Komponententest verspottet werden muss?

Nein. Einige einzelne Testfälle betreffen Fehlerbedingungen, z. B. die Übergabe nullals 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.

Nur ein Unit-Test sollte durch einen Fehler in der getesteten Unit unterbrochen werden.

Ich denke nicht, dass dies eine vernünftige Erwartung ist, weil es gegen die Wiederverwendung funktioniert. Möglicherweise verfügen Sie über eine privateMethode, die von mehreren publicvon 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 jede publicMethode denselben Code kopieren sollten .

wberry
quelle
3
  1. Sie sollten nicht durch eine unabhängige Codeänderung an einer anderen Stelle in der Codebasis unterbrochen werden.

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.

  1. Im Gegensatz zu Integrationstests (die in Haufen brechen können) sollte nur ein Unit-Test durch einen Fehler in der getesteten Unit unterbrochen werden.

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.

Wo genau liegt die Grenze zwischen ihnen und Integrationstests?

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.

Wie genau sollten Komponententests geschrieben werden, ohne sich ausgiebig zu lustig zu machen?

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.

topo morto
quelle
" Was ist überhaupt eine 'Einheit' von Code? ", Eine sehr gute Frage, die unerwartete Antworten haben kann, die sogar davon abhängen können, wer antwortet. Normalerweise erklären die meisten Definitionen von Komponententests, dass sie sich auf eine Methode oder eine Klasse beziehen, aber das ist nicht in allen Fällen ein wirklich nützliches Maß für eine "Einheit". Wenn ich habe, dass eine 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.
VLAZ
1

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:

Bei der Computerprogrammierung ist ein Codegeruch ein beliebiges Merkmal im Quellcode eines Programms, das möglicherweise auf ein tieferes Problem hinweist.

Es geht später weiter ...

"Gerüche sind bestimmte Strukturen im Code, die auf einen Verstoß gegen grundlegende Designprinzipien hinweisen und sich negativ auf die Designqualität auswirken." Suryanarayana, Girish (November 2014). Refactoring für Software Design Smells. Morgan Kaufmann. p. 258.

Code-Gerüche sind normalerweise keine Fehler. Sie sind technisch nicht inkorrekt und beeinträchtigen die Funktion des Programms nicht. Stattdessen weisen sie auf Schwachstellen im Design hin, die die Entwicklung verlangsamen oder das Risiko von Fehlern oder Ausfällen in der Zukunft erhöhen können.

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.

Eric Elliott
quelle
0

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.

TiggerToo
quelle