🕐 Tempo di lettura: 3 minuti
Vedere la percentuale di code coverage che sale ci fa dormire sereni. Ma il nostro sistema è davvero testato o lo è solo apparentemente?
📌 La branch coverage
Osserviamo lo screenshot qui sotto, in particolare la riga dove viene invocato il Predicate isNotStringOrNotBlankString. Abbiamo true hits: 161, false hits: 0.
Cosa ci dice quello zero? Che, nonostante i 333 passaggi totali, non abbiamo mai attraversato il ramo in cui quel predicato fallisce. Questo è sintomo solitamente di una delle seguenti due cose:
- test incompleti: ci siamo dimenticati di testare un caso di input (es. una stringa vuota o un oggetto particolare)
- codice morto: quella condizione è logicamente impossibile da raggiungere a causa di controlli precedenti. Se un ramo non viene mai preso, perché tenerlo?
La branch coverage è il miglior "potatore" di codice inutile che si possa avere!
📌 La trappola combinatoria
Arriviamo al secondo punto: testare solo tramite integration test è un tentativo di risolvere un problema di calcolo combinatorio con la forza bruta.
Immaginiamo un sistema con L layer e M metodi, dove ogni metodo ha delle diramazioni logiche (branch). Se proviamo a testare tutto tramite integrazione, le combinazioni di input che devono attraversare i vari layer crescono in modo esponenziale. Testare ogni scenario possibile provando ogni singola strada partendo sempre dalla porta principale è computazionalmente irrealistico.
📌 I test di unità sono il "cheat code"
- dalla complessità esponenziale alla lineare: testando il singolo metodo (come quello nello screen), riduciamo drasticamente il numero di test necessari. Se ogni pezzo è verificato in ogni suo ramo, la loro combinazione è matematicamente più solida
- leggerezza estrema: mentre il test di integrazione sta ancora "tirando su" il contesto Spring o i container Docker, i test di unità hanno già verificato millemila branch
- precisione chirurgica: se questo test fallisce, sappiamo esattamente che il problema è nel predicato alla riga 56. Se fallisce l'integrazione... beh, buona caccia al tesoro tra i vari layer
Hey, non fraintendetemi, non dico che i test d'integrazione non servano: sono quelli che ci dicono che il sistema gira realmente in condizioni realistiche. Ma sono i test di unità a validare la logica e dirci se ogni pezzetto funziona davvero. Ignorare la branch coverage e affidarsi solo a flussi end-to-end significa guardare il sistema da lontano, dove apparentemente tutto funziona, ma dando per scontato quel che succede dentro.
Alla prossima pillola! ☕
