Wie teste ich die Datenzugriffsschicht?

17

Ich habe eine DAO-Methode, die Spring für JDBC-Zugriff verwendet. Es berechnet die Erfolgsquote eines Verkäufers beim Verkauf eines Artikels.

Hier ist der Code:

public BigDecimal getSellingSuccessRate(long seller_id) {
    String sql = "SELECT SUM(IF(sold_price IS NOT NULL, 1, 0))/SUM(1) 
                  FROM transaction WHERE seller_id = ?";
    Object[] args = {seller_id};
    return getJdbcTemplate().queryForObject(sql, args, BigDecimal.class);
}

Wie soll ich diese Methode oder eine DAO-Methode mit JUnit testen? Was sind einige bewährte Methoden zum Testen der Datenzugriffslogik? Ich denke darüber nach, es mit einer einbettbaren Datenbank zu testen, die mit einigen Daten geladen ist. Sollten wir aber keine Integrationstests durchführen, die in Bezug auf RDBMS und Schema einer Produktionsumgebung ähneln?

Michael
quelle
Testen Sie DBUnit . Es wurde speziell entwickelt, um Ihr Problem zu lösen.
Sergio

Antworten:

15

Das Problem bei der Verwendung einer "echten" Datenbank für Komponententests ist das Einrichten, Herunterfahren und Isolieren der Tests. Sie möchten keine völlig neue MySQL-Datenbank hochfahren und Tabellen und Daten nur für einen Komponententest erstellen müssen. Die Probleme damit haben mit der externen Natur der Datenbank zu tun und Ihre Testdatenbank ist ausgefallen, Ihre Komponententests schlagen fehl. Es gibt auch Probleme damit, sicherzustellen, dass Sie eine eindeutige Datenbank zum Testen haben. Sie können überwunden werden, aber es gibt eine einfachere Antwort.

Das Verspotten der Datenbank ist eine Option, testet jedoch nicht die tatsächlich ausgeführten Abfragen. Es kann als viel einfachere Lösung verwendet werden, wenn Sie sicherstellen möchten, dass die Daten vom DAO das System ordnungsgemäß durchlaufen. Aber zum Testen des DAO selbst brauchst du etwas, hinter dem DAO die Daten hat und die Abfragen einwandfrei laufen.

Als Erstes müssen Sie eine In-Memory-Datenbank verwenden. HyperSQL ist hierfür eine ausgezeichnete Wahl, da es den Dialekt einer anderen Datenbank emulieren kann - so dass die geringfügigen Unterschiede zwischen Datenbanken gleich bleiben (Datentypen, Funktionen und dergleichen). Hsqldb hat auch einige nette Funktionen für Unit-Tests.

db.url=jdbc:hsqldb:file:src/test/resources/testData;shutdown=true;

Dadurch wird der Status der Datenbank (Tabellen, Anfangsdaten) aus der testDataDatei geladen. shutdown=truewird die Datenbank automatisch herunterfahren, wenn die letzte Verbindung geschlossen wird.

Lassen Sie die Komponententests mithilfe der Abhängigkeitsinjektion eine andere Datenbank auswählen, als sie von der Produktions- (oder Test- oder lokalen) Builds verwendet wird.

Ihr DAO verwendet dann die injizierte Datenbank, für die Sie Tests für die Datenbank starten können.

Die Unit-Tests sehen dann so aus (ein paar langweilige Sachen, die der Kürze halber nicht enthalten sind):

    @Before
    public void setUpDB() {
        DBConnection connection = new DBConnection();
        try {
            conn = connection.getDBConnection();
            insert = conn.prepareStatement("INSERT INTO data (txt, ts, active) VALUES (?, ?, ?)");
        } catch (SQLException e) {
            e.printStackTrace();
            fail("Error instantiating database table: " + e.getMessage());
        }
    }

    @After
    public void tearDown() {
        try {
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private void addData(String txt, Timestamp ts, boolean active) throws Exception {
        insert.setString(1, txt);
        insert.setTimestamp(2, ts);
        insert.setBoolean(3, active);
        insert.execute();
    }

    @Test
    public void testGetData() throws Exception {
        // load data
        Calendar time = Calendar.getInstance();
        long now = time.getTimeInMillis();
        long then1h = now - (60 * 60 * 1000);  // one hour ago
        long then2m = now - (60 * 1000 * 2);   // two minutes ago
        addData("active_foo", new Timestamp(then1h), true);     // active but old
        addData("inactive_bar", new Timestamp(then1h), false);  // inactive and old
        addData("active_quz", new Timestamp(then2m), true);     // active and new
        addData("inactive_baz", new Timestamp(then2m), false);  // inactive and new

        DataAccess dao = new DataAccess();
        int count = 0;
        for (Data data : dao.getData()) {
            count++;
            assertTrue(data.getTxt().startsWith("active"));
        }

        assertEquals("got back " + count + " rows instead of 1", count, 1);
    }

Sie haben also einen Komponententest, der das DAO aufruft und die Daten verwendet, die für die Dauer des Tests in einer On-the-Fly-Datenbank eingerichtet wurden. Sie müssen sich keine Gedanken über externe Ressourcen oder den Status der Datenbank machen, bevor Sie sie ausführen oder einen bekannten Status wiederherstellen (der bekannte Status ist "nicht vorhanden" und es ist trivial, ihn wiederherzustellen).

DBUnit kann vieles von dem, was ich beschrieben habe, vereinfachen, indem es die Datenbank einrichtet, die Tabellen erstellt und die Daten lädt. Wenn Sie aus irgendeinem Grund die eigentliche Datenbank benötigen, ist dies bei weitem das bessere Werkzeug.

Der obige Code ist Teil eines Maven-Projekts, das ich für den Proof-of-Concept- Test mit Hsqldb auf Github geschrieben habe


quelle
2
Ich wusste nicht, dass HSQL den Dialekt eines anderen Datenbankanbieters verspotten kann. Vielen Dank.
Michael
1
@Dog Dies kann über die Datenbankeigenschaften erfolgen, z. B. durch sql.syntax_mys=trueÄnderung der Funktionsweise von hsqldb: "Wenn diese Eigenschaft auf true gesetzt ist, werden die Typen TEXT und AUTO_INCREMENT unterstützt und die Kompatibilität mit einigen anderen Aspekten dieses Dialekts ermöglicht." while sql.syntax_ora=truedoes "Diese Eigenschaft aktiviert, wenn sie auf true gesetzt ist, die Unterstützung für nicht standardmäßige Typen. Sie aktiviert außerdem die DUAL-, ROWNUM-, NEXTVAL- und CURRVAL-Syntax und ermöglicht die Kompatibilität mit einigen anderen Aspekten dieses Dialekts."
DBUnit ist der Weg :)
Silviu Burcea
@SilviuBurcea Mit DBUnit ist das Einrichten einer komplexen Umgebung für Datenbanktests viel einfacher als von Hand. Es ist immer noch manchmal nützlich, zu wissen, wie man es bei Bedarf von Hand macht (der oben erwähnte "von Hand" -Ansatz kann in andere Sprachen migriert werden, in denen DBUnit keine Option ist).
Sie können sich Acolyte
cchantep 29.07.16
2

Erstens sollten Sie niemals in einer Produktionsumgebung testen. Sie sollten über eine Testumgebung verfügen, die Ihre Produktionsumgebung widerspiegelt, und dort Integrationstests durchführen.

Wenn Sie das tun, können Sie eine Reihe von Dingen tun.

  • Schreiben Sie Komponententests, mit denen überprüft wird, ob die entsprechende SQL-Anweisung mit einem Verspottungsframework wie Mockito an ein Verspottungselement gesendet wird. Dies stellt sicher, dass Ihre Methode das tut, was sie tun soll, und nimmt die Integration aus dem Bild heraus.
  • Schreiben Sie Test-SQL-Skripte, die die Eignung des SQL belegen, auf das Sie in Ihren Komponententests getestet haben. Dies kann bei Optimierungsproblemen hilfreich sein, da Sie auch Erklärungen und ähnliches basierend auf Ihren Testskripten ausführen können.
  • Verwenden Sie DBUnit, wie von @Sergio erwähnt.
Matthew Flynn
quelle
Woops, als ich Produktionsumgebung sagte, meinte ich eine Simulation davon. Vielen Dank für Ihre Antwort, ich werde mir Mockito ansehen, denn das ist etwas, was ich auch lernen wollte.
Michael
1

In unserem Projekt hat jeder Entwickler eine leere Datenbank, deren Struktur mit der Produktionsdatenbank identisch ist.

In jedem Unit-Test TestInitialize erstellen wir eine Verbindung und Transaktion zur Datenbank sowie einige Standardobjekte, die wir für jeden Test benötigen. Und alles wird nach dem Ende jeder Methode oder Klasse zurückgesetzt.

Auf diese Weise ist es möglich, die SQL-Ebene zu testen. Tatsächlich muss jede Abfrage oder jeder Datenbankaufruf auf diese Weise getestet werden.

Der Nachteil ist, dass es langsam ist, also haben wir es in ein anderes Projekt als unsere regulären Komponententests gestellt. Es ist möglich, dies durch die Verwendung einer In-Memory-Datenbank zu beschleunigen, aber die Idee bleibt dieselbe.

Carra
quelle
Wenn Sie eine In-Memory-Datenbank verwenden, kann ein Drop-Create-Ansatz vor dem Ausführen aller Testsuiten anstelle des Rollback-Transaktionsrechts verwendet werden, was nur viel schneller ist.
Downhillski
Ich hätte nie gedacht, dass das so ist. In unseren Tests erzeugen die meisten Tests ein Benutzer 'x', das jedoch eindeutig ist. Wenn Sie eine Datenbank einmal erstellen, müssen Sie die Tests ändern, um diese Objekte wiederzuverwenden.
Carra
Ich weiß, wir sind auf der gleichen Seite und ich mag Ihren Ansatz. Ihr Ansatz garantiert, dass jeder Testfall unabhängig von der Reihenfolge ausgeführt werden kann, und dass der Status der Datentabelle vor jeder Ausführung derselbe ist.
Downhillski
Das ist richtig, die Reihenfolge spielt dann keine Rolle. Wir haben schon früher gesehen, dass Tests fehlgeschlagen sind, da die Reihenfolge der Testläufe auf unserem Build-PC und unserem lokalen Computer unterschiedlich ist.
Carra