CodeGym /Java Blog /Random-IT /Unit test in Java con JUnit
John Squirrels
Livello 41
San Francisco

Unit test in Java con JUnit

Pubblicato nel gruppo Random-IT

Che cos'è il test unitario in Java?

Prima di iniziare a imparare JUnit in Java, esaminiamo brevemente cos'è il test unitario e perché è così popolare (se conosci già queste cose, vai a "Come scrivo un test JUnit in Java?"). I test unitari in Java rendono lo sviluppo di software su larga scala molto più efficiente e semplice. Può aiutare sia gli individui che i team a ridurre innumerevoli ore di debug e semplificare enormemente il processo di collaborazione. Unit test in Java con JUnit - 1

https://junit.org/junit4/

L'idea essenziale del test unitario è questa: scrivere test atomici di singole funzionalità (chiamati test unitari) e aggiungere lentamente più funzionalità dopo il test e assicurarsi che i precedenti funzionino. È un'idea estremamente semplice ma potente. Come esempio di come potrebbe apparire questo processo, immagina di costruire una calcolatrice scientifica virtuale. Oltre agli apparenti operatori aritmetici ( +, -, x, %), questa calcolatrice avrebbe funzionalità avanzate che richiedono altre funzionalità secondarie per funzionare al suo interno. Per calcolare gli esponenti, la tua calcolatrice deve essere in grado di moltiplicare correttamente. Quindi un approccio di test unitario per costruire e testare questo calcolatore sarebbe:
  • Scrivi una funzione di addizione. Provalo attentamente, cambialo, ripeti finché non funziona.
  • Fai lo stesso per le funzioni di sottrazione, moltiplicazione, divisione.
  • Usa questi operatori di base per scrivere funzioni di operatore più avanzate come gli esponenti, quindi testa anche quelle funzioni.
Ciò garantisce che le funzionalità che si basano su altre funzionalità secondarie più piccole non solo funzionino correttamente di per sé, ma non contengano funzionalità secondarie difettose al loro interno. Ad esempio, se sto testando la funzione esponente e qualcosa va storto, so che il bug probabilmente non è nella funzione secondaria di moltiplicazione, perché la funzione di moltiplicazione è già stata ampiamente testata. Ciò elimina notevolmente la quantità totale di codice di cui ho bisogno per risalire e ispezionare per trovare il bug. Si spera che questo banale esempio chiarisca come è strutturato il processo di pensiero attorno al test unitario. Ma come interagisce il test unitario con il resto del processo di sviluppo del software? E se hai funzionalità ancora più complesse, che devono poter lavorare e comunicare insieme? I test unitari non sono sufficienti per garantire che funzionalità così complesse possano funzionare correttamente insieme. In realtà, è solo il primo passo dei quattro livelli di test del software (uso le lettere maiuscole perché mi riferisco allo standard del settore o all'approccio più comune al test del software). Gli ultimi tre passaggi sonoTest di integrazione , test di sistema e test di accettazione. Tutti questi probabilmente significano esattamente quello che pensi che facciano, ma lasciami chiarire: il test di integrazione è ciò che faremmo per garantire che quelle "caratteristiche complesse" sopra menzionate interagiscano correttamente tra loro. (ad esempio, assicurarsi che la calcolatrice sia in grado di gestire “3 + 7 * 4 - 2”) Il test di sistema consiste nel testare il progetto complessivo di un particolare sistema; ci sono spesso più sistemi di funzionalità complesse che lavorano insieme in un prodotto, quindi li raggruppi in sistemi e li test individualmente. (ad esempio, se stessi costruendo una calcolatrice grafica, prima dovresti costruire il "sistema" aritmetico per gestire i numeri, verificando fino a quando non funziona come previsto, quindi costruiresti e testerai il "sistema" grafico per gestire il ritiro, come sarebbe basato sul sistema aritmetico). Il test di accettazione è un test a livello di utente; è verificare se tutti i sistemi possono lavorare in sincronia per creare un prodotto finito pronto per essere accettato dagli utenti (ad esempio, utenti che testano la calcolatrice). Gli sviluppatori di software a volte possono ignorare questa fase finale del processo, poiché le aziende spesso richiedono ad altri dipendenti di distribuire i test degli utenti (beta) separatamente.

Come scrivo un test JUnit in Java?

Ora che hai un'idea più chiara dei vantaggi e dei limiti dei test unitari, diamo un'occhiata al codice! Useremo un popolare framework di test Java chiamato JUnit (un altro popolare è TestNG, che puoi anche usare se vuoi. Sono molto simili, sintatticamente; TestNG è ispirato a JUnit). Puoi scaricare e installare JUnit qui . Per questo codice di esempio, continueremo dall'esempio di "calcolatrice scientifica" che stavo menzionando in precedenza; è piuttosto semplice avvolgere la testa e il codice di test è semplicissimo. La pratica convenzionale è scrivere classi di test separate per ciascuna delle tue classi, quindi è quello che faremo. Supponiamo che a questo punto abbiamo un Math.javafile con tutte le funzioni matematiche (compreso Math.add), e stiamo scrivendo unMathTests.javafile nello stesso pacchetto. Ora impostiamo le istruzioni di importazione e il corpo della classe: (POSSIBILE DOMANDA DEL COLLOQUIO JUnit: ti potrebbe essere chiesto dove posizionare il tuo test JUnit e se hai bisogno o meno di importare i tuoi file sorgente. Se stai scrivendo le tue classi di test nello stesso pacchetto di le tue classi principali, allora non hai bisogno di istruzioni di importazione per i tuoi file sorgente nella classe di test. Altrimenti, assicurati di importare i tuoi file sorgente!)

import org.junit.jupiter.Test;    //gives us the @Test header
import static org.junit.jupiter.api.Assertions.assertEquals; //less typing :) 

public class MathTests {
	//...
}
La prima dichiarazione di importazione ci fornisce l' @Testintestazione. Scriviamo '' @Test' direttamente sopra ogni definizione di funzione di test, in modo che JUnit sappia che si tratta di un singolo test unitario che può essere eseguito separatamente. Più avanti, ti mostrerò come eseguire test unitari specifici utilizzando questa intestazione. La seconda istruzione di importazione ci risparmia un po' di digitazione. La funzione JUnit primaria che utilizziamo per testare le nostre funzioni è to Assert.assertEquals(), che accetta due parametri (valore effettivo e valore previsto) e si assicura che siano uguali. Avere questa seconda dichiarazione di importazione ci consente solo di digitare '' assertEquals(...' invece di dover specificare ogni volta di quale pacchetto fa parte. Ora scriviamo un test case molto semplice per verificare che 2 + 2 è davvero 4!

import org.junit.jupiter.Test; // gives us the @Test header
import static org.junit.jupiter.api.Assertions.assertEquals; // less typing :) 


public class MathTests {
	@Test
	public void add_twoPlusTwo_returnsFour(){
	final int expected = 4;
	final int actual = Math.add(2, 2);
	assertEquals(“2+2 is 4”, actual, expected);
	}
}
Esaminiamo ciascuna delle cinque righe della funzione di test e cosa fanno: Riga 5: questa @Testintestazione specifica che la definizione della funzione di seguito add_twoPlusTwo_returnsFour()è effettivamente una funzione di test che JUnit può eseguire separatamente. Riga 6: questa è la firma della funzione per il nostro caso di test. I casi di test sono sempre molto singolari; testano solo un esempio specifico, come 2+2=4. È convenzione nominare i casi di test nella forma " [function]_[params]_returns[expected]()", dove [function]è il nome della funzione che stai testando, [params]sono i parametri di esempio specifici che stai testando ed [expected]è il valore di ritorno previsto della funzione. Le funzioni di test hanno quasi sempre un tipo di ritorno ' void' ' perché il punto principale dell'intera funzione è l'esecuzioneassertEquals, che verrà restituito alla console indipendentemente dal fatto che il test sia stato superato; non hai bisogno di altri dati da restituire da nessuna parte. Riga 7: Dichiariamo una finalvariabile ' ' del tipo restituito di Math.add (int), e la chiamiamo 'previsto' per convenzione. Il suo valore è la risposta che ci aspettiamo (4). Riga 8: Dichiariamo una finalvariabile ' ' del tipo restituito di Math.add (int), e la chiamiamo 'actual' per convenzione. Il suo valore è il risultato di Math.add(2, 2). Linea 9: La linea d'oro. Questa è la linea che confronta effettivo e previsto e ci dice che abbiamo superato il test solo se sono uguali. Il primo parametro superato “2+2 is 4” è una descrizione della funzione di test.

Cosa succede se la mia funzione genera un'eccezione?

Se il tuo esempio di test specifico dovrebbe generare un'eccezione invece di affermare che un valore effettivo e previsto sono uguali, JUnit ha un modo per chiarirlo nell'intestazione @Test. Diamo un'occhiata a un esempio qui sotto. Supponendo di avere una funzione in Math.javacalled Math.divide, vogliamo assicurarci che gli input non possano essere divisi per 0. Invece, provare a chiamare Math.divide(a, 0)qualsiasi valore 'a' dovrebbe generare un'eccezione ( ArithmeticException.class). Specifichiamo così nell'intestazione come tale:

import org.junit.jupiter.Test; // gives us the @Test header
import static org.junit.jupiter.api.Assertions.assertEquals; // less typing :) 


public class MathTests {
	@Test (expectedExceptions = ArithmeticException.class)
	public void divide_byZero_throwsException() throws ArithmeticException{
	Math.divide(1, 0);
	}
}
Puoi avere più di un'eccezione per expectedExceptions, assicurati solo di usare parentesi e virgole per elencare le tue classi di eccezione, come ad esempio:

expectedException = {FirstException.class, SecondException.class, … }

Come posso eseguire i miei test JUnit in Java?

Come aggiungere JUnit a IntelliJ: https://stackoverflow.com/questions/19330832/setting-up-junit-with-intellij-idea Puoi eseguire il tuo progetto come faresti normalmente con i test. L'esecuzione di tutti i test in una classe di test li eseguirà in ordine alfabetico. In JUnit 5, puoi aggiungere una priorità ai test aggiungendo un @Ordertag. Un esempio:

@TestMethodOrder(OrderAnnotation.class)
public class Tests {
…
@Test
@Order(2)
public void a_test() { … }

@Test
@Order (1)
public void b_test() { … }
…
}
Anche se a_test()viene prima b_test()in ordine alfabetico e nel codice, b_test()verrà eseguito prima a_test()qui, perché 1 viene prima di 2 in Order. Quindi questo è tutto per le basi di JUnit. Ora, affrontiamo un paio di domande comuni di intervista JUnit e impariamo qualcosa in più su JUnit lungo la strada!

Domande di intervista JUnit (informazioni aggiuntive)

Qui ho raccolto le domande delle interviste JUnit più popolari. Se hai qualcosa da aggiungere, sentiti libero di farlo nei commenti qui sotto. D: Quale metodo puoi chiamare nel tuo metodo di test per fallire automaticamente un test? A: fail(“descrizione errore qui!”); D: Stai testando una classe di cani; per testare un oggetto Dog, è necessario crearne un'istanza prima di poter eseguire i test su di esso. Quindi scrivi una funzione setUp () per istanziare il cane. Vuoi eseguire questa funzione solo una volta durante tutti i test. Cosa devi mettere direttamente sopra la firma della funzione setUp() in modo che JUnit sappia eseguire setUp() prima di eseguire i test? R: @BeforeClass (@BeforeAll in JUnit 5) D:Quale deve essere la firma della funzione setUp() descritta sopra? A: vuoto statico pubblico. Qualsiasi funzione con @BeforeClass (@BeforeAll in JUnit 5) o @AfterClass (@AfterAll in JUnit 5) deve essere statica. D: Hai finito di testare la classe Cane. Scrivi la funzione void tearDown() che ripulisce i dati e stampa le informazioni sulla console dopo ogni test. Vuoi che questa funzione venga eseguita dopo ogni singolo test. Cosa devi mettere direttamente sopra la firma della funzione tearDown() in modo che JUnit sappia eseguire tearDown() dopo aver eseguito ogni test? R: @After (@AfterEach in JUnit 5)
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION