it-swarm.com.de

Sind (Datenbank-) Integrationstests schlecht?

Einige Leute behaupten, dass Integrationstests alle Arten von schlecht und falsch sind - alles muss Unit-getestet werden, was bedeutet, dass Sie Abhängigkeiten verspotten müssen; 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 beweisen, dass dieses Repository tatsächlich Produkte finden kann, die mit verschiedenen angegebenen Schlüsselwörtern übereinstimmen.

Wie kann ich ohne Integrationstests mit einem realen Verbindungsobjekt wissen, dass dies tatsächlich echte Abfragen generiert - 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 Dinge wie "es generiert die erwartete Abfrage" beweisen - aber das bedeutet nicht, dass es tatsächlich funktionieren wird funktionieren .. Das heißt, vielleicht generiert es die Abfrage, die ich erwartet habe, aber vielleicht macht diese Abfrage nicht das, was ich denke.

Mit anderen Worten, ich denke, ein Test, der Aussagen über die generierte Abfrage macht, ist im Wesentlichen wertlos, weil er testet, wie die findByKeyword() -Methode implementiert war, aber das tut es nicht beweise nicht, dass es tatsächlich funktioniert.

Dieses Problem ist nicht auf Repositorys oder die Datenbankintegration beschränkt - es scheint in vielen Fällen zuzutreffen, in denen Aussagen über die Verwendung eines Mocks (Test-Double) nur beweisen, wie die Dinge implementiert werden, nicht, ob sie funktionieren tatsächlich 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 eines Repositorys), dessen einziger Zweck darin besteht Um mit einer anderen Komponente zu interagieren, wie können Sie wirklich etwas testen, ohne Integrationstests durchzuführen?

128
mindplay.dk

Ihr Mitarbeiter 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 Art, über das Testen nachzudenken, ist eine Testpyramide. Es ist ein Konzept, das häufig mit Agile in Verbindung gebracht wird, und viele haben darüber geschrieben, darunter Martin Fowler (der es Mike Cohn in Erfolg mit Agile zuschreibt), Alistair Scott und das Google Testing Blog .

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

Die Idee ist, dass schnell laufende, belastbare Komponententests die Grundlage des Testprozesses bilden - es sollte fokussiertere Komponententests als System-/Integrationstests und mehr System-/Integrationstests als End-to-End-Tests geben. Wenn Sie sich der Spitze nähern, benötigen Tests in der Regel mehr Zeit/Ressourcen, sind eher spröde und schuppiger und sind weniger spezifisch bei der Identifizierung des Systems oder der Datei, die defekt sind =; Natürlich ist es vorzuziehen, nicht "kopflastig" zu sein.

Bis zu diesem Punkt sind Integrationstests nicht schlecht, aber eine starke Abhängigkeit von ihnen kann darauf hinweisen, dass Sie Ihre einzelnen Komponenten nicht so konzipiert haben, dass sie einfach zu testen sind. Denken Sie daran, das Ziel hier ist um zu testen, ob Ihr Gerät seine Spezifikationen erfüllt, während ein Minimum an anderen zerbrechlichen Systemen beteiligt ist: Möglicherweise möchten Sie eine In-Memory-Datenbank ausprobieren (die ich als zähle Unit-Test-freundlicher Test (Double neben Mocks) zum Beispiel für schwere Edge-Case-Tests und anschließend 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, einfach test wie etwas implementiert wird, nicht ob es funktioniert. Das ist so etwas wie ein Antimuster: 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 Abstraktions- oder Realismusstufe.

134
Jeff Bowman

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

Das ist ein bisschen so, als würde man sagen, dass Antibiotika schlecht sind - alles sollte mit Vitaminen geheilt werden.

Unit-Tests können nicht fangen alles - sie testen nur, wie eine Komponente funktioniert in einer kontrollierten Umgebung. Integrationstests bestätigen, dass alles funktioniert zusammen, was schwieriger zu tun ist, aber am Ende aussagekräftiger.

Ein guter, umfassender Testprozess verwendet beide Arten von Tests - Komponententests, um Geschäftsregeln und andere Dinge zu überprüfen, die unabhängig getestet werden können, und Integrationstests, um sicherzustellen, dass alles zusammenarbeitet.

Wie kann ich ohne Integrationstests mit einem realen Verbindungsobjekt wissen, dass dies tatsächlich echte Abfragen generiert - und dass diese Abfragen tatsächlich das tun, was ich denke, dass sie tun?

Sie könnten Unit-Test auf Datenbankebene. Führen Sie die Abfrage mit verschiedenen Parametern aus und prüfen Sie, ob Sie die erwarteten Ergebnisse erhalten. Zugegeben, es bedeutet, alle Änderungen wieder in den "wahren" Code zu kopieren/einzufügen. aber es tut ermöglicht es Ihnen, die Abfrage unabhängig von anderen Abhängigkeiten zu testen.

89
D Stanley

Unit-Tests erfassen nicht alle Mängel. Im Vergleich zu anderen Arten von Tests sind sie jedoch billiger einzurichten und (erneut) auszuführen. 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.

enter image description here

quelle: S.470 in Code Complete 2 von McConnell

17
Nick Alexeev

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

nit Tests

Unit-Tests sollten auf dem Build-Server und lokal ausgeführt werden, nachdem der Code kompiliert wurde. Wenn Unit-Tests fehlschlagen, sollte der Build fehlschlagen oder die Code-Aktualisierung erst festgeschrieben werden, wenn die Tests behoben sind. Der Grund, warum Unit-Tests isoliert werden sollen, besteht darin, dass der Build-Server alle Tests ohne alle Abhängigkeiten ausführen kann. Dann könnten wir den Build ohne alle erforderlichen komplexen Abhängigkeiten ausführen und viele Tests durchführen, die sehr schnell ausgeführt werden.

Für eine Datenbank sollte man also Folgendes haben:

IRespository

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

Jetzt wird die eigentliche Implementierung von IRepository in die Datenbank gehen, um die Produkte zu erhalten, aber für Unit-Tests 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 jede Geschäftslogik mit den verspotteten Daten testen.

Integrationstests

Integrationstests sind typischerweise Grenzüberschreitungstests. Wir möchten diese Tests auf dem Bereitstellungsserver (der realen Umgebung), der Sandbox oder sogar lokal (auf Sandbox verwiesen) ausführen. Sie werden nicht auf dem Build-Server ausgeführt. Nachdem die Software in der Umgebung bereitgestellt wurde, werden diese normalerweise als Aktivität nach der Bereitstellung ausgeführt. Sie können über Befehlszeilenprogramme automatisiert werden. Zum Beispiel können wir nUnit über die Befehlszeile ausführen, wenn wir alle Integrationstests kategorisieren, die wir aufrufen möchten. Diese rufen tatsächlich das reale Repository mit dem realen Datenbankaufruf auf. Diese Art von Tests hilft 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 abreißen müssen. Erwägen Sie das Hinzufügen eines Produkts. Wir möchten das Produkt wahrscheinlich hinzufügen, es abfragen, um festzustellen, ob es hinzugefügt wurde, und es dann entfernen, nachdem wir fertig sind. Wir möchten nicht 100 oder 1000 "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 aus.
  • Führen Sie die Integrationstests für jede Bereitstellung aus.
14
Jon Raynor

Datenbankintegrationstests sind nicht schlecht. Noch mehr sind sie notwendig.

Sie haben Ihre Anwendung wahrscheinlich in Ebenen aufgeteilt, und das ist gut so. Sie können jede Ebene einzeln testen, indem Sie benachbarte Ebenen verspotten, und das ist auch gut so. Unabhängig davon, wie viele Abstraktionsschichten Sie erstellen, muss es irgendwann eine Schicht geben, die die Drecksarbeit erledigt - tatsächlich mit der Datenbank sprechen. Wenn Sie es nicht testen, nicht testen Sie überhaupt. Wenn Sie die Ebene n durch Verspotten der Ebene n-1 testen, bewerten Sie die Annahme, dass die Ebene n funktioniert nter der Bedingung, dass Ebene n-1 funktioniert. Damit dies funktioniert, müssen Sie irgendwie beweisen, dass Layer 0 funktioniert.

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

Fazit

Welches Vertrauen entsteht durch Unit-Tests Ihres Abstract Repository, Ethereal Object-Relational-Mapper, Generic Active Record, Theoretic Persistence Ebenen, wann enthält Ihr generiertes SQL am Ende einen Syntaxfehler?

12
el.pescado

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

Viele Anwendungen würden heute einfach überhaupt nicht funktionieren, wenn ihr Datenbankserver ausfallen würde. Denken Sie zumindest an die Funktion, die Sie testen möchten.

Wenn einerseits das, was Sie testen möchten, nicht von der Datenbank abhängt oder überhaupt nicht davon abhängen kann, schreiben Sie Ihren Test so, dass er nicht einmal versucht, das zu verwenden Datenbank (nur nach Bedarf Scheindaten bereitstellen). Wenn Sie beispielsweise versuchen, beim Bereitstellen einer Webseite eine Authentifizierungslogik zu testen (z. B.), 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 ziemlich leicht verspotten).

Wenn es sich jedoch um eine Funktion handelt, die sich direkt auf Ihre Datenbank stützt und in einer realen Umgebung überhaupt nicht funktioniert, wenn die Datenbank nicht verfügbar ist, verspotten Sie, was die Datenbank in Ihrem DB-Clientcode tut (dh die Ebene, die diese verwendet DB) macht nicht unbedingt Sinn.

Wenn Sie beispielsweise wissen, dass Ihre Anwendung auf einer Datenbank (und möglicherweise auf einem bestimmten Datenbanksystem) basiert, ist es häufig Zeitverschwendung, das Datenbankverhalten zu verspotten. Datenbankmodule (insbesondere RDBMS) sind komplexe Systeme. Ein paar Zeilen SQL können tatsächlich eine Menge Arbeit leisten, die schwer zu simulieren wäre (wenn Ihre SQL-Abfrage einige Zeilen lang ist, benötigen Sie wahrscheinlich viel mehr Zeilen Java/PHP/C #/Python Code, um intern dasselbe Ergebnis zu erzielen): Das Duplizieren der Logik, die Sie bereits in der Datenbank implementiert haben, ist nicht sinnvoll, und das Überprüfen dieses Testcodes würde dann zu einem Problem für sich.

Ich würde dies nicht unbedingt als Problem von Unit Test v.s. integrierter Test , aber schauen Sie sich den Umfang der Tests an. Die allgemeinen Probleme beim Testen von Einheiten und Integrationen 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 zum Schreiben dieses Scheincodes benötigt wird (den Sie eventuell 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, bei dem Sie über eine Abstraktionsebene verfügen, mit der Sie mithilfe einer Konfigurationseinstellung zwischen RDBMS wechseln können, können Sie wahrscheinlich problemlos mit einem Schein-System arbeiten (möglicherweise verschwimmen) die Linie zwischen Einheit und integriertem Testen unter Verwendung eines speicherinternen RDBMS).
  • Wenn Ihre Anwendung eine komplexere Logik verwendet, die spezifisch für SQL Server, MySQL oder PostgreSQL ist (z. B.), ist es im Allgemeinen sinnvoller, einen Test durchzuführen, der dieses spezifische System verwendet.
7
Bruno

Du brauchst beides.

Wenn Sie in Ihrem Beispiel getestet haben, dass eine Datenbank unter bestimmten Bedingungen ausgeführt wird, erhalten Sie beim Ausführen der Methode findByKeyword die Daten zurück, von denen Sie erwarten, dass dies ein guter Integrationstest ist.

In jedem anderen Code, der diese findByKeyword -Methode verwendet, möchten Sie steuern, was in den Test eingegeben 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 Sie wissen genau, was Ihr Test erhalten wird (und Sie verlieren den Aufwand, eine Verbindung zu einer Datenbank herzustellen und sicherzustellen, dass die darin enthaltenen Daten korrekt sind).

6
Froome

Sie können einen solchen Komponententest zu Recht als unvollständig betrachten. Die Unvollständigkeit wird in der Datenbankschnittstelle verspottet. Die Erwartungen oder Behauptungen eines solchen naiven Mocks sind unvollständig.

Um dies zu vervollständigen, müssten Sie genügend Zeit und Ressourcen sparen, um eine SQL-Regelengine zu schreiben oder zu integrieren, die garantiert , dass die SQL-Anweisung von ausgegeben wird Testobjekt würde zu erwarteten Operationen führen.

Die oft vergessene und etwas teure Alternative/Begleitung zum Verspotten ist jedoch "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.

Man könnte sagen, Sie haben aus einem Komponententest einen Integrationstest gemacht. Es gibt unterschiedliche Ansichten darüber, wo die Grenze zwischen Unit-Tests und Integrationstests gezogen werden soll. IMHO ist "Einheit" eine willkürliche Definition und sollte Ihren Bedürfnissen entsprechen.

1
S.D.

In der .Net-Welt habe ich die Angewohnheit, ein Testprojekt zu erstellen 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 interessiert daran, alle Tests für jeden Build auszuführen (weil 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 von Code alle Tests ausgeführt und bestanden werden sollten (wenn die Ausführung der Tests länger dauert, da die Datenbank tatsächlich getroffen wird).

Durch das Verspotten der Datenzugriffsschicht (DAO) und das Nicht-Aufrufen der Datenbank kann ich nicht nur nicht so codieren, wie ich es gewohnt bin und mich daran gewöhnt habe, sondern es fehlt auch ein großer Teil der tatsächlichen Codebasis. Wenn Sie die Datenzugriffsschicht und die Datenbank nicht wirklich testen und nur so tun, als ob Sie etwas Zeit damit verbringen, Dinge zu verspotten, kann ich die Nützlichkeit dieses Ansatzes zum tatsächlichen Testen meines Codes nicht verstehen. Ich teste ein kleines Stück anstelle eines größeren mit einem Test. Ich verstehe, dass mein Ansatz eher einem Integrationstest entspricht, aber es scheint, dass der Komponententest mit dem Mock eine überflüssige Zeitverschwendung ist, wenn Sie den Integrationstest tatsächlich nur einmal und zuerst schreiben. Dies 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, Unit-Tests 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 Behaviour Driven Design (BDD), das versucht, End-to-End mit domänenspezifischer Sprache (DSL) zu testen, 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, um die Datenzugriffsschicht zu verspotten und nicht auf die Datenbank zuzugreifen. 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, dass es Zeitverschwendung ist.

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

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

"Mocking wird hauptsächlich beim Testen von Einheiten verwendet. Ein zu testendes Objekt kann Abhängigkeiten von anderen (komplexen) Objekten aufweisen. Um das Verhalten des zu testenden Objekts zu isolieren, 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, wenn ich irgendetwas Ende-zu-Ende codiere (Web-Benutzeroberfläche zu Business-Schicht zu Datenzugriffsschicht zu Datenbank, Roundtrip), bevor ich als Entwickler etwas einchecke. Wenn ich die Benutzeroberfläche ausschneide und diesen Ablauf ausgehend von einem Test debugge und teste, teste ich alles außer 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 Entwicklungsworkflows 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 abdeckt. Wenn ich nie mehr detailliertere Tests erstelle, habe ich zumindest diesen einen vollständigeren Test, der beweist, dass meine gewünschte Funktionalität funktioniert.

Ein Mitbegründer von Stack Exchange ist nicht überzeugt von den Vorteilen einer 100% igen Abdeckung durch Unit-Tests. Ich bin es auch nicht. Ich würde einen vollständigeren "Integrationstest" durchführen, der die Datenbank über die Aufrechterhaltung einer Reihe von Datenbank-Mocks jeden Tag trifft.

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

0
user3198764

Unit Tests und Integration Tests sind orthgonal zueinander. Sie bieten eine andere Ansicht der Anwendung, die Sie erstellen. Normalerweise du willst beides. Der Zeitpunkt ist jedoch unterschiedlich, wenn 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 unit bezeichnet wird, bleibt dem Leser überlassen. Aber der Zweck ist einfach: schnell Feedback von wann und wo Ihr Code kaputt. Es sollte jedoch klar sein, dass Aufrufe einer tatsächlichen Datenbank ein nono sind.

Andererseits gibt es Dinge, die nur unter schwierigen Bedingungen ohne Datenbank Unit Tested sein können. Vielleicht gibt es eine Race-Bedingung in Ihrem Code und ein Aufruf einer DB wirft eine Verletzung eines unique constraint, die nur ausgelöst werden können, wenn Sie Ihr System tatsächlich verwenden. Aber diese Art von Tests sind teuer Sie können (und wollen) sie nicht so oft ausführen wie unit tests.

0
Thomas Junk