Java Language
Test unitario
Ricerca…
introduzione
Osservazioni
Quadri di test unitari
Esistono numerosi framework disponibili per il test delle unità all'interno di Java. L'opzione più popolare di gran lunga è JUnit. È documentato sotto il seguente:
JUnit4 - Tag proposto per le funzionalità di JUnit4; non ancora implementato .
Esistono altri framework di test unitari e sono disponibili documentazione:
Strumenti di test unitario
Ci sono molti altri strumenti usati per i test unitari:
Mockito - Mocking quadro; consente agli oggetti di essere imitati. Utile per simulare il comportamento previsto di un'unità esterna nel test di una determinata unità, in modo da non collegare il comportamento dell'unità esterna ai test dell'unità dati.
JBehave - BDD Framework. Consente di collegare i test ai comportamenti degli utenti (consentendo la convalida dei requisiti / degli scenari). Nessun tag dei documenti disponibile al momento della scrittura; ecco un link esterno .
Che cos'è il test unitario?
Questo è un po 'un primer. È per lo più messo perché la documentazione è costretta ad avere un esempio, anche se è inteso come un articolo stub. Se conosci già le nozioni di base sui test unitari, sentiti libero di andare avanti alle osservazioni, laddove vengono citati specifici framework.
Il test unitario garantisce che un determinato modulo si comporti come previsto. Nelle applicazioni su larga scala, garantire l'esecuzione appropriata dei moduli nel vuoto è parte integrante della garanzia della fedeltà dell'applicazione.
Considera il seguente (banale) pseudo-esempio:
public class Example {
public static void main (String args[]) {
new Example();
}
// Application-level test.
public Example() {
Consumer c = new Consumer();
System.out.println("VALUE = " + c.getVal());
}
// Your Module.
class Consumer {
private Capitalizer c;
public Consumer() {
c = new Capitalizer();
}
public String getVal() {
return c.getVal();
}
}
// Another team's module.
class Capitalizer {
private DataReader dr;
public Capitalizer() {
dr = new DataReader();
}
public String getVal() {
return dr.readVal().toUpperCase();
}
}
// Another team's module.
class DataReader {
public String readVal() {
// Refers to a file somewhere in your application deployment, or
// perhaps retrieved over a deployment-specific network.
File f;
String s = "data";
// ... Read data from f into s ...
return s;
}
}
}
Quindi questo esempio è banale; DataReader
i dati da un file, passa a Capitalizer
, che converte tutti i caratteri in maiuscolo, che viene quindi passato al Consumer
. Ma DataReader
è fortemente collegato al nostro ambiente applicativo, quindi rimandiamo il collaudo di questa catena fino a quando non saremo pronti a implementare una versione di prova.
Ora, supponiamo, da qualche parte lungo il percorso in una versione, per ragioni sconosciute, il metodo getVal()
in Capitalizer
cambiato dalla restituzione di una stringa toUpperCase()
a una stringa toLowerCase()
:
// Another team's module.
class Capitalizer {
...
public String getVal() {
return dr.readVal().toLowerCase();
}
}
Chiaramente, questo rompe il comportamento previsto. Ma, a causa degli ardui processi coinvolti con l'esecuzione di DataReader
, non lo noteremo fino alla prossima implementazione del test. Così trascorrono giorni / settimane / mesi con questo bug che si trova nel nostro sistema, e quindi il product manager lo vede e si rivolge immediatamente a te, il leader del team associato al Consumer
. "Perché sta succedendo questo? Che cosa avete cambiato voi ragazzi?" Ovviamente, sei senza tracce. Non hai idea di cosa sta succedendo. Non hai cambiato alcun codice che dovrebbe essere toccato da questo; perché è improvvisamente rotto?
Alla fine, dopo una discussione tra le squadre e la collaborazione, il problema viene tracciato e il problema risolto. Ma, si pone la domanda; come è stato possibile prevenirlo?
Ci sono due cose ovvie:
I test devono essere automatizzati
La nostra fiducia nel test manuale fa passare questo inosservato per troppo tempo. Abbiamo bisogno di un modo per automatizzare il processo in base al quale i bug vengono introdotti all'istante . Non 5 settimane da ora. Non più di 5 giorni. Non 5 minuti da ora. Proprio adesso.
Devi apprezzare che, in questo esempio, ho espresso un bug molto banale che è stato introdotto e inosservato. In un'applicazione industriale, con decine di moduli costantemente aggiornati, questi possono insinuarsi dappertutto. Correggere qualcosa con un modulo, solo per rendersi conto che lo stesso comportamento "aggiustato" era invocato in qualche modo altrove (internamente o esternamente).
Senza una convalida rigorosa, le cose si insinueranno nel sistema. E 'possibile che, se trascurata abbastanza lontano, questo si tradurrà in tanto lavoro in più cercando di risolvere i cambiamenti (e quindi fissare le correzioni, ecc), che un prodotto sarà effettivamente aumentare nel lavoro rimanente come lo sforzo è messo in esso. Non vuoi essere in questa situazione.
I test devono essere a grana fine
Il secondo problema notato nel nostro esempio precedente è la quantità di tempo impiegato per tracciare il bug. Il product manager ti ha inviato un ping quando i tester l'hanno notato, hai investigato e hai scoperto che il Capitalizer
restituiva dati apparentemente cattivi, hai fatto il ping al team di Capitalizer
con i tuoi risultati, hanno investigato, ecc. Ecc. Ecc.
Lo stesso punto che ho fatto sopra sulla quantità e la difficoltà di questo banale esempio qui. Ovviamente chiunque sia ragionevolmente esperto di Java potrebbe trovare rapidamente il problema introdotto. Ma spesso è molto, molto più difficile da rintracciare e comunicare problemi. Forse il team di Capitalizer
ti ha fornito un JAR senza fonte. Forse si trovano dall'altra parte del mondo e le ore di comunicazione sono molto limitate (forse alle e-mail che vengono inviate una volta al giorno). Può portare a bug che impiegano settimane o più a tracciare (e, di nuovo, potrebbero esserci molti di questi per una data release).
Al fine di mitigare questo, vogliamo test rigorosi il più fine possibile (si desidera anche test a grana grossa per garantire che i moduli interagiscano correttamente, ma non è questo il nostro punto focale qui). Vogliamo specificare rigorosamente come funziona tutta la funzionalità rivolta verso l'esterno (almeno) e testare tale funzionalità.
Inserisci il test unitario
Immagina se avessimo un test, in particolare assicurando che il metodo getVal()
di Capitalizer
restituito una stringa in maiuscolo per una determinata stringa di input. Inoltre, immagina che il test sia stato eseguito prima ancora che avessimo commesso un codice. Il bug introdotto nel sistema (ovvero toUpperCase()
viene sostituito con toLowerCase()
) non causerebbe problemi perché il bug non sarebbe mai stato introdotto nel sistema. Lo prenderemmo in un test, lo sviluppatore avrebbe (si spera) realizzato il proprio errore e sarebbe stata raggiunta una soluzione alternativa su come introdurre l'effetto desiderato.
Ci sono alcune omissioni qui riportate su come implementare questi test, ma quelli sono coperti nella documentazione specifica del framework (collegata nelle osservazioni). Si spera che questo sia un esempio del perché il test unitario è importante.