2009-06-18 4 views
27

Attualmente il nostro progetto ha oltre 3000 test unitari e "ant testAll" richiede oltre 20 minuti. oltre a ottenere un hardware migliore, ci sono modi per velocizzare le cose?Come velocizzi i test dell'unità java?

+0

Dovresti essere più specifico di ciò che sta trattenendo quei test. – starblue

+0

Ho aderito a questo progetto solo due mesi fa e ha già una base di codice estesa. Non è un'app Web e, a parte un mucchio di file di configurazione xml, non lo fa abbastanza spesso. Utilizziamo il nostro TestRunner personalizzato. ma il fatto è che stiamo sviluppando solo plugin per il sistema, e come tale non abbiamo il codice sorgente di TestRunner. Presumo che l'implementor di TestRunner sia stato abbastanza intelligente da ottimizzare il materiale di installazione, quindi, ad esempio, non dovrà analizzare le configurazioni xml in ogni esecuzione di test. –

+0

stai usando oggetti finti per deridere gli oggetti ad accesso lento dai test delle tue unità? Al lavoro abbiamo 6000 test unitari e impiegano circa un minuto o forse due per funzionare. –

risposta

1

Senza ulteriore conoscenza di ciò che viene testato, gli unici due approcci che presentano prontamente stessi sono:

  • uso hardware migliore (scusate)
  • semplificare la logica di test

Potrebbe voglio anche eseguire un profiler durante l'esecuzione del test e vedere se ci sono test particolarmente particolarmente efficaci.

+0

credo che dovrò eseguire un profiler sui casi di test. –

25

Allo stesso modo si velocizzerebbe qualsiasi altro codice. Scopri quali test richiedono più tempo e vedi come possono essere ottimizzati.

Ci sono molte operazioni che possono essere lente e se le fai 3000 volte, aggiunge. A volte, è utile riutilizzare i dati tra i test (anche se non si suppone che lo faccia nei test unitari, potrebbe essere necessario se questo è quello che serve per far funzionare i test a una velocità accettabile).

Tempo i test. Di solito, il 90% di questi viene eseguito quasi istantaneamente e l'ultimo 10% richiede quasi tutto il tempo. Trova quei 10% e guarda cosa stanno facendo.

Eseguire il codice tramite un profiler e annotare dove viene trascorso il tempo. Indovinare è una perdita di tempo. Che cosa stai facendo il corridore di test non ha senso. Invece di indovinare, scoprire cosa sta facendo. Allora saprai come accelerarlo.

2

Bene, non so cosa stia facendo il vostro test unitario, ma è necessario chiedersi perché ci vogliono 20 minuti. Dalla mia esperienza ci sono spesso molti test che vengono eseguiti in pochi millisecondi e pochi test che costituiscono il resto del tempo richiesto. Molto spesso questi sono test che coinvolgono IO/rete/DB-Stuff. Ad esempio, se trascorri molto tempo in attesa a causa della latenza della rete, potresti prendere in considerazione l'esecuzione dei test in parallelo.

Si potrebbe dare la caccia a quei test e cercare miglioramenti. Ma rendere i test più veloci non migliora il tuo codice reale. Si potrebbe voler verificare i test che richiedono molto tempo perché la classe sottoposta a test non è ottimale. Individuare e migliorare situazioni come queste molto probabilmente renderà il tuo prodotto migliore/più veloce.

13

Alcune idee:

  • utilizzare un quadro beffardo per evitare di colpire basi di dati (o effettuare chiamate di servizi web, ecc).
  • Se si sta eseguendo lo stesso o simile set-up per molti test individuali, provare a farlo in una configurazione di test fixture (ad esempio qualcosa che viene eseguito una volta per fixture piuttosto che una volta per test).
  • Credo che alcuni quadri di prova consentono di eseguire i test in parallelo
+0

+1 per beffardo. –

+1

I due migliori sistemi di derisione che ho trovato sono Mockito e JMockit. JMockit consente di prendere in giro metodi statici e finali, e Mockito è davvero comodo per i mordi veloci. Combinando le due opere GRANDI. – Epaga

+0

Upvoted per derisione ... – ABcDexter

10

Si consiglia di dividere il test di unità in suite. La tua applicazione è modularizzata? Quante volte hai davvero bisogno di eseguire tutti i test?Sarebbe accettabile se i tuoi sviluppatori eseguissero solo i test unitari rilevanti per il proprio modulo, e avevi la serie di test eseguiti ogni notte e/o su un CI?

Ci sono delle unit test particolari che sono molto complessi (lo so, sto scivolando in test funzionali e di integrazione qui, ma la linea è a volte confusa), ma può qualcuno di loro essere eseguito su un sanità mentale - livello durante lo sviluppo e poi essere eseguito completamente sull'IC?

Edit: Solo per i calci, io descrivere brevemente le routine di prova in uno dei miei precedenti progetti

Prima di tutto, la crescita del sistema di test è stato organico, il che significa che non è stato originariamente previsto fuori ma è stato modificato e modificato mentre è cresciuto. Quindi non era perfetto, e c'erano alcune convenzioni di denominazione che erano diventate apocrife con il tempo.

  • A livello di sviluppatore, abbiamo utilizzato una semplice suite di test di due minuti denominata CheckIn, che ha verificato che il codice fosse sufficientemente integro per unire il trunk.
  • Inoltre, abbiamo eseguito continuamente test di integrità su una macchina di CI. Queste erano versioni semplificate dei più complessi test di integrazione e funzionali, tutti i test unitari e tutti i test di regressione.
  • Le suite di test complesse (nel numero di ore) sono state eseguite da un giorno all'altro durante la notte e i risultati sono stati compilati la mattina successiva.

Test automatici: sono i matti del mutt.

+2

Questo è un cattivo consiglio. Dovresti eseguire quanti più test possibile per garantire che tutti i componenti funzionino indipendentemente e insieme. Non puoi mai presumere che un aspetto di un sistema sia 'solo funzionante' perché non stai sviluppando contro quell'area. –

+5

La cosa "insieme" verrà sottoposta ad altri test, ad esempio test di integrazione o qualcosa del genere. Il test unitario riguarda l'unità, quindi l'unità dovrebbe funzionare in modo indipendente. Non significa che non abbiamo bisogno di testare tutte le cose insieme, ma questo non è un test unitario, IMO. –

+1

@atc: Allora non sei mai stato in uno scenario in cui l'intera suite di test (test di integrazione e tutto) richiede diverse ore. Prima di impegnarsi, tutto ciò che è richiesto a uno sviluppatore è quello di garantire la sanità mentale, cioè la funzionalità di base. Questo test non dovrebbe richiedere più di un paio di minuti, o avrai sviluppatori inattivi. Fare in modo che tutto funzioni può essere fatto di notte. – mikek

5

Per iniziare con:
a) Ottieni le statistiche sul tempo di esecuzione dei test Junit. Potresti già essere a conoscenza di tali informazioni nei rapporti di prova.
b) Estrarre le prime 10 classi di test (in tempo utile) e provare a ridurre il tempo. Ciò che è necessario fare su base continuativa.
c) Cercare di ridurre il tempo di esecuzione rifattorizzando o addirittura modificando l'approccio del test.
Uno dei casi in cui mi sono imbattuto in una classe di test per i casi di test CRUD. Il caso di test degli aggiornamenti è stato prima creare il funtionlaity e quindi l'aggiornamento. Ma eravamo già testati in casi di test seprati. Quindi in questi casi è possibile concatenare il test casi come

@Test() 
    public void testCreate() throws Exception 
    {} 
    @Test(dependsOnMethods = "testCreate") 
    public void testAmend() throws Exception 
    {} 
    @Test(dependsOnMethods = "testAmend") 
    public void testDelete() throws Exception 
    {} 

Così risparmiate sul fare test di duplicazione.

d) Un altro esempio in cui ero in grado di ridurre il tempo significativamente era. Avevamo un sistema (ereditario) in cui ogni caso di test stava chiamando SetUp (Strating Spring Server ecc.) E dopo aver chiuso le risorse di sistema. Ciò richiedeva molto tempo, quindi l'ho refactored per avviare le risorse di coomon prima del test suit e dopo l'intera suite si fa poi chiudere quelli.

e) A seconda del progetto, possono essere altri colli di bottiglia che potrebbero essere necessari per appianare.

How to manage Build time in TDD

+0

buona lista di controllo. Grazie mille –

3

Ovviamente c'è qualcosa nel vostro test che richiede molto tempo.

A volte, non è possibile aggirare il test lento. Ad esempio, testando che Spring possa leggere tutti i suoi file di configurazione, testando il funzionamento della mappatura di ibernazione, quel tipo di cose.La cosa positiva di questi test è che devono solo essere eseguiti in un singolo test e quindi puoi prendere in giro tutto, ma puoi anche decidere di eseguirli come parte del test di integrazione e lasciare che il server di compilazione si preoccupi di ciò.

Il resto dei test è lento o perché stanno facendo IO o perché sono troppo vincolati alla CPU.

IO può essere molte cose. Il servizio Web e le chiamate al database possono essere sottratti e derisi e le poche chiamate reali che devi fare possono essere spostate nella fase di integrazione, se necessario. La registrazione può davvero rallentare le cose, specialmente con 3.000 casi di test. Direi che basta disattivare completamente la registrazione e affidarsi al cervello e al debugger quando un test fallisce.

Ci possono essere casi in cui l'IO stesso è l'unità in prova. Ad esempio, se si sta testando la parte di un server database che scrive i dati della tabella su disco. In tal caso, cerca di conservare il più possibile IO nella memoria. In Java, molte delle astrazioni IO hanno implementazioni in memoria.

I test sui limiti della CPU sono disponibili in diverse versioni. La prova pura e il rendimento devono essere nella fase di test di integrazione. Se stai facendo girare un mucchio di thread per provare un veterinario a un bug di concorrenza, allora potresti spostare il big test nella fase di integrazione e mantenere una versione "leggera" nella tua normale suite di test.

Infine, il profiler è tuo amico. Potrebbe essere che le parti del tuo codice possano essere rese più efficienti e velocizzare sensibilmente i tuoi test.

+0

non è un'app web. quindi sono abbastanza fiducioso che non è legato all'IO. se sono davvero pochi i casi di test che impiegano incredibilmente a lungo. Immagino che il problema sia più facile da risolvere. grazie, proverò a jprofiler. –

4

Suppongo che tu abbia seguito tutti gli altri passaggi abituali come le chiamate al database di derisione, l'ottimizzazione delle fasi di impostazione del test ecc. E quello che sta facendo il test così a lungo è che hai 3000 test, non che i singoli test sono molto lento.

In tal caso, un approccio è quello di multithreading della corsa di prova. Test-NG lo supporta molto bene. La conversione dei test da junit a test-ng non è così difficile e deve essere eseguita solo una volta.

I test che devono essere eseguite in sequenza possono essere facilmente marcati:

@Test(sequential = true) 
public class ATest { 
    ... 

Su una macchina multi-core vedrete enormi miglioramenti in fase di esecuzione. Anche su un singolo core vedrai un buon miglioramento poiché alcuni thread attendono le operazioni io.

Vedi qui per i dettagli su come impostare questa funzione:

http://beust.com/weblog/archives/000407.html

Spero che questo aiuti.

....

Alcuni ulteriori suggerimenti - Non posso credere che non si sta utilizzando l'integrazione continua. Fidati di me 30 sviluppatori non sovraccaricheranno il tuo server CI. Anche se non riesci a comprare per CI, installa hudson sul tuo computer - ci vorranno 10 minuti per l'installazione e i benefici sono enormi. Chiedete al vostro manager che è peggio di ogni sviluppatore che sta aspettando che i test unitari vengano completati o che un server lo faccia per voi. Avere uno stupido cappello da indossare per la persona che ha rotto la build è di solito sufficiente per convincere gli sviluppatori a eseguire i loro test unitari.

Se la qualità dei check-in è davvero un grosso problema (non dimenticare che il check-in può sempre essere ripristinato) considera Teamcity - esegue i test e non esegue il commit del codice se i test falliscono.

Infine, un'opzione che può essere adatta al caso d'uso è clover and bamboo. L'ultima versione tiene traccia di quale codice viene testato da quali test e quando viene apportata una modifica esegue solo i test rilevanti. Questo è potenzialmente molto potente.

Ma ricorda che strumenti intelligenti come test-ng, teamcity e trifoglio ti faranno solo arrivare fino a qui - i buoni test non si scriveranno da soli!

Per riassumere la mia soluzione è provare tutte o alcune delle seguenti operazioni:

  1. Ottimizzare i test - prende in giro, situazione comune ecc
  2. Esegui i test in parallelo
  3. ottenere qualcos'altro a esegui i test per te: rendilo un'attività offline usando hudson o simili
  4. Esegui solo i test che devono essere eseguiti - ordinali in pacchetti o usa il trifoglio e il bambù.
+0

Sembra promettente. grazie. –

1

Sono d'accordo con Pablojim. Parallelizza i tuoi test. Stiamo utilizzando la clearcase e il trasferimento di tutto dai viewerver rallenta davvero le cose. Quando abbiamo eseguito il parallelismo su un duelcore abbiamo ottenuto una prova di prova più breve, 6-8 volte più veloce.

Stiamo utilizzando il framework CPPUnit e abbiamo appena aggiunto uno script python per avviare diverse suite di test su thread diversi.

Abbiamo anche usato clearmake per parallelizzare il processo di compilazione. Il prossimo passo sarà probabilmente quello di parallelizzare i test sui client degli sviluppatori.

+0

utilizziamo anche clearcase, clearcase ha alcun supporto per la creazione di progetti java? –

+0

Non ho esperienza con la creazione di app Java con clearmake, ma un rapido google ha fornito: http://publib.boulder.ibm.com/infocenter/cchelp/v7r0m0/index.jsp?topic=/com.ibm.rational.clearcase. books.cc_build_unix.doc/using_clearcase_build_tools_with_java.htm – tombjo

1

Spostare l'intera suite di test in un sistema di motore Integrazione COntinuous, in modo che gli sviluppatori non debbano eseguirli tutti ogni volta. Tali sistemi hanno molta più pazienza degli sviluppatori.

+0

ho suggerito questo approccio. tuttavia, il gestore ha paura che gli sviluppatori diventino pigri e nessuno esegua mai i casi di test prima di eseguire il codice. per garantire la qualità del ramo principale, possiamo impostare una build continua per il ramo di tutti. abbiamo circa 30 sviluppatori, che potrebbero sovraccaricare il server CI. ma, sì, vale la pena provare e vedere come funziona davvero. –

+0

Se gli utenti non riescono a verificare il codice prima di effettuare il check-in, allora il tuo manager dovrebbe avere a che fare con esso. Non suggerirò di avere un manager pigro;) IntelliJ/TeamCity ha una funzione che ti permette di eseguire la compilazione sul server e di controllare solo il codice se tutti i test passano. L'integrazione continua consente di risparmiare un sacco di tempo, dovresti davvero averne uno. –

+0

@James, dai un'occhiata a Hudson. È abbastanza semplice da ridimensionare in quanto consente di utilizzare i computer slave per creare.Se al momento è un problema eseguire unittests, fallo e poi lascia che quelli che rompono la build debbano comprare qualcosa per l'intero team (solo qualcosa di piccolo). –

4

Si sta utilizzando fork="yes" nella chiamata junit? In tal caso, assicurati di impostare forkMode="once", altrimenti l'attività junit avvierà una nuova VM per ciascuna classe di TestCase. Con 3000 test unitari, ciò farà una differenza drammatica.

http://ant.apache.org/manual/Tasks/junit.html

+0

Ho visto questo. il fatto è che non stiamo usando Testunità jentr di formiche, ma uno personalizzato in un barattolo. Non so come è implementato, in quanto il codice sorgente non è disponibile per noi :(. –

+0

Ricordati che Eclipse con un decompilatore come jadclipse può aiutarti a sbirciare in un vaso del genere. –

+0

guardando il file build.xml, TestRunner non avvierà un jvm per ogni testcase grazie per aver raccomandato jadclipse, sono sicuro che tornerà utile in futuro –

1

vorrei affrontare questo come si farebbe con qualsiasi altro problema di prestazioni:

  1. Non fare ipotesi su ciò che il problema è
  2. Analizzare l'esecuzione di test con un profiler per determinare gli hotspot
  3. Analizzare i punti caldi uno alla volta, riprovando dopo ogni cambio di codice.

Si potrebbe scoprire che è necessario scavare in quel corridore di prova alla fine. È possibile utilizzare uno strumento di decompilazione come cavaj per generare codice sorgente dal file di classe (anche se sarà più difficile da leggere rispetto al codice originale, ovviamente). Potresti scoprire che qualcosa nell'esecuzione del test runner sta influenzando le prestazioni. Ad esempio, hai già menzionato la lettura dei file di configurazione XML come un'attività che esegue il test runner - questo è qualcosa che potrebbe potenzialmente influire sulle prestazioni.

Un'altra area in cui è possibile riscontrare problemi di prestazioni è in classi di test case 'base' personalizzate. Questi tendono ad essere cose che aggiungono molta convenienza, ma può essere difficile ricordare che i comportamenti che aggiungono convenienza potrebbero essere ammortizzati su test da 10.000 in un progetto di grandi dimensioni, indipendentemente dal fatto che ciascun test abbia o meno bisogno del comportamento pratico.

1

vi suggerisco di avere due generazioni (incrementale che viene eseguito su tutti i check-in, e una generazione completa che viene eseguito durante la notte)

Le piste incrementali test più brevi in ​​circa 7 minuti, mentre la generazione completa viene eseguito tutti i test più a lungo in meno di 40 minuti.

Clearcase incoraggia un incubo ramificato, ma dovresti essere in grado di avere due build per sviluppatore. Vorrei mettere in discussione il valore di avere ogni sviluppo sulla propria branca poiché ritengo che ci sia qualche vantaggio nel far lavorare gli sviluppatori (in coppia o più) sullo stesso ramo.

Nota: un server di integrazione continua può disporre di un numero qualsiasi di agenti e se non è possibile acquistare più di un server, è possibile utilizzare i PC come agenti build. (Devi avere almeno 30 di questi)

+0

hai ragione. dev lavora sul proprio ramo solo gli sviluppatori che risolvono bug lavorano sulle loro filiali Gli sviluppatori di funzioni lavorano sullo stesso ramo. –

1

Ecco l'approccio che vorrei prendere.

  1. Rivedi i test case, cerca eventuali test ridondanti. Con 3000 test, è probabile che si tratti di parti doppie e quintuple che non devono necessariamente essere.
  2. Scegli i tuoi "canarini". Questi sono i test che vuoi eseguire sempre, quelli che annusano il pericolo in altre parti. Sono più che probabili i casi di test di livello superiore che testano le interfacce API pubbliche utilizzate tra i componenti. Se uno di questi fallisce, puoi entrare ed eseguire l'intera suite di test per il componente.
  3. Inizia la migrazione a un framework come TestNG e inizia a classificare i casi di test, quindi esegui solo la classifcation di ciò su cui stai lavorando con test completi notturni.
0

Il modo più efficace per accelerare una grande suite di test è quello di eseguire in modo incrementale, in modo che solo i test che toccano codice cambiati dall'ultimo ciclo di prova vengono rieseguita. Dopo tutto, i test più veloci saranno sempre quelli che sono non eseguiti. 8 ^)

La parte difficile è in realtà farlo funzionare. Attualmente sto lavorando a test incrementali per JUnit 4, che fa parte dello strumento "JMockit Coverage" nel toolkit di test degli sviluppatori di JMockit. È ancora immaturo, ma credo che funzionerà bene.

0

L'accesso al database e la latenza di rete potrebbero essere un'area da esaminare. Se stai eseguendo un sacco di accesso al database nei tuoi test di integrazione, potresti voler esplorare usando un database in memoria come HSQL, H2 o Derby invece di un database "reale" come Oracle. Se stai usando Hibernate, dovrai anche modificare le impostazioni nelle tue configurazioni di Hibernate per usare il dialetto specifico per quel DB (ad esempio, HSQLDialect invece di OracleDialect). Era in un progetto una volta in cui ogni build completa avrebbe dovuto abbandonare e ricreare un intero schema Oracle ed eseguire numerosi test db sulla rete e talvolta richiedere fino a 20 minuti, e quindi si trova qualcuno che ha effettuato il check in e le cose sono andate a posto ancora. :(

Idealmente si vorrebbe avere un solo script DB utilizzabile per entrambi i database, ma si potrebbe finire per dover sincronizzare due diversi script di creazione del DB: uno per la produzione, uno per i test di integrazione.

DB nella stessa JVM vs DB attraverso la rete: potrebbe fare la differenza.