Poco valore nel test unitario del componente del database

1

Avere un componente che rappresenta il database è meraviglioso! Puoi usarlo dalla logica di business per leggere e scrivere dati. Ma questo componente dovrebbe essere testato unitamente?

Direi che non dovrebbe. I test unitari riguardano esclusivamente la verifica del contratto di un componente con i suoi attori esterni.

Prendiamo un'interfaccia tipica di un componente del database

void insertUsers(List<UserRecord> users);
List<UserRecord> fetchUsers(List<UserID> userIds);
void deleteUsers(List<UserID> userIds);

Ogni metodo elencato utilizza la libreria del database per inviare le query appropriate al database.

Gli attori esterni del componente del database sono:

  • La logica aziendale
  • Il database

Il test delle unità mi richiederebbe di prendere in giro la connessione, l'istruzione preparata e gli oggetti del set di risultati della libreria del database.

Tuttavia, poiché l'intero componente del database consiste solo di interazioni con la libreria del database, qualsiasi test che scrivo finirà per rispecchiare il codice del componente ed è quindi fragile.

Verificare il contratto significa affermare quanto segue:

  • Chiamare insertUsers dovrebbe scrivere quegli utenti nel database
  • Chiamare fetchUsers dovrebbe recuperare quegli utenti dal database
  • Chiamare deleteUsers dovrebbe eliminare quegli utenti dal database

Il test dell'interazione con la libreria del database porterà a un codice fragile:

  • È possibile creare molte istruzioni SQL equivalenti. La modifica di una dichiarazione in una equivalente non interrompe il contratto e non dovrebbe interrompere il test
  • Si potrebbe usare la libreria del database in diversi modi: ad esempio: utilizzare una dichiarazione invece di un oggetto istruzione preparato. Anche questo non rompe il contratto
  • La modifica dell'ordine delle colonne in un'istruzione select o insert produrrebbe risultati equivalenti. Assenza di affermazione su resultset.getString (0) o preparedStatement.setString (1, "Bill")
  • La libreria di database utilizzata non dovrebbe avere importanza.

Le seguenti considerazioni mi hanno portato alla conclusione che i test unitari per il componente del database offrono poco valore. Sento davvero che un test di integrazione che richiede un vero database è la strada da percorrere.

Per favore condividi le tue opinioni sull'argomento; Potrebbe essere che mi manca qualcosa?

Modifica: Si prega di suggerire in che modo il seguente codice può essere testato unitamente in modo non fragile. Sentiti libero di refactoring il codice se ti piace.

void insertUsers(List<UserRecord> users) throws RepositoryException{
    try(Connection connection = datasource.getConnection();
        PreparedStatement stmt = connection.prepareStatement("insert into users (Name, Surname, DateOfBirth) values (?,?,?)")){
        for (UserRecord user : users){
            stmt.setString(1, user.name);
            stmt.setString(2, user.surname);
            stmt.setTimestamp(3, user.dateOfBirth);
            stmt.execute();
        }
    } catch(SQLException ex){
        throw new RepositoryException(ex);
    }
}
    
posta Lefteris Eleftheriades 12.06.2018 - 15:24
fonte

4 risposte

7

Presumibilmente, questo componente di database è solo un involucro abbastanza sottile che esegue una conversione tra gli oggetti utilizzati nella business logic e l'interfaccia SQL fornita dal database reale.

Hai ragione che testare un componente del genere in completo isolamento sarà fragile e non molto utile. D'altro canto, testare un componente in combinazione con un vero database fornisce un sacco di valore aggiunto.

Se puoi dimostrare che il "componente del database" interagisce correttamente con un database reale per tutti i metodi forniti dal componente, puoi eseguire tutti i dozzine di casi aziendali in cui un utente viene richiamato con una simulazione del componente del database e ho ancora fiducia che funzionerà anche con un vero database.

    
risposta data 12.06.2018 - 15:59
fonte
1

Tu stai parlando di un database fittizio, giusto? A mio avviso, se vale la pena utilizzare un componente utilizzato solo per i test, vale la pena testarlo.

Non importa se è piccolo e supporta solo alcune delle cose supportate da un vero database. Dovresti avere una copertura di prova di qualsiasi componente non banale nella tua base di codice, indipendentemente dal fatto che risieda nel codice commerciale o nella suite di test. Naturalmente, se riesci a mettere tutto sotto controllo senza le principali classi di aiuto, tanto meglio. Ma se usare classi di helper non banali nella tua suite di test è la soluzione più conveniente, allora può valere la pena di scrivere test aggiuntivi per una classe di simulazione.

    
risposta data 12.06.2018 - 15:41
fonte
0

Sono d'accordo con il tuo processo di pensiero qui. I test di integrazione sono dove vedrai i maggiori benefici. L'unico posto in cui prenderei in considerazione i test unitari sarebbe quando si puliscono i dati prima di colpire il database. Ciò contribuirà a garantire che invii ciò che ti aspetti e ti faccia sapere se le regressioni sono dovute al tuo codice o al comportamento del database. I test di integrazione probabilmente ti porteranno alla fonte del problema abbastanza velocemente, quindi è davvero una sentenza basata sul tuo codice.

Aggiungerò che un altro vantaggio dei test di integrazione è che sarà più facile garantire un comportamento coerente quando si aggiorna il database. La versione più recente potrebbe presentare alcune modifiche alla sintassi che i test di unità nasconderebbero.

    
risposta data 12.06.2018 - 16:01
fonte
0

should that component be unit-tested?

Ecco cosa ha scritto Kent Beck nel 2008

I get paid for code that works, not for tests, so my philosophy is to test as little as possible to reach a given level of confidence

Una delle cose che trovo interessanti nei test isolati è che ti incoraggiano a pensare quali parti del tuo codice vivono nel nucleo funzionale e quali interagiscono con la shell imperativa

Unit testing would require me to mock the connection, prepared statement and result set objects of the database library.

Forse - ma non è tutto quello che sta succedendo qui. Ci sono in realtà un certo numero di fasi differenti

  • Traduci la rappresentazione dei dati dal processo locale in una rappresentazione dei dati per il database (cioè, convertendo i dati in una "query")
  • Invia la query al database
  • Ricevi la risposta dal database
  • Traduci la rappresentazione dei dati dalla risposta in una rappresentazione compresa dal sistema locale.

Le traduzioni sono pezzi puramente funzionali che puoi testare con il factoring appropriato dei tuoi elementi.

Il componente del database sceglie la dichiarazione preparata corretta? Mette gli argomenti corretti nei parametri corretti? Traducete correttamente i set di risultati negli oggetti del vostro dominio? Gestisci correttamente le condizioni di errore che potrebbero verificarsi? Tutte queste cose sono testabili senza lasciando il limite del processo.

Dovresti progettare il componente del database in modo tale che le parti funzionali possano essere testate? Probabilmente - questa è la "separazione delle preoccupazioni" euristica al lavoro.

Dopo aver separato le parti funzionali, dovresti testarle? Mi piace prendere in prestito un'euristica da Hoare - il codice ovviamente non ha carenze? Se è vero, allora forse non ha bisogno di test. Immagino che lo faccia

Hai bisogno di testare la collaborazione delle parti funzionali? Qui, immagino che tu possa scrivere la collaborazione in un modo che ovviamente non ha carenze. Sarei probabilmente a mio agio anche se questo codice è stato testato solo a livello di sistema.

Testing interaction with the database library will lead to fragile code

Questa è una offerta eccessiva. Accoppiare strettamente i test a una particolare scelta di implementazione può portare a test fragili. Questo è particolarmente vero se il tuo test interferisce con i requisiti non funzionali.

È molto più facile eliminare un test che non è utile piuttosto che eseguire un test che non è presente. Lì è un vero compromesso da considerare, ma non esagerare i costi del test di fine vita.

    
risposta data 12.06.2018 - 17:47
fonte

Leggi altre domande sui tag