Frage

Ich arbeite mit einer Menge von Web-Anwendungen, die von Datenbanken unterschiedliche Komplexität auf dem Backend angetrieben werden. Typischerweise gibt es eine ORM Schicht getrennt von der Geschäfts- und Präsentationslogik. Dies macht Komponententests die Geschäftslogik ziemlich einfach; Dinge können für den Test durch das Objekt spöttischen gefälscht werden können benötigt in diskreten Modulen und alle Daten implementiert werden.

Aber die Prüfung der ORM und Datenbank selbst hat immer mit Problemen und Kompromissen behaftet sind.

Im Laufe der Jahre habe ich einige Strategien ausprobiert, von denen keines mir völlig zufrieden.

  • Legen Sie eine Testdatenbank mit bekannten Daten. Führen Sie Tests gegen die ORM und bestätigen, dass die richtigen Daten zurückkommt. Der Nachteil hierbei ist, dass Ihr Test DB mit allen Schemaänderungen in der Anwendungsdatenbank zu halten hat, und könnte nicht mehr synchronisiert ist. Es stützt sich auch auf künstliche Daten und nicht aussetzen Fehler, die aufgrund dumm Benutzereingaben auftreten. Schließlich, wenn die Testdatenbank klein ist, wird es nicht Ineffizienzen wie ein fehlender Index offenbart. (OK, das letzte ist nicht wirklich das, was Unit-Tests für verwendet werden sollte, aber es tut nicht weh.)

  • Legen Sie eine Kopie der Produktionsdatenbank und Test dagegen. Das Problem hierbei ist, dass Sie keine Ahnung haben, was zu einem bestimmten Zeitpunkt in der Produktion DB ist; Ihre Tests benötigen, wenn Datenänderungen im Laufe der Zeit neu geschrieben werden.

Einige Leute haben darauf hingewiesen, dass diese beiden Strategien auf bestimmte Daten verlassen und eine Einheit Test sollte nur Funktionalität testen. Zu diesem Zweck habe ich gesehen vorgeschlagen:

  • Verwenden Sie einen Mock-Datenbankserver, und überprüft nur, dass die ORM die korrekten Abfragen in Reaktion auf ein bestimmtes Methodenaufruf sendet.

Welche Strategien Sie zum Testen von Datenbank-gestützten Anwendungen verwendet haben, wenn überhaupt? Was das Beste für Sie gearbeitet?

War es hilfreich?

Lösung

Ich habe tatsächlich verwendete Ihren ersten Ansatz mit einigem Erfolg, aber auf eine etwas anderen Art und Weise, dass ich denken würde, einige Ihrer Probleme lösen:

  1. Halten Sie das gesamte Schema und Skripte für die es in der Quellcodeverwaltung zu schaffen, so dass jeder das aktuelle Datenbankschema nach einem Check out erstellen kann. Darüber hinaus muß die Beispieldaten in Datendateien, die von einem Teil des Build-Prozesses geladen werden. Wie Sie Daten entdecken, die Fehler, fügen Sie es Ihre Beispieldaten verursacht zu überprüfen, dass Fehler nicht wieder auftauchen.

  2. Verwenden Sie eine kontinuierliche Integration Server das Datenbankschema, laden Sie die Beispieldaten und führen Tests zu bauen. Dies ist, wie wir unsere Testdatenbank halten synchron (es bei jedem Testlauf Wiederaufbau). Obwohl dies erfordert, dass der CI-Server-Zugriff und das Eigentum an seinen eigenen dedizierten Datenbankinstanz haben, sage ich, dass unser db Schema gebaut zu haben 3 mal am Tag geholfen hat sich dramatisch Fehler zu finden, würden wahrscheinlich erst kurz vor der Auslieferung gefunden wurden (wenn nicht später ). Ich kann nicht sagen, dass ich das Schema neu erstellen vor jedem begehen. Hat jemand? Mit diesem Ansatz haben Sie nicht (gut vielleicht sollten wir, aber es ist keine große Sache, wenn jemand vergisst).

  3. Für meine Fraktion ist eine Benutzereingabe auf der Anwendungsebene durchgeführt (nicht db), so wird dies über Standard-Unit-Tests getestet.

Laden Produktion Datenbankkopie:
Dies war der Ansatz, der bei meinem letzten Job verwendet wurde. Es war eine große Schmerz Ursache für ein paar Probleme:

  1. Die Kopie würde veraltet aus der Produktionsversion erhalten
  2. Änderungen würden das Schema der Kopie gemacht werden und nicht auf die Produktionssysteme erhalten propagiert würden. An dieser Stelle würden wir Zerstreuungs Schemata haben. Keinen Spaß.

Mocking Database Server:
Wir tun dies auch in meinem derzeitigen Job. Nach jedem Commit führen wir Unit-Tests gegen den Anwendungscode, den Mock db Accessoren injiziert haben. Dann dreimal am Tag führen wir die volle db Build oben beschrieben. Ich empfehle auf jeden Fall beide Ansätze.

Andere Tipps

Ich laufe immer Tests gegen eine In-Memory-DB (HSQLDB oder Derby) aus diesen Gründen:

  • Es macht denken Sie, welche Daten in Ihrem Test DB zu halten und warum. Nur Ihre Produktion DB in einem Testsystem schleppen übersetzt „Ich habe keine Ahnung, was ich tue oder warum und wenn etwas kaputt geht, war es mir nicht !!“ ;)
  • Es stellt sicher, dass die Datenbank mit geringen Aufwand an einem neuen Ort neu erstellt werden
  • (zum Beispiel, wenn wir einen Fehler aus der Produktion replizieren müssen)
  • Es hilft enorm mit der Qualität der DDL-Dateien.

Die In-Memory-DB wird mit neuen Daten geladen, sobald die Tests starten und nach den meisten Tests, rufe ich es ROLLBACK stabil zu halten. immer halten die Daten im Test DB stabil! Wenn die Daten die ganze Zeit ändert, kann man nicht testen.

Die Daten werden von SQL, eine Vorlage DB oder ein dump / Backup geladen. Ich ziehe es Dumps, wenn sie in einem lesbaren Format sind, weil ich sie in VCS setzen kann. Wenn das nicht funktioniert, verwende ich eine CSV-Datei oder XML. Wenn ich enorme Datenmengen geladen werden müssen ... Ich weiß nicht. Sie haben nie große Mengen an Daten laden :) Nicht für Unit-Tests. Performance-Tests sind ein anderes Thema und andere Regeln gelten.

Ich habe für eine lange Zeit, um diese Frage gefragt, aber ich glaube, es gibt keinen Königsweg für die ist.

Was ich zur Zeit tun wird, um die DAO-Objekte spöttisch und eine in Erinnerung Darstellung einer guten Sammlung von Objekten zu halten, die interessante Fälle von Daten dar, die auf der Datenbank leben.

Das Hauptproblem ich mit diesem Ansatz zu sehen ist, dass Sie nur den Code sind abdeckt, die mit dem DAO Schicht in Wechselwirkung tritt, aber nie die Prüfung die DAO selbst, und in meiner Erfahrung, die ich sehe, dass viele Fehler auf dieser Ebene geschehen, wie Gut. Ich habe auch immer ein paar Unit-Tests, die in der Datenbank ausführen (aus Gründen der TDD oder ein schnellen Test vor Ort verwendet wird), aber diese Tests sind nie auf meinem Continuous Integration Server ausgeführt werden, da wir zu diesem Zweck keine Datenbank halten und ich Tests denken, die auf CI-Server ausgeführt werden soll unabhängig sein.

Ein weiterer Ansatz, den ich sehr interessant finde, aber nicht immer wert, da raubend ein wenig Zeit ist, ist das gleiche Schema, das Sie für die Produktion auf eine eingebettete Datenbank verwenden zu erstellen, die in der Unit-Tests nur ausgeführt wird.

Auch wenn es keine Frage, dieser Ansatz Ihre Berichterstattung verbessert, gibt es einige Nachteile, da Sie so nah wie möglich zu ANSI SQL sein müssen, um es sowohl mit dem aktuellen DBMS funktioniert und der Embedded-Ersatz.

Egal, was Sie denken, ist mehr relevant für Ihren Code, gibt es ein paar Projekte gibt, die es leichter machen kann, wie DbUnit .

Auch wenn es Tools, die Sie Ihre Datenbank in der einen oder anderen (zB jOOQ verspotten lassen‘ s MockConnection , die man sehen kann in diese Antwort - Disclaimer, ich arbeite für jOOQ des Verkäufers), würde ich raten nicht zu verspotten größere Datenbanken mit komplexen Abfragen.

Selbst wenn Sie nur wollen integrations testen Sie Ihre ORM, passen sie, dass ein ORM eine sehr komplexe Reihe von Anfragen an die Datenbank gibt, die in

kann variieren
  • Syntax
  • Komplexität
  • um (!)

Mocking, dass alle vernünftigen Dummy-Daten zu erzeugen, ziemlich hart ist, es sei denn, Sie tatsächlich eine kleine Datenbank in Ihrem Mock Gebäude, die die übertragenen SQL-Anweisungen interpretiert. so gesagt hat, verwenden Sie eine bekannte Integrations-Testdatenbank, die Sie leicht mit bekannten Daten zurücksetzen, gegen denen Sie Ihre Integrationstests ausgeführt werden können.

Ich benutze das erste (den Code gegen eine Testdatenbank ausgeführt wird). Die einzige wesentliche Problem sehe ich Sie mit diesem Ansatz erhöht die Möglichkeit von Schemata nicht synchron bekommen, die ich mit umgehen, indem Sie eine Versionsnummer in der Datenbank zu halten und machen alle Schemaänderungen über ein Skript, das die Änderungen für jede Version Schritt gilt.

Ich mache auch alle Änderungen (einschließlich dem Datenbankschema) gegen meine Testumgebung zunächst, so dass es endet als der andere Weg um: Nachdem alle Tests bestehen, wenden Sie das Schema-Updates auf den Produktionsserver. Ich habe auch immer ein separates Paar von Test vs. Anwendungsdatenbanken auf meinem Entwicklungssystem, so dass ich dort überprüfen kann, dass das db-Upgrade funktioniert, bevor die reale Produktion Feld berührt (es).

Ich bin mit dem ersten Ansatz, aber ein bisschen anders, die Probleme lösen können Sie erwähnt.

Alles, was benötigt wird, Tests für DAOs zu laufen ist in der Quellcodeverwaltung. Es enthält Schema und Skripte, die DB zu erstellen (Docker ist sehr gut für diese). Wenn die eingebettete DB genutzt werden kann - ich benutze es für die Geschwindigkeit

.

Der wesentliche Unterschied zu den anderen beschriebenen Ansätzen besteht darin, dass die Daten, die für den Test erforderlich ist, nicht von SQL-Skripts oder XML-Dateien geladen werden. Alles (außer einigen Wörterbuch-Daten, die effektiv konstant ist) durch Anwendung erstellt wird Utility-Funktionen / Klassen.

Der Hauptzweck ist es, Daten von Test verwendet, um

  1. sehr nah an den Test
  2. explizit (mit Hilfe von SQL-Dateien für Daten, die es sehr problematisch machen, um zu sehen, welcher Teil der Daten durch, welchen Test verwendet wird)
  3. isolieren Tests von den unabhängigen Änderungen.

Im Grunde bedeutet es, dass diese Programme erlauben deklarativ nur Dinge wesentlich für den Test in Test selbst angeben, und irrelevante Dinge weglassen.

Um eine Vorstellung davon, was es bedeutet in der Praxis, den Test für einige DAO, die ihr von Comment mit Posts zu Authorss geschrieben arbeitet. Um einige Daten CRUD-Operationen für einen solchen DAO zu testen, sollte in der DB angelegt werden. Der Test würde wie folgt aussehen:

@Test
public void savedCommentCanBeRead() {
    // Builder is needed to declaratively specify the entity with all attributes relevant
    // for this specific test
    // Missing attributes are generated with reasonable values
    // factory's responsibility is to create entity (and all entities required by it
    //  in our example Author) in the DB
    Post post = factory.create(PostBuilder.post());

    Comment comment = CommentBuilder.comment().forPost(post).build();

    sut.save(comment);

    Comment savedComment = sut.get(comment.getId());

    // this checks fields that are directly stored
    assertThat(saveComment, fieldwiseEqualTo(comment));
    // if there are some fields that are generated during save check them separately
    assertThat(saveComment.getGeneratedField(), equalTo(expectedValue));        
}

Dies hat mehrere Vorteile gegenüber SQL-Skripts oder XML-Dateien mit Testdaten:

  1. Sie den Code pflegen ist viel einfacher (eine obligatorische Spalte zum Beispiel in einigen Unternehmen hinzufügen, die in vielen Tests Bezug genommen wird, wie Autor, erfordert nicht viele Dateien / Datensätze zu ändern, sondern nur eine Änderung in dem Builder und / oder Fabrik)
  2. Die Daten, die von spezifischem Test erforderlich ist, im Test selbst beschrieben und nicht in einer anderen Datei. Diese Nähe ist sehr wichtig für Test Nachvollziehbarkeit.

Zurücksetzen vs Commit

Ich finde es bequemer, dass Tests begehen, wenn sie ausgeführt werden. Zum einen können einige Effekte (zum Beispiel DEFERRED CONSTRAINTS) nicht überprüft werden, wenn begehen nie passiert. Zweitens, wenn ein Test der Daten nicht in der DB geprüft werden, da sie nicht durch das Rollback rückgängig gemacht wird.

Von der Ursache hat dies einen Nachteil, dass Test ein gebrochenen Daten erzeugen kann und dies zu den Fehlern in anderen Tests führt. Um mit dieser Ich versuche, die Tests zu isolieren. In dem obigen Beispiel jedem Test erstellen können neue Author und alle anderen Einheiten werden im Zusammenhang mit der Erstellung so Kollisionen selten sind. Um mit den übrigen Invarianten, die möglicherweise gebrochen werden kann, aber nicht als DB-Ebene Einschränkung ausgedrückt werden ich einige programmatische Kontrollen für fehlerhafte Bedingungen verwenden, die nach jedem einzelnen Test durchgeführt werden können (und sie sind in CI laufen, aber in der Regel ausgeschaltet lokal für Leistung Gründe).

Für JDBC basiertes Projekt (direkt oder indirekt, beispielsweise JPA, EJB, ...) Sie können nicht die gesamte Datenbank Mockup (in diesem Fall wäre es besser, einen Test db auf einem echten RDBMS zu verwenden), aber nur Mockup auf JDBC-Ebene.

Vorteil ist Abstraktion, das mit dieser Art und Weise kommt, als JDBC-Daten (Ergebnismenge, Aktualisierungszählwert, Warnung, ...) ist die gleiche, was auch immer das Backend ist: Ihre prod db, ein Test db, oder nur einige Mockups Daten zur Verfügung gestellt für jeden Testfall.

Mit JDBC-Verbindung für jeden Fall verspottet besteht keine Notwendigkeit Test db zu verwalten (Bereinigung, nur ein Test zum Zeitpunkt, laden Spiele, ...). Jede Mockup Verbindung isoliert und es gibt keine Notwendigkeit zu bereinigen. Nur minimal erforderliche Armaturen sind in jedem Testfall vorgesehen JDBC Austausch zu verspotten, die Komplexität bei der Verwaltung eines gesamten Test db zu vermeiden.

Acolyte ist mein Rahmen, der einen JDBC-Treiber und Utility für diese Art von Mock-up umfasst: http://acolyte.eu. org .

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top