Ich arbeite mit vielen Webanwendungen, die von Datenbanken unterschiedlicher Komplexität im Backend gesteuert werden. In der Regel gibt es eine ORM- Schicht, die von der Geschäfts- und Präsentationslogik getrennt ist. Dies macht das Unit-Testen der Geschäftslogik ziemlich einfach; Dinge können in diskreten Modulen implementiert werden und alle Daten, die für den Test benötigt werden, können durch Objektverspottung gefälscht werden.
Das Testen des ORM und der Datenbank selbst war jedoch immer mit Problemen und Kompromissen behaftet.
Im Laufe der Jahre habe ich einige Strategien ausprobiert, von denen mich keine vollständig zufriedenstellte.
Laden Sie eine Testdatenbank mit bekannten Daten. Führen Sie Tests gegen das ORM durch und bestätigen Sie, dass die richtigen Daten zurückkommen. Der Nachteil hierbei ist, dass Ihre Test-DB mit allen Schemaänderungen in der Anwendungsdatenbank Schritt halten muss und möglicherweise nicht mehr synchron ist. Es basiert auch auf künstlichen Daten und deckt möglicherweise keine Fehler auf, die aufgrund dummer Benutzereingaben auftreten. Wenn die Testdatenbank klein ist, werden keine Ineffizienzen wie ein fehlender Index angezeigt. (OK, das letzte ist nicht wirklich das, wofür Unit-Tests verwendet werden sollten, aber es tut nicht weh.)
Laden Sie eine Kopie der Produktionsdatenbank und testen Sie diese. Das Problem hierbei ist, dass Sie möglicherweise zu keinem Zeitpunkt eine Ahnung haben, was sich in der Produktionsdatenbank 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 beide Strategien auf bestimmten Daten beruhen und ein Komponententest nur die Funktionalität testen sollte. Zu diesem Zweck habe ich vorgeschlagen gesehen:
- Verwenden Sie einen nachgebildeten Datenbankserver und überprüfen Sie nur, ob der ORM die richtigen Abfragen als Antwort auf einen bestimmten Methodenaufruf sendet.
Welche Strategien haben Sie zum Testen datenbankgesteuerter Anwendungen verwendet, falls vorhanden? Was hat bei Ihnen am besten funktioniert?
quelle
Antworten:
Ich habe Ihren ersten Ansatz tatsächlich mit einigem Erfolg verwendet, aber auf eine etwas andere Art und Weise, die meiner Meinung nach einige Ihrer Probleme lösen würde:
Behalten Sie das gesamte Schema und die Skripte zum Erstellen in der Quellcodeverwaltung bei, damit jeder nach dem Auschecken das aktuelle Datenbankschema erstellen kann. Bewahren Sie außerdem Beispieldaten in Datendateien auf, die von einem Teil des Erstellungsprozesses geladen werden. Wenn Sie Daten entdecken, die Fehler verursachen, fügen Sie sie Ihren Beispieldaten hinzu, um zu überprüfen, ob Fehler nicht erneut auftreten.
Verwenden Sie einen Continuous Integration Server, um das Datenbankschema zu erstellen, die Beispieldaten zu laden und Tests auszuführen. Auf diese Weise halten wir unsere Testdatenbank synchron (erstellen sie bei jedem Testlauf neu). Obwohl dies erfordert, dass der CI-Server Zugriff und Besitz seiner eigenen dedizierten Datenbankinstanz hat, hat die dreimalige Erstellung unseres Datenbankschemas dazu beigetragen, Fehler zu finden, die wahrscheinlich erst kurz vor der Auslieferung (wenn nicht später) gefunden worden wären ). Ich kann nicht sagen, dass ich das Schema vor jedem Commit neu erstelle. Hat jemand? Mit diesem Ansatz müssen Sie nicht (na ja, vielleicht sollten wir, aber es ist keine große Sache, wenn jemand vergisst).
Für meine Gruppe erfolgt die Benutzereingabe auf Anwendungsebene (nicht db), sodass dies über Standard-Unit-Tests getestet wird.
Laden der Produktionsdatenbankkopie:
Dies war der Ansatz, der bei meinem letzten Job verwendet wurde. Es war eine große Schmerzursache für ein paar Probleme:
Verspottender Datenbankserver:
Wir machen das auch bei meinem aktuellen Job. Nach jedem Commit führen wir Unit-Tests für den Anwendungscode durch, in den Schein-DB-Accessoren injiziert wurden. Dann führen wir dreimal am Tag den oben beschriebenen vollständigen Datenbank-Build aus. Ich empfehle definitiv beide Ansätze.
quelle
Ich führe aus folgenden Gründen immer Tests gegen eine In-Memory-Datenbank (HSQLDB oder Derby) durch:
Die In-Memory-Datenbank wird nach dem Start der Tests mit neuen Daten geladen. Nach den meisten Tests rufe ich ROLLBACK auf, um sie stabil zu halten. IMMERHalten 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, da ich sie in VCS einfügen kann. Wenn das nicht funktioniert, verwende ich eine CSV-Datei oder XML. Wenn ich enorme Datenmengen laden muss ... tue ich nicht. Sie müssen nie enorme Datenmengen laden :) Nicht für Unit-Tests. Leistungstests sind ein weiteres Problem, und es gelten andere Regeln.
quelle
Ich habe diese Frage schon lange gestellt, aber ich denke, dafür gibt es keine Silberkugel.
Was ich derzeit mache, ist das Verspotten der DAO-Objekte und das Speichern einer guten Sammlung von Objekten im Speicher, die interessante Fälle von Daten darstellen, die in der Datenbank leben könnten.
Das Hauptproblem, das ich bei diesem Ansatz sehe, besteht darin, dass Sie nur den Code abdecken, der mit Ihrer DAO-Ebene interagiert, aber niemals das DAO selbst testen, und meiner Erfahrung nach treten auch auf dieser Ebene viele Fehler auf. Ich führe auch einige Komponententests durch, die für die Datenbank ausgeführt werden (um TDD zu verwenden oder schnell lokal zu testen), aber diese Tests werden niemals auf meinem Server für kontinuierliche Integration ausgeführt, da wir zu diesem Zweck keine Datenbank führen und ich Denken Sie, dass Tests, die auf dem CI-Server ausgeführt werden, in sich geschlossen sein sollten.
Ein anderer Ansatz, den ich sehr interessant finde, der sich jedoch nicht immer lohnt, da er etwas zeitaufwändig ist, besteht darin, dasselbe Schema zu erstellen, das Sie für die Produktion in einer eingebetteten Datenbank verwenden, die nur innerhalb des Komponententests ausgeführt wird.
Obwohl es keine Frage gibt, dass dieser Ansatz Ihre 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 Ihrer Meinung nach für Ihren Code relevanter ist, gibt es einige Projekte, die es möglicherweise einfacher machen, wie DbUnit .
quelle
Auch wenn es Tools, die es Ihnen ermöglichen , Ihre Datenbank in eine oder andere Weise zu verspotten (zB jOOQ ‚s
MockConnection
, die in zu sehen ist diese Antwort - Disclaimer, die ich für jOOQ des Verkäufers arbeiten), würde ich raten , nicht größere Datenbanken mit komplexen zu verspotten Anfragen.Auch wenn Sie Ihren ORM nur auf Integrationstest testen möchten, achten Sie darauf, dass ein ORM eine sehr komplexe Reihe von Abfragen an Ihre Datenbank ausgibt, die sich darin unterscheiden können
Das alles zu verspotten, um vernünftige Dummy-Daten zu erzeugen, ist ziemlich schwierig, es sei denn, Sie erstellen tatsächlich eine kleine Datenbank in Ihrem Mock, die die übertragenen SQL-Anweisungen interpretiert. Verwenden Sie jedoch eine bekannte Integrationstest-Datenbank, die Sie problemlos mit bekannten Daten zurücksetzen können, anhand derer Sie Ihre Integrationstests ausführen können.
quelle
Ich benutze die erste (Ausführen des Codes gegen eine Testdatenbank). Das einzige wesentliche Problem, das Sie bei diesem Ansatz ansprechen, ist die Möglichkeit, dass Schemas nicht mehr synchron sind. Ich behebe dies, indem ich eine Versionsnummer in meiner Datenbank behalte und alle Schemaänderungen über ein Skript vornehme, das die Änderungen für jedes Versionsinkrement anwendet.
Ich nehme auch zuerst alle Änderungen (einschließlich des Datenbankschemas) an meiner Testumgebung vor, sodass es umgekehrt ist: Wenden Sie nach Abschluss aller Tests die Schemaaktualisierungen auf den Produktionshost an. Ich habe 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 realen Produktionsboxen berühre.
quelle
Ich verwende den ersten Ansatz, aber etwas anders, um die von Ihnen genannten Probleme anzugehen.
Alles, was zum Ausführen von Tests für DAOs benötigt wird, befindet sich in der Quellcodeverwaltung. Es enthält ein Schema und Skripte zum Erstellen der Datenbank (Docker ist dafür sehr gut geeignet). Wenn die eingebettete Datenbank verwendet werden kann, verwende ich sie aus Gründen der Geschwindigkeit.
Der wichtige Unterschied zu den anderen beschriebenen Ansätzen besteht darin, dass die für den Test erforderlichen Daten nicht aus SQL-Skripten oder XML-Dateien geladen werden. Alles (mit Ausnahme einiger Wörterbuchdaten, die effektiv konstant sind) wird von der Anwendung mithilfe von Dienstprogrammfunktionen / -klassen erstellt.
Der Hauptzweck besteht darin, Daten für Tests zu verwenden
Dies bedeutet im Grunde, dass diese Dienstprogramme es ermöglichen, nur Dinge, die für den Test im Test selbst wesentlich sind, deklarativ anzugeben und irrelevante Dinge wegzulassen.
Um eine Vorstellung davon zu bekommen, was es in der Praxis bedeutet, betrachten Sie den Test für ein DAO, das mit
Comment
s bisPost
s arbeitet, die von geschrieben wurdenAuthors
. Um CRUD-Operationen für ein solches DAO zu testen, sollten einige Daten in der Datenbank erstellt werden. Der Test würde so aussehen:Dies hat mehrere Vorteile gegenüber SQL-Skripten oder XML-Dateien mit Testdaten:
Rollback gegen Commit
Ich finde es bequemer, dass Tests festgeschrieben werden, wenn sie ausgeführt werden. Erstens einige Effekte (zum Beispiel
DEFERRED CONSTRAINTS
) nicht überprüft werden, wenn ein Commit nie stattfindet. Zweitens können die Daten, wenn ein Test fehlschlägt, in der Datenbank überprüft werden, da sie durch das Rollback nicht zurückgesetzt werden.Dies hat natürlich den Nachteil, dass der Test zu fehlerhaften Daten führen kann und dies zu Fehlern bei anderen Tests führt. Um damit fertig zu werden, versuche ich die Tests zu isolieren. Im obigen Beispiel kann jeder Test neue erstellen
Author
und alle anderen Entitäten werden im Zusammenhang damit erstellt, sodass Kollisionen selten sind. Um mit den verbleibenden Invarianten umzugehen, die möglicherweise beschädigt werden können, aber nicht als Einschränkung auf DB-Ebene ausgedrückt werden können, verwende ich einige programmatische Überprüfungen auf fehlerhafte Bedingungen, die nach jedem einzelnen Test ausgeführt werden können (und die in CI ausgeführt werden, aber normalerweise aus Leistungsgründen lokal ausgeschaltet sind Gründe dafür).quelle
PostBuilder.post()
. Es werden einige Werte für alle obligatorischen Attribute des Beitrags generiert. Dies wird im Produktionscode nicht benötigt.Für JDBC-basierte Projekte (direkt oder indirekt, z. B. JPA, EJB, ...) können Sie nicht die gesamte Datenbank modellieren (in diesem Fall ist es besser, eine Test-Datenbank auf einem echten RDBMS zu verwenden), sondern nur ein Modell auf JDBC-Ebene .
Vorteil ist die Abstraktion, die auf diese Weise entsteht, da JDBC-Daten (Ergebnismenge, Aktualisierungsanzahl, Warnung, ...) unabhängig vom Backend gleich sind: Ihre Produktdatenbank, eine Testdatenbank oder nur einige Modelldaten, die für jeden Test bereitgestellt werden Fall.
Wenn die JDBC-Verbindung für jeden Fall nachgebildet ist, muss die Test-Datenbank nicht verwaltet werden (Bereinigung, jeweils nur ein Test, Nachladen von Fixtures, ...). Jede Modellverbindung ist isoliert und muss nicht bereinigt werden. In jedem Testfall werden nur minimal erforderliche Vorrichtungen bereitgestellt, um den JDBC-Austausch zu verspotten, wodurch die Komplexität der Verwaltung einer gesamten Testdatenbank vermieden wird.
Acolyte ist mein Framework, das einen JDBC-Treiber und ein Dienstprogramm für diese Art von Modell enthält: http://acolyte.eu.org .
quelle