Come testare il livello di accesso ai dati?


17

Ho un metodo DAO che utilizza Spring per l'accesso JDBC. Calcola la percentuale di successo di un venditore nella vendita di un articolo.

Ecco il codice:

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);
}

Come devo fare per testare questo metodo o qualsiasi metodo DAO con JUnit? Quali sono alcune best practice per testare la logica di accesso ai dati? Sto pensando di testarlo su un database incorporabile caricato con alcuni dati, ma non dovremmo fare test di integrazione simili a un ambiente di produzione in termini di RDBMS e schema?


Dai un'occhiata a DBUnit . È stato creato appositamente per risolvere il tuo problema.
Sergio,

Risposte:


15

Il problema con l'utilizzo di un database "reale" per i test unitari è l'installazione, il decollo e l'isolamento dei test. Non è necessario creare un database MySQL completamente nuovo e creare tabelle e dati solo per un test unitario. I problemi con questo hanno a che fare con la natura esterna del database e il database di test è inattivo, i test delle unità falliscono. Ci sono anche problemi nell'assicurarsi di disporre di un database univoco per il test. Possono essere superati, ma c'è una risposta più semplice.

Deridere il database è un'opzione, tuttavia non verifica le query effettive che vengono eseguite. Può essere utilizzato come soluzione molto più semplice quando si desidera assicurarsi che i dati del DAO attraversino correttamente il sistema. Ma per testare il DAO stesso è necessario qualcosa dietro al DAO che i dati e le query vengono eseguite correttamente.

La prima cosa da fare è utilizzare un database in memoria. HyperSQL è una scelta eccellente per questo perché ha la capacità di emulare il dialetto di un altro database, in modo che le differenze minori tra i database rimangano le stesse (tipi di dati, funzioni e simili). hsqldb ha anche alcune belle funzioni per i test unitari.

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

Questo carica lo stato del database (le tabelle, i dati iniziali) dal testDatafile. shutdown=truechiuderà automaticamente il database alla chiusura dell'ultima connessione.

Utilizzando l' iniezione delle dipendenze , fare in modo che i test unitari selezionino un database diverso da quello utilizzato dalla produzione (o test o locale).

Il DAO utilizza quindi il database iniettato per il quale è possibile avviare i test sul database.

I test unitari appariranno quindi simili (un mucchio di cose noiose non incluse per brevità):

    @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);
    }

E quindi, hai un unit test che chiama il DAO e sta usando i dati che sono stati impostati in un database al volo che esiste per la durata del test. Non devi preoccuparti delle risorse esterne o dello stato del database prima dell'esecuzione o del ripristino a uno stato noto (beh, lo "stato noto" è "non esiste", che è banale a cui tornare).

DBUnit può fare molto di quello che ho descritto un processo più semplice nell'impostazione del database, nella creazione delle tabelle e nel caricamento dei dati. Se avessi bisogno di utilizzare il database reale per qualche motivo, questo è di gran lunga lo strumento migliore da usare.

Il codice sopra è parte di un progetto maven che ho scritto per la prova del concetto TestingWithHsqldb su github


2
Non sapevo della parte secondo cui HSQL può deridere il dialetto di un altro fornitore di database. Grazie.
Michael,

1
@ Cane questo può essere fatto tramite le proprietà del database come quelle sql.syntax_mys=trueche cambiano il modo in cui hsqldb funziona: "Questa proprietà, se impostata su true, abilita il supporto per i tipi TEXT e AUTO_INCREMENT e consente anche la compatibilità con alcuni altri aspetti di questo dialetto." while sql.syntax_ora=truefa "Questa proprietà, se impostata su true, abilita il supporto per tipi non standard. Abilita anche la sintassi DUAL, ROWNUM, NEXTVAL e CURRVAL e consente anche la compatibilità con alcuni altri aspetti di questo dialetto."

DBUnit è il modo :)
Silviu Burcea,

@SilviuBurcea DBUnit rende certamente molto più "flessibile" l'installazione di un ambiente di test di database complessi molto più semplice rispetto a farlo manualmente. A volte è ancora utile sapere come farlo manualmente se necessario (l'approccio "manuale" sopra menzionato potrebbe essere migrato in altre lingue in cui DBUnit non è un'opzione).

Puoi dare un'occhiata a Acolyte
cchantep

2

Innanzitutto, non dovresti mai fare test in un ambiente di produzione. È necessario disporre di un ambiente di test che rispecchi l'ambiente di produzione e di eseguire test di integrazione lì.

Se lo fai, puoi fare una serie di cose.

  • Scrivi unit test che testano per vedere se l'SQL appropriato viene inviato a un oggetto simulato usando un framework di simulazione come Mockito. Questo assicurerà che il tuo metodo stia facendo quello che dovrebbe fare e toglie l'integrazione dall'immagine.
  • Scrivi degli script SQL di prova che dimostrano l'adeguatezza dell'SQL che hai testato nei test unitari. Ciò può essere d'aiuto in caso di problemi di ottimizzazione, in quanto è anche possibile eseguire spiegazioni e simili in base agli script di test.
  • Utilizzare DBUnit, come indicato da @Sergio.

Woops quando dicevo che l'ambiente di produzione intendeva davvero simularne. Grazie per la tua risposta, darò un'occhiata a Mockito perché è qualcosa che anch'io volevo imparare.
Michael,

1

Nel nostro progetto, ogni sviluppatore sta eseguendo un database vuoto, la sua struttura è la stessa del database di produzione.

In ogni unit test TestInitialize, creiamo una connessione e transazione al database oltre ad alcuni oggetti predefiniti necessari per ogni test. E tutto viene ripristinato dopo la fine di ogni metodo o classe.

In questo modo è possibile testare il layer sql. In effetti, ogni query o chiamata al database deve essere testata in questo modo.

Il rovescio della medaglia è che è lento, quindi lo abbiamo inserito in un progetto separato dai nostri test unitari regolari. È possibile accelerarlo utilizzando un database in memoria, ma l'idea rimane la stessa.


Se si utilizza un database in memoria, è possibile utilizzare un approccio drop-create prima dell'esecuzione di tutte le suite di test al posto della transazione di rollback, che è molto più veloce.
Downhillski,

Non avrei mai pensato di farlo in questo modo prima. Nei nostri test, la maggior parte dei test crea un utente 'x' sebbene sia unico. Creare una volta un db significherebbe cambiare i test per riutilizzare quegli oggetti.
Carra,

Lo so, siamo sulla stessa pagina e mi piace il tuo approccio. il vostro approccio garantisce che tutti i casi di test possano essere eseguiti indipendentemente dall'ordine e ogni volta che viene eseguito lo stato della tabella dei dati è lo stesso.
Downhillski,

È corretto, l'ordine non ha importanza allora. Abbiamo già visto fallire i test perché l'ordine di esecuzione dei test unitari è diverso sul nostro pc di costruzione e sulla nostra macchina locale.
Carra,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.