it-swarm.it

Le affermazioni o i test unitari sono più importanti?

Sia le affermazioni che i test unitari servono come documentazione per una base di codice e un mezzo per scoprire i bug. Le differenze principali sono che le asserzioni funzionano come controlli di integrità e vedono input reali, mentre i test unitari vengono eseguiti su input simulati specifici e sono test su una singola "risposta corretta" ben definita. Quali sono i meriti relativi dell'utilizzo di assert vs. unità test come mezzo principale per verificare la correttezza? Quale credi dovrebbe essere enfatizzato più pesantemente?

52
dsimcha

Assert sono utili per parlarti di lo stato interno del programma. Ad esempio, che le strutture di dati abbiano uno stato valido, ad esempio che una struttura di dati Time non conterrà il valore di 25:61:61. Le condizioni controllate dalle asserzioni sono:

  • Presupposti, che assicurano che il chiamante mantenga il suo contratto,

  • Postcondizioni, che assicurano che la chiamata mantenga il suo contratto, e

  • Invarianti, che assicurano che la struttura dei dati abbia sempre delle proprietà dopo il ritorno della funzione. Un invariante è una condizione che è una precondizione e una postcondizione.

nit test sono utili per parlarti di il comportamento esterno del modulo. Il tuo Stack potrebbe avere uno stato coerente dopo che è stato chiamato il metodo Push(), ma se la dimensione dello stack non aumenta di tre dopo che è stata chiamata tre volte, allora si tratta di un errore . (Ad esempio, il caso banale in cui l'implementazione errata di Push() controlla solo le affermazioni e le uscite.)

A rigor di termini, la principale differenza tra asserzioni e unit test è che i test unitari hanno dati di test (valori per far funzionare il programma), mentre gli assert no. Cioè, puoi eseguire i test unitari automaticamente, mentre non puoi dire lo stesso per le asserzioni. Per motivi di questa discussione, ho ipotizzato che tu stia parlando dell'esecuzione del programma nel contesto di test funzionali di ordine superiore (che eseguono l'intero programma e non guidano moduli come unit test). Se non stai parlando di test funzionali automatizzati come mezzo per "vedere input reali", allora chiaramente il valore risiede nell'automazione e quindi i test unitari vincerebbero. Se ne stai parlando nel contesto di test funzionali (automatizzati), vedi sotto.

Ci può essere qualche sovrapposizione in ciò che viene testato. Ad esempio, un postcondition di Stack potrebbe effettivamente affermare che la dimensione dello stack aumenta di uno. Ma ci sono limiti a ciò che può essere eseguito in quell'asserzione: dovrebbe anche verificare che l'elemento superiore sia ciò che è stato appena aggiunto?

Per entrambi, l'obiettivo è aumentare la qualità. Per i test unitari, l'obiettivo è trovare i bug. Per asserzioni, l'obiettivo è quello di rendere più semplice il debug osservando stati di programma non validi non appena si verificano.

Notare che nessuna delle tecniche verifica la correttezza. In effetti, se si eseguono test di unità con l'obiettivo di verificare che il programma sia corretto, è probabile che si verifichino test poco interessanti che si sa funzioneranno. È un effetto psicologico: farai qualunque cosa per raggiungere il tuo obiettivo. Se il tuo obiettivo è trovare bug, le tue attività lo rifletteranno.

Entrambi sono importanti e hanno i loro scopi.

[Come nota finale sulle asserzioni: per ottenere il massimo valore, è necessario utilizzarle in tutti i punti critici del programma e non in alcune funzioni chiave. Altrimenti, la fonte originale del problema potrebbe essere stata mascherata e difficile da rilevare senza ore di debug.]

43
Macneil

Quando si parla di affermazioni, tenere presente che possono essere disattivate con un semplice tocco di un interruttore.

Esempio di affermazione molto negativa:

char *c = malloc(1024);
assert(c != NULL);

Perché è così male? Perché nessun controllo degli errori viene eseguito se tale affermazione viene ignorata a causa della definizione di qualcosa come NDEBUG.

Un test unitario (probabilmente) sarebbe semplicemente segfault sul codice sopra. Certo, ha fatto il suo lavoro dicendoti che qualcosa è andato storto o no? Con quale probabilità malloc() non riesce durante il test?

Le asserzioni sono a scopo di debug quando un programmatore deve implicare che nessun evento "normale" causerebbe l'attivazione dell'affermazione. malloc() il fallimento è, in effetti, un evento normale, quindi non dovrebbe mai essere affermato.

Esistono molti altri casi in cui vengono utilizzate le asserzioni invece di gestire adeguatamente le cose che potrebbero andare storte. Questo è il motivo per cui le asserzioni hanno una cattiva reputazione e perché lingue come Go non le includono.

I test unitari sono progettati per dirti quando qualcosa che hai cambiato ha rotto qualcos'altro. Sono progettati per salvarti (nella maggior parte dei casi) dall'esaminare tutte le funzionalità del programma (tuttavia, i tester sono importanti per le versioni) ogni volta che effettui una build.

Non c'è davvero alcuna netta correlazione tra i due, tranne che entrambi ti dicono che qualcosa è andato storto. Pensa a un'affermazione come a un punto di interruzione in qualcosa a cui stai lavorando, senza dover usare un debugger. Pensa a un unit test come a qualcosa che ti dice se hai rotto qualcosa su cui non stai .

7
Tim Post

Sono entrambi strumenti usati per aiutare a migliorare la qualità generale del sistema che stai costruendo. Molto dipende dalla lingua che stai usando, dal tipo di applicazione che stai costruendo e da dove trascorri il tuo tempo. Per non parlare del fatto che ci sono alcune scuole di pensiero a riguardo.

Per iniziare, se stai usando una lingua senza una parola chiave assert, non puoi usare assert (almeno non nel modo in cui stiamo parlando qui). Per molto tempo, Java non aveva una parola chiave assert, e un certo numero di lingue ancora no. Il test unitario diventa quindi un po 'più importante. In alcune lingue le asserzioni vengono eseguite solo quando viene impostato un flag (di nuovo con Java qui). Quando le protezioni non sono sempre presenti, non è una funzione molto utile.

C'è una scuola di pensiero che dice che se stai "affermando" qualcosa, potresti anche scrivere un significativo blocco di eccezioni if/throw. Questo processo di pensiero deriva da molti asserzioni poste all'inizio di un metodo per garantire che tutti i valori siano nei limiti. Testare i tuoi presupposti è una parte molto importante di avere un postcondizionamento atteso.

Il test unitario è un codice aggiuntivo che deve essere scritto e gestito. Per molti questo è uno svantaggio. Tuttavia, con l'attuale ritaglio di framework di unit test disponibili, è possibile generare un numero maggiore di condizioni di test con un codice relativamente piccolo. Test e "teorie" parametrizzati eseguiranno lo stesso test con un gran numero di campioni di dati che possono scoprire alcuni bug difficili da trovare.

Personalmente scopro di avere più chilometraggi con i test unitari rispetto alle asserzioni di aspersione, ma ciò è dovuto alle piattaforme che sviluppo la maggior parte del tempo (Java/C #). Altre lingue hanno un supporto dell'asserzione più solido e persino "Design by Contract" (vedi sotto) per fornire ancora più garanzie. Se stessi lavorando con una di queste lingue, potrei usare il DBC più del test unitario.

http://en.wikipedia.org/wiki/Design_by_contract#Languages_with_native_support

5
Berin Loritsch