Devo scrivere test di unità automatizzate che falliscono quando il codice cambia?

6

Generalmente durante la scrittura di test di unità automatizzate (ad es. JUnit, Karma) mirano a:

  • copre tutte le condizioni al contorno
  • ottenere un alto livello di copertura.

Ho sentito qualcuno dire:

coverage and boundary conditions aren't enough for a unit test, you need to write them so they will break if the code changes.

In teoria mi suona bene, ma non sono sicuro di come applicarlo.

La mia domanda è: Devo scrivere test di unità automatizzate che falliscono quando il codice cambia? In tal caso, come.

    
posta hawkeye 09.01.2016 - 12:09
fonte

2 risposte

29

Il tuo scopo non dovrebbe essere quello di scrivere test unitari che falliscono quando cambia il codice , ma test unitari che falliscono quando il comportamento cambia. Qui, comportamento significa tutto ciò che un chiamante esterno del metodo vuole che faccia, come restituire la giusta risposta a una domanda o salvare la cosa giusta in un database. Come ottiene che è la sua implementazione interna, non il suo comportamento.

Testando il comportamento anziché l'implementazione, puoi rifattorizzare il codice per apportare miglioramenti e verificare immediatamente se hai accidentalmente cambiato il modo in cui si comporta eseguendo i test.

In realtà, non è possibile raggiungere perfettamente questo obiettivo. Se hai un metodo:

int add(int x, int y) {
    return x + y;
}

Puoi scrivere quanti test di unità vuoi, ma è estremamente improbabile che nessuno di essi fallirà se lo modifichi in:

int add(int x, int y) {
    if(x==10731 && y == -405571) {
        return 0;
    }
    return x + y;
}

Tuttavia, puoi prendere alcune misure sensate per ottenere il massimo dalla copertura comportamentale completa, come è pratico:

  • Come hai detto, pensa alle condizioni al contorno e ai casi d'angolo. Questi sono i luoghi in cui è più probabile che si verifichi un cambiamento accidentale del comportamento.
  • Pensa che la copertura linea per linea sia "necessaria, ma non sufficiente". Immagina di cercare di essere il più pigro possibile e ottenere una copertura completa riga per riga, e vedrai quanto è facile scrivere una serie inadeguata di test che seguono questo
  • Pensa al comportamento che il tuo metodo dovrebbe fornire e ai rami che può seguire. Idealmente dovresti testare che per ogni percorso attraverso la sua implementazione, fornisce tutto il comportamento che ci si aspetta.
  • Quando hai scritto una serie di test, chiedi "Esistono implementazioni del mio metodo che siano almeno altrettanto semplici di quelle esistenti, che supererebbero tutti i miei test, ma che hanno un comportamento sbagliato?" Questa è una buona regola per vedere se la copertura comportamentale è abbastanza buona.

    Come mostrato nel metodo di esempio add sopra, non puoi mai difendere completamente contro le modifiche al tuo metodo che aggiungono cose extra ad esso, rendendolo meno semplice. Ma molto più probabilmente, i bug stanno entrando di nascosto attraverso la modifica o la rimozione di parti, senza aggiungere complessità (perché, perché si dovrebbe refactoring in un modo che aggiunge alla sua complessità?). Quindi, aggiungendo la condizione "almeno altrettanto semplice", ottieni qualcosa di praticamente realizzabile.

risposta data 09.01.2016 - 12:54
fonte
0

Le risposte fornite - che questa è una cosa sbagliata da fare e viola ogni principio di buona sperimentazione - sono corrette.

Ma questa è programmazione. Ci sono sempre alcuni razionali ragionevoli anche per la richiesta più strana, e può essere produttivo considerarli.

Supponiamo che tu abbia un codice kernel critico, o parte della tua libreria SSH ad alta sicurezza, che assolutamente, in nessuna circostanza, dovrebbe mai cambiare il suo comportamento, nemmeno per alcuni minuscoli sottoinsiemi di valori infiniti, tutto che non potresti mai testare.

E di avere una società creata in modo tale che ci sia un dipartimento di garanzia della qualità responsabile della scrittura dei test unitari contro il codice fornito dal dipartimento di sviluppo, per assicurarsi che soddisfi i requisiti aziendali.

In questa situazione, ha senso dire "i percorsi critici del codice dovrebbero essere modificati solo da Dev quando QA è stato notificato con signoff dalla gestione superiore ." Qualsiasi modifica senza approvazione e notifica non è intenzionale e potrebbe essere dannosa.

In tal caso, un test che utilizza l'introspezione per ottenere un hash dei contenuti di ciascun metodo e confrontarlo con un hash memorizzato potrebbe essere legittimo. Non è possibile serializzare Method (), ma è possibile suddividere il metodo in serializzazione. Forse un approccio migliore è ottenere un hash del file stesso. O semplicemente confronta il file con un backup sicuro del file.

Ma probabilmente l'approccio migliore se il requisito è quello di fallire in qualsiasi modifica, è quello di verificare i registri del sistema di controllo della versione e vedere se sono state scaricate eventuali modifiche per i file rilevanti. Se sì, allora fallire.

    
risposta data 09.01.2016 - 23:41
fonte

Leggi altre domande sui tag