Sind (Datenbank-) Integrationstests schlecht?


120

Einige Leute behaupten, dass Integrationstests alle Arten von schlecht und falsch sind - alles muss Unit-getestet sein, was bedeutet, dass man Abhängigkeiten verspotten muss; Eine Option, die ich aus verschiedenen Gründen nicht immer mag.

Ich finde, dass ein Unit-Test in einigen Fällen einfach nichts beweist.

Nehmen wir als Beispiel die folgende (triviale, naive) Repository-Implementierung (in PHP):

class ProductRepository
{
    private $db;

    public function __construct(ConnectionInterface $db) {
        $this->db = $db;
    }

    public function findByKeyword($keyword) {
        // this might have a query builder, keyword processing, etc. - this is
        // a totally naive example just to illustrate the DB dependency, mkay?

        return $this->db->fetch("SELECT * FROM products p"
            . " WHERE p.name LIKE :keyword", ['keyword' => $keyword]);
    }
}

Angenommen, ich möchte in einem Test nachweisen, dass dieses Repository tatsächlich Produkte finden kann, die mit verschiedenen angegebenen Schlüsselwörtern übereinstimmen.

Wie kann ich nach einem Integrationstest mit einem realen Verbindungsobjekt feststellen, dass tatsächlich echte Abfragen generiert werden - und dass diese Abfragen tatsächlich das tun, was ich denke, dass sie tun?

Wenn ich das Verbindungsobjekt in einem Unit-Test verspotten muss, kann ich nur beweisen, dass "es die erwartete Abfrage generiert" - aber das bedeutet nicht, dass es tatsächlich funktionieren wird ... das heißt, dass es möglicherweise die Abfrage generiert Ich habe es erwartet, aber vielleicht macht diese Abfrage nicht das, was ich denke.

Mit anderen Worten, ich fühle mich wie ein Test, der Aussagen über die generierte Abfrage macht, im Wesentlichen wertlos ist, weil er testet, wie die findByKeyword()Methode implementiert wurde , aber das beweist nicht, dass sie tatsächlich funktioniert .

Dieses Problem ist nicht auf Repositorys oder die Datenbankintegration beschränkt - es scheint in vielen Fällen zutreffend zu sein, in denen Aussagen über die Verwendung eines Mocks (Test-Double) nur beweisen, wie die Dinge implementiert sind, nicht, ob sie implementiert werden eigentlich arbeiten.

Wie gehen Sie mit solchen Situationen um?

Sind Integrationstests in einem solchen Fall wirklich "schlecht"?

Ich verstehe, dass es besser ist, eine Sache zu testen, und ich verstehe auch, warum Integrationstests zu unzähligen Codepfaden führen, die alle nicht getestet werden können - aber im Fall eines Dienstes (wie z. B. eines Repositorys), dessen einziger Zweck darin besteht Wie können Sie ohne Integrationstests wirklich etwas testen, um mit einer anderen Komponente zu interagieren?


5
Lesen Sie agitar.com/downloads/TheWayOfTestivus.pdf , insbesondere Seite 6 "Der Test ist wichtiger als das Gerät".
Doc Brown

2
@ user61852 heißt es in der beschreibung "naiv", ja?
mindplay.dk

4
Wie würde Ihr Mitarbeiter absolut sicher sein, dass sich seine verspottete Datenbank wie die reale verhält?
Thorbjørn Ravn Andersen

12
Sie versuchen realistisch zu sein. Ihr Mitarbeiter versucht, sich an Regeln zu halten. Schreiben Sie immer Tests, die Wert erzeugen . Verschwenden Sie keine Zeit damit, Tests zu schreiben, die nicht reparabel sind, und schreiben Sie keine Tests, die eine der folgenden Aufgaben nicht ausführen: Erhöhen Sie die Wahrscheinlichkeit, dass Ihr Code korrekt ist, oder zwingen Sie Sie, wartbareren Code zu schreiben.
jpmc26

3
@ mindplay.dk: Der Schlüsselsatz in diesem Absatz lautet: "Aber bleiben Sie nicht bei einem Dogma hängen . Schreiben Sie den Test, der geschrieben werden muss." Ihr Kollege scheint in einem Dogma gefangen zu sein. Und Sie brauchen niemanden Erklären Sie, welcher Test in Ihrem Beispiel geschrieben werden muss - das wissen Sie bereits. Es ist ziemlich offensichtlich, dass Sie zum Testen, ob Ihre Datenbank die Abfrage versteht, die Abfrage für die reale Datenbank ausführen müssen - kein Schein kann es Ihnen sagen this.
Doc Brown

Antworten:


129

Ihr Kollege hat Recht, dass alles, was Unit-Tests unterzogen werden können, Unit-Tests unterzogen werden sollten, und Sie haben Recht, dass Unit-Tests Sie nur so weit und nicht weiter bringen, insbesondere wenn Sie einfache Wrapper für komplexe externe Services schreiben.

Eine übliche Denkweise beim Testen ist eine Testpyramide . Es ist ein Konzept, das häufig mit Agile in Verbindung steht, und viele haben darüber geschrieben, darunter Martin Fowler (der es Mike Cohn beim Erfolg von Agile zuschreibt ), Alistair Scott und der Google Testing Blog .

        /\                           --------------
       /  \        UI / End-to-End    \          /
      /----\                           \--------/
     /      \     Integration/System    \      /
    /--------\                           \----/
   /          \          Unit             \  /
  --------------                           \/
  Pyramid (good)                   Ice cream cone (bad)

Der Gedanke ist, dass schnell ablaufende, ausfallsichere Komponententests die Grundlage des Testprozesses bilden. Es sollten konzentriertere Komponententests als System- / Integrationstests und mehr System- / Integrationstests als End-to-End-Tests vorhanden sein. Wenn Sie sich der Spitze nähern, benötigen die Tests in der Regel mehr Zeit und Ressourcen, sind spröder und instabiler und können weniger genau feststellen, welches System oder welche Datei defekt ist . Natürlich ist es vorzuziehen, nicht "kopflastig" zu sein.

Bis zu diesem Punkt sind Integrationstests nicht schlecht , aber starkes Vertrauen in sie kann darauf hinweisen, dass Sie Ihre einzelnen Komponenten nicht so entworfen haben, dass sie einfach zu testen sind. Denken Sie daran, dass das Ziel hier darin besteht, zu testen, ob Ihr Gerät seinen Spezifikationen entspricht, und dabei ein Minimum an anderen zerbrechlichen Systemen zu berücksichtigen : Möglicherweise möchten Sie eine speicherinterne Datenbank testen (die ich neben Mocks als ein für Unit-Tests geeignetes Test-Double bezeichne ) zum Beispiel für umfangreiche Edge-Case-Tests, und schreiben Sie dann einige Integrationstests mit der realen Datenbank-Engine, um festzustellen, ob die Hauptfälle beim Zusammenbau des Systems funktionieren.


Als Randnotiz haben Sie erwähnt, dass die Mocks, die Sie schreiben, lediglich testen, wie etwas implementiert ist, nicht, ob es funktioniert . Das ist so etwas wie ein Gegenmuster: Ein Test, der ein perfekter Spiegel seiner Implementierung ist, testet überhaupt nichts. Testen Sie stattdessen, ob sich jede Klasse oder Methode gemäß ihrer eigenen Spezifikation verhält , unabhängig von der erforderlichen Abstraktion oder dem erforderlichen Realismus.


13
+1 für "Ein Test, der ein perfekter Spiegel seiner Implementierung ist, testet eigentlich gar nichts." Allzu häufig. Ich nenne das das Doppelgänger-Antipattern .
dodgethesteamroller

6
Eine konträre Schule für Software-Qualitätssicherung, die kontextgesteuerte Testbewegung, widmet sich zum Teil der Auseinandersetzung darüber , dass es so nützliche allgemeine Faustregeln wie die Testpyramide gibt. Insbesondere die zukunftsträchtige Texte geben der Bewegung viele Beispiele , in denen Integrationstests viel wertvoller sind als andere Arten von Tests (weil sie testen das System im Kontext als System ) ....
dodgethesteamroller

7
... folglich Fowler et al., In dem Argumente , dass weniger Aufwand auf Integrationstests und User Acceptance Tests ausgegeben werden soll , weil sie zu schwer sind in einer robusten und wartungsfreundlicher Art und Weise zu schreiben, bietet wirklich nur eine nachträgliche Entschuldigung dafür , warum Sie haben nicht herausgefunden, wie man auf höheren Ebenen gut testet.
dodgethesteamroller

1
@dodgethesteamroller Eingehende Diskussionen über eine solche "Contrarian School" sind wahrscheinlich am besten für ihre eigene Antwort geeignet. Ich persönlich finde , dass die Google Testing Blog hat einen ziemlich guten Job beschreibt die Vorzüge der schnellen, dicht scoped automatisierte Tests neben System-in-context - Tests. Für den Fall, dass Sie es unklar fanden, liste ich die Testpyramide hier als nützliches Modell oder Ausgangspunkt auf, nicht als Entschuldigung, um nicht mehr als Ingenieur zu denken.
Jeff Bowman

1
Sehr empfehlenswerte Darstellung der Hierarchie und des Verhältnisses von Unittests zu Integrationstests: vimeo.com/80533536 Sehr gut erklärt.
Szalski

88

Einer meiner Mitarbeiter meint, dass Integrationstests alle Arten von schlecht und falsch sind - alles muss Unit-getestet werden,

Das ist ein bisschen wie zu sagen, dass Antibiotika schlecht sind - alles sollte mit Vitaminen geheilt werden.

Unit-Tests können nicht alles erfassen - sie testen nur, wie eine Komponente in einer kontrollierten Umgebung funktioniert . Integrationstests bestätigen, dass alles zusammenarbeitet , was schwieriger, aber letztendlich aussagekräftiger ist.

Ein guter, umfassender Testprozess verwendet beide Arten von Tests - Komponententests zur Überprüfung von Geschäftsregeln und anderen Dingen, die unabhängig voneinander getestet werden können, und Integrationstests, um sicherzustellen, dass alles zusammenarbeitet.

Wie kann ich nach einem Integrationstest mit einem realen Verbindungsobjekt feststellen, dass tatsächlich echte Abfragen generiert werden - und dass diese Abfragen tatsächlich das tun, was ich denke, dass sie tun?

Sie können es auf Datenbankebene testen . Führen Sie die Abfrage mit verschiedenen Parametern aus und prüfen Sie, ob Sie die erwarteten Ergebnisse erhalten. Zugegeben, es bedeutet, dass alle Änderungen zurück in den "wahren" Code kopiert / eingefügt werden. aber es wird Ihnen erlauben , die Abfrage unabhängig von irgendwelchen anderen Abhängigkeiten zu testen.


Würden Sie nicht testen, ob die Datenbank bestimmte Daten enthält oder nicht?
Tulains Córdova

Möglicherweise - aber Sie könnten auch testen, ob Ihre Filter, komplexen Verknüpfungen usw. funktionieren. Die Beispielabfrage ist wahrscheinlich nicht der beste Kandidat für "Komponententests", aber eine mit komplexen Verknüpfungen und / oder Aggregationen.
D Stanley

Ja - das Beispiel, das ich verwendet habe, ist trivial; Ein echtes Repository kann alle möglichen komplexen Such- und
Sortieroptionen enthalten

2
Gute Antwort, aber ich würde hinzufügen, dass die DB im Speicher sein sollte, um sicherzustellen, dass Unit-Tests schnell sind.
Freitag,

3
@ BЈовић: Leider ist das nicht immer möglich, da es leider keine zwei kompatiblen DBs gibt und nicht alle im Speicher arbeiten. Es gibt auch Lizenzierungsprobleme für kommerzielle DBs (Sie haben möglicherweise nicht die Lizenz, um sie auf einem Computer auszuführen), ...
Matthieu M.

17

Unit Tests erkennen nicht alle Mängel. Aber sie sind billiger einzurichten und (erneut) auszuführen als andere Arten von Tests. Die Unit-Tests sind durch die Kombination von moderatem Wert und niedrigen bis moderaten Kosten gerechtfertigt.

In der folgenden Tabelle sind die Fehlererkennungsraten für verschiedene Testarten aufgeführt.

Bildbeschreibung hier eingeben

Quelle: S.470 in Code Complete 2 von McConnell


5
Diese Daten wurden 1986 gesammelt . Das war vor dreißig Jahren . Unit Tests im Jahr 1986 waren nicht das, was sie heute sind. Ich wäre skeptisch gegenüber diesen Daten. Ganz zu schweigen davon, dass Komponententests den Fehler erkennen, bevor er jemals begangen wurde. Daher ist es zweifelhaft, ob sie gemeldet werden.
RubberDuck

3
@RubberDuck Diese Grafik stammt aus einem Buch von 2006 und basiert auf Daten von 1986, 1996, 2002 (wenn Sie genau hinschauen). Ich habe mir die Datenerfassungsprotokolle in den Quellen nicht angesehen und kann nicht sagen, wann sie begonnen haben, einen Defekt zu verfolgen und wie er gemeldet wurde. Könnte dieses Diagramm etwas veraltet sein? Es könnte. Letzten Dezember war ich auf einem Seminar und der Kursleiter erwähnte, dass Integrationstests mehr Fehler finden als Unit-Tests (um den Faktor 2, iirc). Diese Tabelle besagt, dass sie ungefähr gleich sind.
Nick Alexeev

13

Nein, sie sind nicht schlecht. Hoffentlich sollte man Unit- und Integrationstests haben. Sie werden in verschiedenen Phasen des Entwicklungszyklus verwendet und ausgeführt.

Unit-Tests

Unit-Tests sollten auf dem Build-Server und lokal ausgeführt werden, nachdem der Code kompiliert wurde. Wenn ein Komponententest fehlschlägt, sollte der Build fehlschlagen oder die Code-Aktualisierung erst durchgeführt werden, wenn die Tests behoben sind. Der Grund, warum wir Komponententests isoliert haben möchten, ist, dass der Build-Server alle Tests ohne alle Abhängigkeiten ausführen kann. Dann könnten wir den Build ohne all die komplexen Abhängigkeiten ausführen und viele Tests durchführen, die sehr schnell ablaufen.

Für eine Datenbank sollte man also ungefähr Folgendes haben:

IRespository

List<Product> GetProducts<String Size, String Color);

Jetzt geht die eigentliche Implementierung von IRepository in die Datenbank, um die Produkte abzurufen, aber für Komponententests kann man IRepository mit einer Fälschung verspotten, um alle Tests nach Bedarf ohne eine Actaul-Datenbank auszuführen, da wir alle Arten von Produktlisten simulieren können von der Scheininstanz zurückgegeben werden und eine Geschäftslogik mit den gespielten Daten testen.

Integrationstests

Integrationstests sind typischerweise Grenzüberschreitungstests. Wir möchten diese Tests auf dem Bereitstellungsserver (der realen Umgebung), in der Sandbox oder sogar lokal (auf die Sandbox verwiesen) ausführen. Sie werden nicht auf dem Buildserver ausgeführt. Nachdem die Software in der Umgebung bereitgestellt wurde, werden diese normalerweise als Aktivitäten nach der Bereitstellung ausgeführt. Sie können über Befehlszeilendienstprogramme automatisiert werden. Zum Beispiel können wir nUnit über die Befehlszeile ausführen, wenn wir alle Integrationstests, die wir aufrufen möchten, kategorisieren. Diese rufen tatsächlich das reale Repository mit dem realen Datenbankaufruf auf. Diese Art von Tests helfen bei:

  • Umwelt Gesundheit Stabilität Bereitschaft
  • Testen Sie die reale Sache

Diese Tests sind manchmal schwieriger durchzuführen, da wir sie möglicherweise auch einrichten und / oder abbauen müssen. Erwägen Sie das Hinzufügen eines Produkts. Wahrscheinlich möchten wir das Produkt hinzufügen, es abfragen, um festzustellen, ob es hinzugefügt wurde, und es anschließend entfernen, nachdem wir fertig sind. Wir möchten keine 100er oder 1000er "Integrations" -Produkte hinzufügen, daher ist eine zusätzliche Einrichtung erforderlich.

Die Integrationstests können sich als sehr wertvoll erweisen, um eine Umgebung zu validieren und sicherzustellen, dass die Realität funktioniert.

Man sollte beides haben.

  • Führen Sie die Komponententests für jeden Build durch.
  • Führen Sie die Integrationstests für jede Bereitstellung aus.

Ich würde empfehlen, Integrationstests für jeden Build auszuführen, anstatt einen Commit und Push durchführen zu müssen. Kommt darauf an, wie lange sie dauern, aber es ist aus vielen Gründen auch eine gute Idee, sie schnell zu halten.
Artbristol

@ArtBristol - In der Regel ist auf unserem Build-Server nicht die vollständige Umgebungsabhängigkeit konfiguriert, sodass wir dort keine Integrationstests ausführen können. Aber wenn man die Integrationstests dort durchführen kann, dann ist es das Richtige. Nach dem Build wird eine Deployment-Sandbox eingerichtet, die wir zum Testen der Integration verwenden, um das Deployment zu überprüfen. Aber jede Situation ist anders.
Jon Raynor

11

Datenbankintegrationstests sind nicht schlecht. Mehr noch, sie sind notwendig.

Sie haben Ihre Anwendung wahrscheinlich in Ebenen aufgeteilt, und es ist eine gute Sache. Sie können jede Ebene einzeln testen, indem Sie benachbarte Ebenen verspotten, und das ist auch gut so. Ganz gleich, wie viele Abstraktionsebenen Sie erstellen, irgendwann muss es eine Ebene geben, die die Dirty-Arbeit erledigt - tatsächlich mit der Datenbank sprechen. Wenn Sie es nicht testen, testen Sie überhaupt nicht . Wenn Sie die Ebene n testen, indem Sie die Ebene n-1 verspotten, bewerten Sie die Annahme, dass die Ebene n unter der Bedingung funktioniert , dass die Ebene n-1 funktioniert. Damit dies funktioniert, müssen Sie irgendwie beweisen, dass Schicht 0 funktioniert.

Während Sie theoretisch eine Unit-Test-Datenbank erstellen könnten, indem Sie generiertes SQL analysieren und interpretieren, ist es viel einfacher und zuverlässiger, eine Test-Datenbank im laufenden Betrieb zu erstellen und mit ihr zu kommunizieren.

Fazit

Welches Vertrauen wird durch Unit-Tests Ihres Abstract Repository , Ethereal Object-Relational-Mapper , generischen aktiven Datensatzes und theoretischen Persistenz- Layers gewonnen, wenn Ihr generiertes SQL letztendlich einen Syntaxfehler enthält?


Ich habe darüber nachgedacht, eine ähnliche Antwort wie Ihre hinzuzufügen, aber Sie haben es besser gesagt! Nach meiner Erfahrung haben einige Tests auf der Ebene, die Daten abruft und speichert, mir viel Kummer erspart.
Daniel Hollinrake

Datenbankintegrationstests sind jedoch schlecht. Haben Sie eine Datenbank in Ihrer ci / cd-Zeile? Das erscheint mir ziemlich komplex. Es ist viel einfacher, das Datenbankmaterial zu verspotten und eine Abstraktionsebene zu erstellen, um dies zu verwenden. Dies ist nicht nur eine viel elegantere Lösung, sondern auch so schnell wie möglich. Unit-Tests müssen schnell sein. Das Testen Ihrer Datenbank verlangsamt Ihre Unittests erheblich auf ein Maß, das nicht akzeptabel ist. Unittests sollten nicht länger als 10 Minuten dauern, auch wenn Sie Tausende haben.
David

@David Haben Sie eine Datenbank in Ihrer ci / cd-Zeile? Klar, das ist ziemlich Standard - Feature . Übrigens, ich befürworte keine Integrationstests anstelle von Komponententests - ich befürworte Integrationstests in Verbindung mit Komponententests. Schnelle Komponententests sind unerlässlich, aber Datenbanken sind zu komplex, um sich auf Komponententests mit verspotteten Interaktionen zu verlassen.
el.pescado

@ el.pescado da muss ich widersprechen. Wenn sich Ihre Datenbankkommunikation hinter einer Abstraktionsebene befindet, ist es wirklich einfach, sich lustig zu machen. Sie können einfach entscheiden, welches Objekt zurückgegeben werden soll. Auch die Tatsache, dass etwas Standard ist, macht es nicht zu einer guten Sache.
David

@ David Ich denke, es hängt davon ab, wie Sie Datenbank nähern. Ist es ein Implementierungsdetail oder ein wesentlicher Teil des Systems ? (Ich neige mich zu Letzterem) . Wenn Sie eine Datenbank als dummen Datenspeicher betrachten, dann können Sie auf Integrationstests verzichten. Wenn die Datenbank jedoch Logik enthält - Einschränkungen, Trigger, Fremdschlüssel, Transaktionen oder Ihre Datenschicht verwendet benutzerdefiniertes SQL anstelle von einfachen ORM-Methoden -, sind Unit-Tests meiner Meinung nach nicht ausreichend.
el.pescado

6

Du brauchst beides.

Wenn Sie in Ihrem Beispiel getestet haben, dass sich eine Datenbank in einem bestimmten Zustand befindet, erhalten Sie beim findByKeywordAusführen der Methode die Daten zurück, von denen Sie erwarten, dass dies ein guter Integrationstest ist.

In jedem anderen Code, der diese findByKeywordMethode verwendet, möchten Sie steuern, was in den Test eingespeist wird, damit Sie Nullen oder die richtigen Wörter für Ihren Test oder was auch immer zurückgeben können. Dann verspotten Sie die Datenbankabhängigkeit, damit Sie genau wissen, was Ihr Test bewirken wird empfangen (und Sie verlieren den Overhead, wenn Sie eine Verbindung zu einer Datenbank herstellen und sicherstellen, dass die darin enthaltenen Daten korrekt sind)


6

Der Autor des Blog-Artikels, auf den Sie verweisen, befasst sich hauptsächlich mit der potenziellen Komplexität, die sich aus integrierten Tests ergeben kann (obwohl er sehr eigenwillig und kategorisch geschrieben ist). Integrierte Tests sind jedoch nicht unbedingt schlecht, und einige sind sogar nützlicher als reine Komponententests. Es hängt wirklich vom Kontext Ihrer Anwendung und dem ab, was Sie testen möchten.

Viele Anwendungen funktionieren heutzutage einfach überhaupt nicht, wenn der Datenbankserver ausfällt. Denken Sie zumindest an die Funktion, die Sie testen möchten.

Auf der einen Seite, wenn das, was Sie testen möchten, nicht von der Datenbank abhängt oder überhaupt nicht davon abhängig gemacht werden kann, schreiben Sie Ihren Test so, dass er nicht einmal versucht, den zu verwenden Datenbank (geben Sie nur nach Bedarf Scheindaten an). Wenn Sie beispielsweise versuchen, eine Authentifizierungslogik beim Bereitstellen einer Webseite zu testen, ist es wahrscheinlich eine gute Sache, diese vollständig von der Datenbank zu trennen (vorausgesetzt, Sie verlassen sich bei der Authentifizierung nicht auf die Datenbank oder auf diese Sie können es einigermaßen leicht verspotten).

Wenn es sich andererseits um eine Funktion handelt, die sich direkt auf Ihre Datenbank stützt und in einer realen Umgebung überhaupt nicht funktioniert, sollte die Datenbank nicht verfügbar sein, und Sie sollten sich darüber lustig machen, was die Datenbank in Ihrem DB-Client-Code tut (dh auf der Ebene, die diese verwendet) DB) macht nicht unbedingt Sinn.

Wenn Sie beispielsweise wissen, dass Ihre Anwendung auf eine Datenbank (und möglicherweise auf ein bestimmtes Datenbanksystem) angewiesen ist, ist es oft Zeitverschwendung, das Datenbankverhalten zu verspotten. Datenbank-Engines (insbesondere RDBMS) sind komplexe Systeme. Einige SQL-Zeilen können tatsächlich viel Arbeit verrichten, was schwierig zu simulieren ist (wenn Ihre SQL-Abfrage einige Zeilen lang ist, benötigen Sie wahrscheinlich viel mehr Java / PHP / C # / Python-Zeilen) Code, um intern dasselbe Ergebnis zu erzielen): Das Duplizieren der bereits in der Datenbank implementierten Logik ist nicht sinnvoll, und das Überprüfen des Testcodes würde dann zu einem Problem für sich.

Ich würde dies als ein Problem der nicht unbedingt behandeln Unit - Test vs integriert Test , sondern sehen den Umfang dessen , was getestet wird. Die allgemeinen Probleme beim Unit- und Integrationstest bleiben bestehen: Sie benötigen einen einigermaßen realistischen Satz von Testdaten und Testfällen, der jedoch auch so klein ist, dass die Tests schnell ausgeführt werden können.

Die Zeit zum Zurücksetzen der Datenbank und zum erneuten Auffüllen mit Testdaten ist ein zu berücksichtigender Aspekt. Sie würden dies im Allgemeinen anhand der Zeit bewerten, die erforderlich ist, um diesen Scheincode zu schreiben (den Sie schließlich auch pflegen müssten).

Ein weiterer zu berücksichtigender Punkt ist der Grad der Abhängigkeit Ihrer Anwendung von der Datenbank.

  • Wenn Ihre Anwendung einfach einem CRUD-Modell folgt, in dem Sie über eine Abstraktionsebene verfügen, mit der Sie mithilfe einer Konfigurationseinstellung zwischen beliebigen RDBMS wechseln können, können Sie möglicherweise ganz leicht mit einem Schein-System arbeiten (das möglicherweise verwischt) die Grenze zwischen Einheit und integriertem Testen unter Verwendung eines speicherinternen RDBMS).
  • Wenn Ihre Anwendung komplexere Logik verwendet, die beispielsweise für SQL Server, MySQL oder PostgreSQL spezifisch ist, ist es im Allgemeinen sinnvoller, einen Test zu haben, der dieses spezielle System verwendet.

"Viele Anwendungen würden heute einfach gar nicht funktionieren, wenn ihr Datenbankserver ausfallen würde" - das ist wirklich ein wichtiger Punkt!
el.pescado

Gute Erklärung der Grenzen komplexer Mocks, wie die Verwendung einer anderen Sprache zum Verspotten von SQL. Wenn der Testcode so kompliziert wird, dass er anscheinend selbst getestet werden muss, ist das ein QS-Geruch.
dodgethesteamroller

1

Sie halten einen solchen Komponententest zu Recht für unvollständig. Die Unvollständigkeit wird in der Datenbankschnittstelle verspottet. Solche naiven Scheinerwartungen oder Behauptungen sind unvollständig.

Um dies zu vervollständigen, müssten Sie genügend Zeit und Ressourcen für das Schreiben oder Integrieren einer SQL-Regelengine einplanen, die gewährleisten würde , dass die SQL-Anweisung, die vom Testobjekt ausgegeben wird, zu erwarteten Vorgängen führt.

Die häufig vergessene und etwas teure Alternative zum Verspotten ist jedoch die "Virtualisierung" .

Können Sie eine temporäre, speicherinterne, aber "echte" DB-Instanz zum Testen einer einzelnen Funktion starten? Ja ? Dort haben Sie einen besseren Test, der die tatsächlich gespeicherten und abgerufenen Daten überprüft.

Nun könnte man sagen, Sie haben einen Unit-Test in einen Integrationstest verwandelt. Es gibt unterschiedliche Ansichten darüber, wo die Grenze gezogen werden muss, um zwischen Komponententests und Integrationstests zu klassifizieren. IMHO ist "Einheit" eine willkürliche Definition und sollte Ihren Bedürfnissen entsprechen.


1
dies scheint nur Punkte gemacht und erklärt in wiederholen diesem Stand der Antwort , die mehrere Stunden geschrieben wurde
gnat

0

Unit Testsund Integration Testssind orthgonal zueinander. Sie bieten eine andere Sicht auf die Anwendung, die Sie erstellen. Normalerweise möchten Sie beides . Der Zeitpunkt ist jedoch unterschiedlich, wann Sie welche Art von Tests wünschen.

Am häufigsten möchten Sie Unit Tests. Unit-Tests konzentrieren sich auf einen kleinen Teil des zu testenden Codes - was genau als a bezeichnet unitwird, bleibt dem Leser überlassen. Das Ziel ist jedoch einfach: Sie erhalten schnelles Feedback, wann und wo Ihr Code kaputt gegangen ist . Das heißt, es sollte klar sein, dass Aufrufe an eine tatsächliche DB ein Nono sind .

Andererseits gibt es Dinge, die nur unter harten Bedingungen ohne Datenbank getestet werden können. Möglicherweise enthält Ihr Code eine Race-Bedingung , und ein Aufruf einer DB führt zu einer Verletzung von a, unique constraintdie nur ausgelöst werden kann, wenn Sie Ihr System tatsächlich verwenden. Aber solche Tests sind teuer, und Sie können (und wollen) sie nicht so oft ausführen unit tests.


0

In der .Net-Welt habe ich die Angewohnheit, ein Testprojekt und Tests als Methode zum Codieren / Debuggen / Testen von Roundtrips abzüglich der Benutzeroberfläche zu erstellen. Dies ist ein effizienter Weg für mich, mich zu entwickeln. Ich war nicht so sehr daran interessiert, alle Tests für jeden Build durchzuführen (da dies meinen Entwicklungsworkflow verlangsamt), aber ich verstehe, wie nützlich dies für ein größeres Team ist. Sie können jedoch die Regel festlegen, dass vor dem Festschreiben des Codes alle Tests ausgeführt werden und bestanden werden sollten (wenn die Ausführung der Tests länger dauert, da die Datenbank tatsächlich betroffen ist).

Das Verspotten der Datenzugriffsschicht (DAO) und das Nichttreffen auf der Datenbank ermöglicht es mir nicht nur, so zu codieren, wie ich es mag und wie ich es gewöhnt bin, sondern es fehlt auch ein großer Teil der eigentlichen Codebasis. Wenn Sie die Datenzugriffsebene und die Datenbank nicht wirklich testen und nur so tun, als würden Sie viel Zeit damit verbringen, Dinge zu verspotten, kann ich den Nutzen dieses Ansatzes nicht erfassen, um meinen Code tatsächlich zu testen. Ich teste ein kleines Stück anstelle eines größeren mit einem Test. Ich verstehe, dass mein Ansatz eher im Sinne eines Integrationstests ist, aber es scheint, dass der Unit-Test mit dem Mock eine überflüssige Zeitverschwendung ist, wenn Sie den Integrationstest tatsächlich nur einmal und zuerst schreiben. Es ist auch eine gute Möglichkeit zum Entwickeln und Debuggen.

Tatsächlich kenne ich TDD und Behaviour Driven Design (BDD) schon seit einiger Zeit und denke darüber nach, wie ich es verwenden kann, aber es ist schwierig, Komponententests rückwirkend hinzuzufügen. Vielleicht irre ich mich, aber das Schreiben eines Tests, der mehr Code von Ende zu Ende mit der enthaltenen Datenbank abdeckt, scheint ein viel vollständigerer Test mit höherer Priorität zu sein, der mehr Code abdeckt und eine effizientere Methode zum Schreiben von Tests darstellt.

Tatsächlich denke ich, dass so etwas wie Behavior Driven Design (BDD), das End-to-End-Tests mit domänenspezifischer Sprache (DSL) durchführt, der richtige Weg sein sollte. Wir haben SpecFlow in der .Net-Welt, aber es begann als Open Source mit Cucumber.

https://cucumber.io/

Ich bin einfach nicht wirklich beeindruckt von der wahren Nützlichkeit des Tests, den ich geschrieben habe, indem ich die Datenzugriffsebene verspottet habe und nicht auf die Datenbank zugegriffen habe. Das zurückgegebene Objekt hat die Datenbank nicht erreicht und wurde nicht mit Daten gefüllt. Es war ein völlig leeres Objekt, das ich auf unnatürliche Weise verspotten musste. Ich denke nur, es ist Zeitverschwendung.

Laut Stack Overflow wird Mocking verwendet, wenn es unpraktisch ist, reale Objekte in den Komponententest einzubeziehen.

https://stackoverflow.com/questions/2665812/what-is-mocking

"Mocking wird hauptsächlich beim Komponententest verwendet. Ein zu testendes Objekt kann Abhängigkeiten von anderen (komplexen) Objekten aufweisen. Um das Verhalten des zu testenden Objekts einzugrenzen, ersetzen Sie die anderen Objekte durch Mocks, die das Verhalten der realen Objekte simulieren. Dies ist nützlich, wenn es unpraktisch ist, die realen Objekte in den Komponententest einzubeziehen. "

Mein Argument ist, dass ich diesen Roundtrip-Ablauf testen werde, bevor ich als Entwickler etwas einchecke, wenn ich irgendetwas von Ende zu Ende codiere (Web-Benutzeroberfläche zu Business-Schicht zu Datenzugriffsschicht zu Datenbank, Roundtrip). Wenn ich die Benutzeroberfläche ausschneide und diesen Ablauf ausgehend von einem Test debugge und teste, teste ich alles außerhalb der Benutzeroberfläche und gebe genau das zurück, was die Benutzeroberfläche erwartet. Ich muss nur noch die Benutzeroberfläche senden, was sie will.

Ich habe einen vollständigeren Test, der Teil meines natürlichen Entwicklungs-Workflows ist. Für mich sollte dies der Test mit der höchsten Priorität sein, der das Testen der tatsächlichen Benutzerspezifikation von Ende zu Ende so weit wie möglich umfasst. Wenn ich noch nie einen detaillierteren Test erstellt habe, habe ich zumindest diesen vollständigen Test, der beweist, dass meine gewünschte Funktionalität funktioniert.

Ein Mitbegründer von Stack Exchange ist nicht überzeugt von den Vorteilen einer 100-prozentigen Testabdeckung. Ich bin es auch nicht. Ich würde einen vollständigeren "Integrationstest" machen, der die Datenbank überfordert, jeden Tag eine Reihe von Datenbank-Mocks zu warten.

https://www.joelonsoftware.com/2009/01/31/from-podcast-38/


es ist so offensichtlich, dass Sie den Unterschied zwischen
Unit-

Ich denke es kommt auf das Projekt an. Bei kleineren Projekten mit weniger Ressourcen, bei denen ein Entwickler aufgrund des Mangels an Testern und der Synchronisierung der Dokumentation mit dem Code insgesamt für Tests und Regressionstests verantwortlich ist, werden es diese sein, wenn ich Zeit damit verbringe, Tests zu schreiben gibt mir das meiste für mein Geld. Ich möchte so viele Vögel wie möglich mit einer Klappe schlagen. Wenn die meisten meiner Logik und Fehler von gespeicherten Datenbankprozeduren stammen, die Berichte generieren, oder vom Front-End-JavaScript, hilft es nicht viel, wenn die mittlere Ebene über vollständige Unit-Test-Abdeckung verfügt.
user3198764

-1

Externe Abhängigkeiten sollten verspottet werden, da Sie sie nicht kontrollieren können (sie können während der Integrationstestphase bestehen, aber in der Produktion scheitern). Laufwerke können ausfallen, Datenbankverbindungen können aus einer Reihe von Gründen ausfallen, es können Netzwerkprobleme auftreten usw. Integrationstests geben kein zusätzliches Vertrauen, da sie alle Probleme darstellen, die zur Laufzeit auftreten können.

Mit echten Komponententests testen Sie innerhalb der Grenzen der Sandbox und es sollte klar sein. Wenn ein Entwickler eine SQL-Abfrage geschrieben hat, die in QA / PROD fehlgeschlagen ist, bedeutet dies, dass er sie vorher nicht einmal getestet hat.


+1 für "Sie können sie nicht steuern (sie bestehen möglicherweise während der Integrationstestphase, schlagen jedoch in der Produktion fehl)" .
Tulains Córdova

Sie können sie in zufriedenstellendem Maße steuern.
el.pescado

Ich verstehe, aber ich denke, das war früher wahrer als heute? Mit Automatisierung und Tools (wie Docker) können Sie die Einrichtung all Ihrer Binär- / Server-Abhängigkeiten genau und zuverlässig für Integrationstestsuiten replizieren und wiederholen. Natürlich kann physische Hardware (und Dienste von Drittanbietern usw.) ausfallen.
mindplay.dk

5
Ich stimme absolut nicht zu. Sie sollten (zusätzliche) Integrationstests schreiben, da externe Abhängigkeiten fehlschlagen können. Externe Abhängigkeiten haben möglicherweise ihre eigenen Macken, die Sie höchstwahrscheinlich übersehen werden, wenn Sie sich über alles lustig machen.
Paul Kertscher

1
@PaulK überlegt immer noch, welche Antwort als akzeptiert zu markieren ist, aber ich neige zu dem gleichen Ergebnis.
mindplay.dk
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.