Come testare il livello di accesso ai dati?

16

Ho un metodo DAO che utilizza Spring per l'accesso JDBC. Calcola il tasso di successo di un venditore di 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 dovrei andare a testare questo metodo o qualsiasi metodo DAO con JUnit? Quali sono le migliori pratiche per testare la logica di accesso ai dati? Sto pensando di testarlo su un database integrabile caricato con alcuni dati, ma non dovremmo fare test di integrazione simili a un ambiente di produzione in termini di RDBMS e dello schema?

    
posta Michael 24.11.2013 - 06:22
fonte

3 risposte

14

Il problema con l'utilizzo di un database 'reale' per il test delle unità è l'impostazione, l'eliminazione e l'isolamento dei test. Non si vuole dover creare un database MySQL completamente nuovo e creare tabelle e dati solo per un test di unità. I problemi con questo hanno a che fare con la natura esterna del database e il database di test è inattivo, i test dell'unità falliscono. Ci sono anche problemi nell'assicurarsi di avere un database univoco con cui testare. Possono essere superati, ma c'è una risposta più semplice.

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

La prima cosa da fare è usare 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 (dati tipi, funzioni e simili). hsqldb ha anche alcune caratteristiche interessanti per il test delle unità.

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

Carica lo stato del database (le tabelle, i dati iniziali) dal file testData . shutdown=true spegne automaticamente il database quando si chiude l'ultima connessione.

Usando iniezione di dipendenza , i test dell'unità selezionano un diverso database di quello che la produzione (o test, o locale) usa.

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

I test unitari avranno quindi un aspetto simile (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 test unitario che chiama il DAO e sta utilizzando 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 di uno stato conosciuto (beh, lo 'stato conosciuto' è 'non esiste' che è banale da ripristinare).

DBUnit può rendere molto di ciò che ho descritto un processo più semplice nella configurazione del database, nella creazione delle tabelle e nel caricamento dei dati. Se avessi bisogno di utilizzare il database attuale per qualche ragione, 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

    
risposta data 25.11.2013 - 03:01
fonte
2

In primo luogo, non si dovrebbe mai fare test in un ambiente di produzione. Dovresti disporre di un ambiente di test che rispecchi l'ambiente di produzione e esegua test di integrazione.

Se lo fai, allora puoi fare una serie di cose.

  • Scrive i test delle unità che verificano se l'SQL appropriato viene inviato a un elemento fittizio usando un framework di simulazione come Mockito. Ciò assicurerà che il tuo metodo stia facendo ciò che dovrebbe fare e toglie l'integrazione dall'immagine.
  • Scrivere script SQL di test che dimostrino l'adeguatezza dell'SQL testato nei test di unità. Ciò può essere d'aiuto per qualsiasi problema di tuning che potresti incontrare, poiché puoi anche eseguire spiegazioni e simili in base agli script di test.
  • Usa DBUnit, come menzionato da @Sergio.
risposta data 24.11.2013 - 08:04
fonte
1

Sul 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 & transazione al database più alcuni oggetti predefiniti di cui abbiamo bisogno per ogni test. E tutto viene ripristinato dopo la fine di ogni metodo o classe.

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

Il lato negativo è che è lento, quindi lo abbiamo inserito in un progetto separato dai nostri normali test di unità. È possibile accelerare l'uso di un database in memoria, ma l'idea rimane la stessa.

    
risposta data 15.01.2014 - 16:20
fonte

Leggi altre domande sui tag