La Bufala della Copertura al 95%
Oggi parliamo di test ed in particolare del livello di copertura desiderato.
In alcuni ambienti è stata introdotta la legge del 95%: cioè un software è accettabile se il livello di copertura dei test è del 95%.
Come tutte le misure quantitative, anche il livello di copertura fa acqua da qualunque prospettiva lo si guardi.
Innanzi tutto che cosa si intende per livello di copertura dei test.
Copertura delle istruzioni
Secondo alcuni la copertura riguarda il numero di istruzioni eseguite per la verifica dei test: quindi il 95% vuol dire che i test nel loro complesso hanno riportato esito favorevole sempre eseguendo il 95% degli statement del codice.
Ancora una volta siamo ad una misura di basso livello: diremmo legata all'output e non all'outcome.
Ed il risultato è sempre lo stesso: la guerra contro le metriche.
Lo scopo è aggirare la regola che bloccherebbe il rilascio impiegando meno tempo possibile.
I test sono scritti a codice ultimato e non hanno lo scopo di verificarne la solidità funzionale, ma solo ottenere il bollino della copertura: gli sviluppatori alimentano i test con valori che potrebbero non aver alcun significato applicativo, ma hanno solo lo scopo di testare più istruzioni possibili.
Rimangono alcuni concetti strani da risolvere tipo: ha senso ottenere il bollino verde del test su un pezzo di codice che non viene più usato? Questo è un bene o un male?
E' chiaro che se l'ok dei test su un pezzo di codice non più utilizzato contribuisce al 95%, potrebbe capitare che un pezzo di codice nuovo, molto utilizzato, non venga testato visto che il 95% è già stato raggiunto.
Questo è uno dei tanti effetti di interferenza che si creano quando il test è slegato dal significato funzionale.
Copertura dei Percorsi
Per cercare di migliorare il senso dei test ad un certo punto, qualcuno ha proposto di misurare il numero di percorsi testati: ogni if, ogni ciclo, ogni branch di chiamata doveva essere considerato e la copertura andava calcolata sul prodotto cartesiano di questi comportamenti.
L'idea, in questo caso, è che per testare una specifica combinazione di percorsi, il test doveva essere alimentato con informazioni scelte con attenzione e non solo con lo scopo di coprire il numero maggiore di istruzioni.
Una questione simile è stata tratta qui: Il numero ciclomatico e gli unit test
Ora, per quanto l'approccio sia certamente più intelligente del precedente, rimane basato sull'output con tutti i limiti che ciò comporta e che abbiamo visto in precedenza.
Consigliati da LinkedIn
Analisi Statica ed Analisi Dinamica
Perchè i primi due approcci sono così diffusi?
Perchè gli strumenti automatici che li verificano sono semplici ed a basso costo.
Il problema è sempre lo stesso: l'analisi statica del codice è piuttosto semplice: un parser, un AST, ed un po' di regole...
Il problema è che uno strumento semplice di solito fornisce dati di scarso valore.
Ed infatti vediamo che gli sviluppatori si impegnano più a fregare lo strumento (e non fanno molta fatica) che a produrre qualcosa di realmente utile.
E peggio ancora, i manager ricevono report con metriche rassicuranti... ma poi si rendono conto che in produzione arrivano meno problemi, ma tutti di gravità elevata.
Quindi?
Immaginate invece di associare il concetto di 95% di copertura in modo che privilegi le azioni più eseguite, quelle più critiche, quelle più importanti...
Perchè, ancora una volta, non si può separare il valore da ciò che si vuole rilasciare: meglio copertura al 100% sul 50% del codice più critico che il 95% su tutto il codice.
Ancora una volta, l'idea è che se qualcosa deve scappare, deve essere qualcosa di poco importante, non una tragedia che danneggia la base dati o blocca gli utenti.
Adesso nasce il problema: come si fa a creare un processo che ci porti a questo risultato?
Il primo punto è che il valore emerga dai requisiti: cioè ciò che è importante e ciò che non lo è.
I criteri di accettazione delle user story sono uno strumento che si presta molto bene a svolgere questo ruolo: sono funzionali, sono legati a ciò che deve essere garantito come comportamento...
Per rendere efficace l'approccio, bisogna prevedere un processo di sviluppo che sia orientato in modo specifico a rilasciare solo ciò che effettivamente serve, cioè i criteri di accettazione... e questo modo di lavorare ha un nome ed un cognome.
Si chiama Test Driven Development.
La differenza tra test scritti a codice finito e test scritti durante la fase di programmazione come verifica dei vari step di avanzamento è proprio che i test del TTD non sono orientati alla copertura, ma sono orientati al funzionamento quindi più vicini all'outcome.
Concludo dicendo che in fase di modifica di certe funzionalità sarebbe utile introdurre anche test che falliscano se è presente il vecchio comportamento. In questo modo avremmo anche un reminder del codice che eventualmente dobbiamo togliere...