Come creare test di integrazione scalabili e senza effetti collaterali?


14

Nel mio progetto attuale, ho difficoltà a trovare una buona soluzione per creare test di integrazione scalabili che non hanno effetti collaterali. Un piccolo chiarimento sulla proprietà libera dell'effetto collaterale: riguarda principalmente il database; non ci dovrebbero essere cambiamenti nel database dopo che i test sono stati completati (lo stato dovrebbe essere preservato). Forse la scalabilità e la conservazione dello stato non si uniscono, ma voglio davvero spingere per una soluzione migliore.

Ecco un tipico test di integrazione (questi test toccano il livello del database):

public class OrderTests {

    List<Order> ordersToDelete = new ArrayList<Order>(); 

    public testOrderCreation() {
        Order order = new Order();
        assertTrue(order.save());
        orderToDelete.add(order);
    }

    public testOrderComparison() {
        Order order = new Order();
        Order order2 = new Order();
        assertFalse(order.isEqual(order2);
        orderToDelete.add(order);
        orderToDelete.add(order2);
    }
    // More tests

    public teardown() {
         for(Order order : ordersToDelete)
             order.delete();
    }
}

Come si potrebbe immaginare, questo approccio produce test estremamente lenti. E, quando applicato a tutti i test di integrazione, sono necessari circa 5 secondi per testare solo una piccola parte del sistema. Posso immaginare che questo numero aumenti quando aumenta la copertura.

Quale sarebbe un altro approccio per scrivere tali test? Un'alternativa a cui riesco a pensare è avere una sorta di variabili globali (all'interno di una classe) e tutti i metodi di test condividono questa variabile. Di conseguenza, solo pochi ordini vengono creati ed eliminati; con conseguente test più veloci. Tuttavia, penso che questo introduca un problema più grande; i test non sono più isolati e diventa sempre più difficile comprenderli e analizzarli.

È possibile che i test di integrazione non vengano eseguiti con la stessa frequenza dei test unitari; quindi le prestazioni basse potrebbero essere accettabili per quelli. In ogni caso, sarebbe bello sapere se qualcuno ha trovato alternative per migliorare la scalabilità.

Risposte:


6

Cerca di utilizzare Hypersonic o un altro DB in memoria per test unitari. Non solo i test verranno eseguiti più velocemente, ma gli effetti collaterali non sono rilevanti. (Il rollback delle transazioni dopo ogni test consente anche di eseguire molti test sulla stessa istanza)

Questo ti costringerà anche a creare modelli di dati, il che è una buona cosa IMO, poiché significa che qualcosa che si verifica nel database di produzione non può inspiegabilmente iniziare a fallire i test, e ti dà un punto di partenza per ciò che un "installazione pulita del database "sembra, il che aiuta se è necessario distribuire improvvisamente una nuova istanza dell'applicazione senza alcuna connessione al sito di produzione esistente.

Sì, utilizzo questo metodo da solo, e sì, è stato un PITA da configurare la PRIMA volta, ma è più che pagato per se stesso nella vita del progetto che sto per completare. Ci sono anche una serie di strumenti che aiutano con i modelli di dati.


Sembra un'ottima idea. Mi piace soprattutto il suo completo isolamento; a partire da una nuova installazione del database. Sembra che ci vorrà qualche sforzo, ma una volta installato, sarà molto utile. Grazie.
Guven,

3

Questo è l'eterno problema che tutti devono affrontare durante la scrittura di test di integrazione.

La soluzione ideale, in particolare se si sta testando la produzione, è l'apertura di una transazione in fase di installazione e il rollback in fase di smontaggio. Penso che questo dovrebbe soddisfare le tue esigenze.

Laddove ciò non sia possibile, ad esempio quando si sta testando l'app dal livello client, un'altra soluzione consiste nell'utilizzare un DB su una macchina virtuale e fare un'istantanea durante l'installazione e tornare ad essa in fase di smontaggio (non impiegare il tempo che ci si potrebbe aspettare).


Non riesco a credere di non aver pensato al rollback della transazione. Userò quell'idea come soluzione rapida alla soluzione, ma alla fine il DB in memoria sembra molto promettente.
Guven,

3

I test di integrazione devono sempre essere eseguiti in base all'impostazione della produzione. Nel tuo caso significa che dovresti avere lo stesso database e lo stesso server delle applicazioni. Naturalmente, per motivi di prestazioni, è possibile decidere di utilizzare un DB in memoria.

Quello che non dovresti mai fare è estendere il tuo ambito di transazione. Alcune persone hanno suggerito di assumere il controllo della transazione e di ripristinarla dopo i test. Quando lo fai, tutte le entità (supponendo che tu stia utilizzando l'APP) rimarranno attaccate al contesto di persistenza durante l'esecuzione del test. Ciò può comportare alcuni bug molto cattivi che sono molto difficili da trovare .

Invece, è necessario cancellare manualmente il database dopo ogni test attraverso una libreria come JPAUnit o simile a questo approccio (cancellare tutte le tabelle usando JDBC).

A causa dei tuoi problemi di prestazioni, non dovresti eseguire i test di integrazione su ogni build. Lascia che il tuo server di integrazione continua faccia questo. Se si utilizza Maven, è possibile utilizzare il plug-in fail - safe che consente di separare i test in unità e test di integrazione.

Inoltre, non dovresti deridere nulla. Ricorda che stai testando l'integrazione, ovvero testando il comportamento all'interno dell'ambiente di esecuzione runtime.


Buona risposta. Un punto: può essere accettabile deridere alcune dipendenze esterne (ad es. Invio di e-mail). Ma dovresti piuttosto deridere impostando un servizio / server finto completo, non deriderlo nel codice Java.
sleske,

Sleske, sono d'accordo con te. Soprattutto quando si utilizza JPA, EJB, JMS o si implementa con un'altra specifica. È possibile scambiare il server delle applicazioni, il provider di persistenza o il database. Ad esempio, potresti voler utilizzare Glassfish e HSQLDB integrati per configurare e migliorare semplicemente la velocità (sei ovviamente libero di scegliere un'altra implementazione certificata).
BenR,

2

Sulla scalabilità

Ho avuto questo problema alcune volte in precedenza, che i test di integrazione richiedevano troppo tempo per essere eseguiti e non erano pratici per un singolo sviluppatore di eseguire continuamente in un ciclo di feedback stretto di modifiche. Alcune strategie per far fronte a questo sono:

  • Scrivi meno test di integrazione : se hai una buona copertura con i test unitari nel sistema, i test di integrazione dovrebbero essere più focalizzati su (ahem) problemi di integrazione, su come diversi componenti lavorano insieme. Sono più simili ai test del fumo, che provano solo a vedere se il sistema funziona ancora nel suo complesso. Quindi, idealmente, i test unitari coprono la maggior parte delle parti funzionali dell'applicazione e i test di integrazione controllano semplicemente come tali parti interagiscono tra loro. Non è necessaria un'ampia copertura dei percorsi logici qui, solo alcuni percorsi critici attraverso il sistema.
  • Esegui solo un sottoinsieme dei test alla volta : come singolo sviluppatore che lavora su alcune funzionalità, di solito hai un'idea dei test necessari per coprire tale funzionalità. Esegui solo i test che hanno senso per coprire le modifiche. Frame come JUnit ti consentono di raggruppare i dispositivi in ​​una categoria . Crea i raggruppamenti che consentono la migliore copertura per ogni funzione che hai. Ovviamente, vuoi comunque eseguire tutti i test di integrazione ad un certo punto, forse prima di passare al sistema di controllo del codice sorgente e sicuramente al server di integrazione continua.
  • Ottimizza il sistema : i test di integrazione associati ad alcuni test delle prestazioni potrebbero fornire l'input necessario per ottimizzare le parti lente del sistema, in modo che i test vengano eseguiti più rapidamente in seguito. Ciò potrebbe aiutare a scoprire gli indici necessari nel database, le interfacce chat tra sottosistemi o altri colli di bottiglia delle prestazioni che devono essere indirizzati.
  • Esegui i test in parallelo : se disponi di un buon raggruppamento di test ortogonali, puoi provare a eseguirli in parallelo . Siate attenti ai requisiti ortogonali che ho citato, tuttavia, non volete che i vostri test facciano un passo gli uni agli altri.

Prova a combinare queste tecniche per un maggiore effetto.


1
Linee guida davvero buone; scrivere meno test di integrazione è sicuramente il miglior consiglio. Inoltre, eseguirli in parallelo sarebbe un'ottima alternativa; un "test" perfetto per verificare se i test sono stati eseguiti direttamente in termini di isolamento.
Guven,

2

A scopo di test abbiamo utilizzato una distribuzione basata su file di un database SQLite (basta copiare una risorsa). Ciò è stato fatto in modo da poter testare anche le migrazioni degli schemi. Per quanto ne so, le modifiche allo schema non sono transazionali, quindi non verranno ripristinate dopo l'interruzione di una transazione. Inoltre, il fatto di non dover fare affidamento sul supporto delle transazioni per l'impostazione del test consente di testare correttamente il comportamento delle transazioni dall'applicazione.

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.