409 Stimmen

Was ist die beste Strategie für Unit-Tests von datenbankgestützten Anwendungen?

Ich arbeite mit einer Vielzahl von Webanwendungen, die von Datenbanken unterschiedlicher Komplexität im Backend gesteuert werden. Typischerweise gibt es eine ORM Schicht, die von der Geschäfts- und Präsentationslogik getrennt ist. Dies macht das Unit-Testing der Geschäftslogik recht einfach; die Dinge können in diskreten Modulen implementiert werden und alle für den Test benötigten Daten können durch Objekt-Mocking gefälscht werden.

Aber das Testen des ORM und der Datenbank selbst war schon immer mit Problemen und Kompromissen behaftet.

Im Laufe der Jahre habe ich einige Strategien ausprobiert, von denen mich keine wirklich zufrieden gestellt hat.

  • Laden Sie eine Testdatenbank mit bekannten Daten. Führen Sie Tests gegen den ORM durch und bestätigen Sie, dass die richtigen Daten zurückkommen. Der Nachteil dabei ist, dass Ihre Test-DB mit allen Schemaänderungen in der Anwendungsdatenbank Schritt halten muss und daher möglicherweise nicht mehr synchron ist. Außerdem ist sie auf künstliche Daten angewiesen und kann Fehler, die durch dumme Benutzereingaben entstehen, nicht aufdecken. Und schließlich, wenn die Testdatenbank klein ist, werden Ineffizienzen wie ein fehlender Index nicht aufgedeckt. (OK, der letzte Punkt ist nicht wirklich das, wofür Unit-Tests verwendet werden sollten, aber es kann nicht schaden).

  • Laden Sie eine Kopie der Produktionsdatenbank und testen Sie mit dieser. Das Problem dabei ist, dass Sie möglicherweise keine Ahnung haben, was sich zu einem bestimmten Zeitpunkt in der Produktions-DB befindet; Ihre Tests müssen möglicherweise neu geschrieben werden, wenn sich die Daten im Laufe der Zeit ändern.

Einige Leute haben darauf hingewiesen, dass diese beiden Strategien auf bestimmten Daten beruhen und dass ein Einheitstest nur die Funktionalität testen sollte. Zu diesem Zweck habe ich gesehen, vorgeschlagen:

  • Verwenden Sie einen Mock-Datenbankserver und überprüfen Sie nur, ob der ORM die richtigen Abfragen als Antwort auf einen bestimmten Methodenaufruf sendet.

Welche Strategien haben Sie für das Testen datenbankgestützter Anwendungen verwendet, wenn überhaupt? Was hat sich für Sie am besten bewährt?

172voto

Mark Roddy Punkte 25266

Ich habe Ihren ersten Ansatz mit einigem Erfolg angewandt, aber auf eine etwas andere Weise, die meiner Meinung nach einige Ihrer Probleme lösen würde:

  1. Bewahren Sie das gesamte Schema und die Skripte zu seiner Erstellung in der Versionskontrolle auf, damit jeder das aktuelle Datenbankschema nach einem Check-out erstellen kann. Bewahren Sie außerdem Beispieldaten in Datendateien auf, die im Rahmen des Build-Prozesses geladen werden. Wenn Sie Daten entdecken, die Fehler verursachen, fügen Sie diese zu Ihren Beispieldaten hinzu, um zu überprüfen, dass die Fehler nicht erneut auftreten.

  2. Verwenden Sie einen kontinuierlichen Integrationsserver, um das Datenbankschema zu erstellen, die Beispieldaten zu laden und Tests durchzuführen. Auf diese Weise halten wir unsere Testdatenbank synchron (und bauen sie bei jedem Testlauf neu auf). Obwohl dies voraussetzt, dass der CI-Server Zugriff auf seine eigene dedizierte Datenbankinstanz hat und diese auch besitzt, kann ich sagen, dass die dreimal tägliche Erstellung des Datenbankschemas erheblich dazu beigetragen hat, Fehler zu finden, die wahrscheinlich erst kurz vor der Auslieferung (wenn nicht sogar später) gefunden worden wären. Ich kann nicht sagen, dass ich das Schema vor jeder Übertragung neu aufbaue. Tut das jemand? Mit diesem Ansatz müssen Sie das nicht (vielleicht sollten wir es tun, aber es ist nicht schlimm, wenn jemand es vergisst).

  3. Für meine Gruppe erfolgt die Benutzereingabe auf der Anwendungsebene (nicht in der Datenbank), so dass dies über Standard-Unit-Tests getestet wird.

Laden der Produktionsdatenbankkopie:
Dies war der Ansatz, der bei meiner letzten Stelle angewandt wurde. Es war eine große Qual, weil es einige Probleme gab:

  1. Die Kopie würde gegenüber der Produktionsversion veraltet sein.
  2. Die Änderungen würden am Schema der Kopie vorgenommen und nicht an die Produktionssysteme weitergegeben werden. An diesem Punkt hätten wir divergierende Schemata. Das macht keinen Spaß.

Mocking Database Server:
Das machen wir auch bei meiner jetzigen Arbeit. Nach jedem Commit führen wir Unit-Tests gegen den Anwendungscode aus, die Mock-DB-Accessors injiziert haben. Dann führen wir dreimal am Tag den oben beschriebenen vollständigen DB-Build aus. Ich empfehle definitiv beide Ansätze.

61voto

Aaron Digulla Punkte 308693

Aus diesen Gründen führe ich immer Tests gegen eine In-Memory-DB (HSQLDB oder Derby) durch:

  • Es bringt Sie zum Nachdenken, welche Daten Sie in Ihrer Test-DB behalten sollten und warum. Wenn Sie Ihre Produktions-DB in ein Testsystem einbringen, bedeutet das: "Ich habe keine Ahnung, was ich tue oder warum, und wenn etwas kaputt geht, dann war ich es nicht" ;)
  • Es stellt sicher, dass die Datenbank mit geringem Aufwand an einem neuen Ort wiederhergestellt werden kann (z.B. wenn wir einen Fehler aus der Produktion replizieren müssen)
  • Das hilft enorm bei der Qualität der DDL-Dateien.

Die In-Memory-DB wird mit frischen Daten geladen, sobald die Tests beginnen, und nach den meisten Tests rufe ich ROLLBACK auf, um sie stabil zu halten. IMMER Halten Sie die Daten in der Test-DB stabil! Wenn sich die Daten ständig ändern, können Sie nicht testen.

Die Daten werden aus SQL, einer Vorlagen-DB oder einem Dump/Backup geladen. Ich bevorzuge Dumps, wenn sie in einem lesbaren Format vorliegen, weil ich sie in VCS einfügen kann. Wenn das nicht funktioniert, verwende ich eine CSV-Datei oder XML. Wenn ich riesige Datenmengen laden muss ... Ich tue es nicht. Sie müssen nie riesige Datenmengen laden :) Nicht für Unit-Tests. Leistungstests sind ein anderes Thema und es gelten andere Regeln.

15voto

Lukas Eder Punkte 194234

Auch wenn es Tools gibt, mit denen Sie Ihre Datenbank auf die eine oder andere Weise nachahmen können (z. B. jOOQ 's MockConnection die zu sehen ist in diese Antwort - Haftungsausschluss, ich arbeite für den Anbieter von jOOQ), würde ich raten ノット um größere Datenbanken mit komplexen Abfragen zu simulieren.

Selbst wenn Sie nur einen Integrationstest für Ihr ORM durchführen wollen, sollten Sie sich darüber im Klaren sein, dass ein ORM eine sehr komplexe Reihe von Abfragen an Ihre Datenbank stellt, die sich in

  • Syntax
  • Komplexität
  • bestellen (!)

All das zu spiegeln, um vernünftige Dummy-Daten zu erzeugen, ist ziemlich schwierig, es sei denn, man baut eine kleine Datenbank in seinem Mock auf, die die übermittelten SQL-Anweisungen interpretiert. Verwenden Sie daher eine bekannte Datenbank für Integrationstests, die Sie leicht mit bekannten Daten zurücksetzen können, gegen die Sie Ihre Integrationstests durchführen können.

14voto

kolrie Punkte 12202

Diese Frage stelle ich mir schon seit langem, aber ich glaube, dass es dafür kein Patentrezept gibt.

Was ich derzeit tue, ist Mocking die DAO-Objekte und halten eine im Speicher Darstellung einer guten Sammlung von Objekten, die interessante Fälle von Daten, die auf der Datenbank leben könnte darstellen.

Das Hauptproblem, das ich bei diesem Ansatz sehe, ist, dass man nur den Code abdeckt, der mit der DAO-Schicht interagiert, aber nie die DAO selbst testet, und meiner Erfahrung nach passieren viele Fehler auch in dieser Schicht. Ich führe auch ein paar Unit-Tests durch, die gegen die Datenbank laufen (um TDD oder schnelle lokale Tests zu ermöglichen), aber diese Tests werden nie auf meinem Continuous Integration Server ausgeführt, da wir keine Datenbank für diesen Zweck haben und ich denke, dass Tests, die auf dem CI-Server laufen, in sich geschlossen sein sollten.

Ein anderer Ansatz, den ich sehr interessant finde, der sich aber nicht immer lohnt, da er etwas zeitaufwändig ist, besteht darin, dasselbe Schema, das Sie für die Produktion verwenden, in einer eingebetteten Datenbank zu erstellen, die nur im Rahmen der Unit-Tests läuft.

Obwohl dieser Ansatz zweifelsohne die Abdeckung verbessert, gibt es einige Nachteile, da Sie so nah wie möglich an ANSI-SQL sein müssen, damit es sowohl mit Ihrem aktuellen DBMS als auch mit dem eingebetteten Ersatz funktioniert.

Unabhängig davon, was Sie für Ihren Code für relevanter halten, gibt es einige Projekte, die die Arbeit erleichtern, z. B. DbUnit .

5voto

Dave Sherohman Punkte 44017

Ich verwende die erste Variante (Ausführung des Codes gegen eine Testdatenbank). Das einzige wesentliche Problem, das Sie bei diesem Ansatz ansprechen, ist die Möglichkeit, dass Schemata nicht mehr synchronisiert werden. Ich gehe damit um, indem ich eine Versionsnummer in meiner Datenbank behalte und alle Schemaänderungen über ein Skript vornehme, das die Änderungen für jedes Versionsinkrement übernimmt.

Außerdem nehme ich alle Änderungen (einschließlich des Datenbankschemas) zuerst an meiner Testumgebung vor, so dass es am Ende umgekehrt ist: Nachdem alle Tests bestanden sind, übertrage ich die Schemaaktualisierungen auf den Produktionshost. Ich führe auch ein separates Paar von Test- und Anwendungsdatenbanken auf meinem Entwicklungssystem, damit ich dort überprüfen kann, ob das Datenbank-Upgrade ordnungsgemäß funktioniert, bevor ich die echte(n) Produktionsumgebung(en) berühre.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X