Quante volte, nella vita di uno sviluppatore, ci si è trovati nella condizione di dover scrivere un test con una logica identica a quella di molti altri scritti in precedenza, in cui cambiavano solo alcuni dettagli come i parametri?
Una soluzione per evitare la ripetitività di queste situazioni è ora disponibile grazie a Junit 5.
Da Junit4 a Jupiter
JUnit è uno dei framework di test più utilizzati nel mondo Java.
Nel 2017, a undici anni dall’ultima major release, è stata rilasciata la versione 5, nota col nome di Jupiter. A differenza delle versioni precedenti, il framework è stato suddiviso in tre artefatti:
• Junit Jupiter, per contenere le API per definire i test
• Junit Vintage, per garantire la retrocompatibilità con la versione precedente di JUnit
• Junit Platform, per l’esecuzione dei test
Le novità riguardanti la parte di refactoring delle API di Jupiter sono spiegate nella documentazione ufficiale, mentre le più interessanti riguardano l’automatizzazione nella scrittura dei test, con l’introduzione di due macrocategorie: i test parametrizzati e i test dinamici.
Quest’ultimi si differenziano tra loro per il formato delle risorse usate come input e per alcune caratteristiche tecniche, ma l’adozione dell’una una o dell’altra non impatta significativamente sul risultato finale.
Vantaggi di JUnit
Se in passato era necessario scrivere molte righe di codice, di solito ripetitivo, per definire la propria “batteria” di test, oggi questo può essere evitato mediante l’utilizzo di JUnit5, a patto di un overhead nella costruzione della codebase durante la parte iniziale del ciclo di vita del software.
Sul lungo periodo ne derivano molti vantaggi:
• Stop al copia/incolla da parte degli sviluppatori;
• Aumento la manutenibilità del codice portando a fattore comune la maggior parte delle logiche di test;
• Adozione di un approccio test-driven
• Riduzione dei tempi di scrittura;
• Si evita la noia che gli sviluppatori provano nella stesura dei test, rendendoli più propensi a testare il proprio codice.
Caso d’uso di JUnit
In un contesto di business molto ampio, come può essere quello del settore bancario, si sviluppano di frequente servizi per un Enterprise Service Bus (ESB). Ogni servizio richiede una struttura del codice, e quindi anche dei test, molto standardizzata. Generalmente i test hanno lo scopo di verificare sempre le stesse casistiche (successo, fallimento, timeout ecc.) e variano solo per i parametri con cui devono essere eseguiti: è il caso d’uso perfetto per spiegare i vantaggi dei test dinamici.
Accade di frequente che qualche casistica non testata di un servizio sia proprio quella che nasconde un bug. Ciò accade perché chi scrive i test è distratto e si è dimenticato di scrivere tutti i test, o perché durante un copia/incolla ha sbagliato a riportare un valore. Se gli fosse stata data la possibilità di privilegiare l’aspetto funzionale rispetto a quello implementativo, oltre ad evitare il copia/incolla, probabilmente sarebbe stato più minuzioso nel rispettare le verifiche su tutte le casistiche.
Proprio in queste situazioni le nuove funzionalità di JUnit rendono al meglio. Se lo sviluppatore avesse potuto scrivere poche informazioni essenziali (nome del test, risorse e risultati previsti), avrebbe visto con molta più chiarezza il suo scopo, in quanto svincolato dall’implementazione. Con Jupiter è possibile proprio tramite i test dinamici, che permettono di definire delle risorse di input che verranno utilizzate da delle procedure di test; in questo specifico caso d’uso tali procedure sarebbero standardizzate e comuni a tutti i servizi ESB.
Il formato dei file sorgente supportato ufficialmente per questa funzionalità di JUnit è il CSV, ma tramite un parsing personalizzato si può utilizzare un qualsiasi formato.
Liberandosi dell’implementazione e del vincolo sul formato di input, si può permettere anche a persone con profili meno tecnici di definire le risorse dei test (ad esempio tramite un foglio Excel o un file YAML), centralizzando in un unico punto tutte le informazioni.
Tutto questo porta ad una nuova metodologia di definizione di un test: oltre a non essere più necessario uno sviluppatore, sarà sufficiente definire una serie di valori all’interno dell’input rispettando il modello stabilito.
Quando utilizzare Junit5
È stato illustrato un caso in cui l’adozione di test dinamici o parametrizzai porta dei vantaggi concreti, ma questa soluzione è davvero utile in tutti i casi?
Come per ogni scelta nel mondo dell’informatica, prima di stabilire se un nuovo framework debba essere adottato, è necessaria una valutazione del rapporto tra costi e benefici.
I test parametrizzati o dinamici sono utili solo nel caso in cui il numero di test sia elevato e accomunato da una forte standardizzazione. Per un numero di test ridotto, con logiche diverse, la definizione dell’infrastruttura per l’utilizzo dei test dinamici rappresenterebbe un costo eccessivo rispetto alla scrittura dei test stessi.
Inoltre, all’attuale versione stabile dei plugin di test di default di Maven (Surefire e Failsafe), il logging generato da una struttura dinamica di test rende più complicato il debug, perché fa riferire i log ad un indice numerico e non al nome del test. Gli autori di Surefire e Failsafe, però, garantiscono che nella versione 3 dei plugin il problema sarà risolto. Lo si può già notare nella versione 3.0.0-M4. Si attende quindi la nuova versione stabile per dire addio al problema. Nel frattempo, framework di test come Allure possono sopperire al problema.
Nuovi scenari grazie a Jupiter
Sono disponibili nuovi strumenti per rendere il lavoro meno monotono e ripetitivo in fase di definizione dei test, a patto di saper individuare le casistiche in cui si può trarne un effettivo beneficio.
Si potrebbe addirittura pensare di generare interi progetti tramite un archetipo che impone un approccio test-driven, in cui gli analisti hanno definito a priori la struttura e le risorse di test. Allo sviluppatore rimarrebbe l’unico compito di scrivere la logica applicativa.
In questo modo si eliminerebbero le situazioni in cui ci si dimentica di scrivere il test, o in cui si fa pigramente copia/incolla del codice da un test all’altro.
Certo, risulta difficile pensare ad un simile approccio, e forse è addirittura impossibile un cambio così radicale di mentalità, ma da oggi grazie a JUnit5 immaginarlo è quantomeno possibile.