I test di asserzioni o unit test sono più importanti?

50

Sia le asserzioni che i test unitari servono come documentazione per un codebase e un mezzo per scoprire bug. Le principali differenze sono che asserisce di funzionare come controlli di integrità e di vedere gli input reali, mentre i test di unità funzionano su specifici input simulati e sono test contro una singola "risposta corretta" ben definita. Quali sono i meriti relativi dell'uso di assert contro test unitari come mezzo principale per verificare la correttezza? Quale ritieni debba essere sottolineato più pesantemente?

    
posta dsimcha 11.11.2010 - 05:19
fonte

3 risposte

43

Assertimenti sono utili per informarti su lo stato interno del programma . Ad esempio, che le tue strutture di dati hanno uno stato valido, ad es. Che una struttura di dati Time non manterrà il valore di 25:61:61 . Le condizioni controllate da asserti sono:

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

  • Post-condizioni, che assicurano che il destinatario mantenga il contratto, e

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

Test unitari sono utili per informarti su 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 essere stata chiamata tre volte, allora si tratta di un errore. (Ad esempio, il caso banale in cui l'implementazione push() non corretta controlla solo le asserzioni e le uscite.)

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

Ci può essere qualche sovrapposizione in ciò che viene testato. Ad esempio, una postcondizione di Stack può effettivamente affermare che la dimensione dello stack aumenta di uno. Ma ci sono dei limiti a ciò che può essere eseguito in quella affermazione: dovrebbe anche controllare che l'elemento superiore sia quello che è stato appena aggiunto?

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

Si noti che la tecnica verifica la correttezza. In effetti, se conduci un test unitario con l'obiettivo di verificare che il programma sia corretto, probabilmente otterrai un test poco interessante che sai funzionerà. È un effetto psicologico: farai qualsiasi 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.]

    
risposta data 11.11.2010 - 06:06
fonte
7

Quando parli di asserzioni, tieni presente che possono essere disattivate con un semplice clic.

Esempio di asserzione molto brutta:

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

Perché è così male? Perché nessun controllo degli errori viene eseguito se tale asserzione viene saltata a causa di qualcosa come NDEBUG in fase di definizione.

Un test unitario sarebbe (probabilmente) solo segfault sul codice sopra. Certo, ha fatto il suo lavoro dicendoti che qualcosa è andato storto o l'ha fatto? Quanto è probabile che malloc() fallisca sotto il test?

Le asserzioni sono a scopo di debug quando un programmatore deve implicare che nessun evento "normale" possa causare l'asserzione. malloc() fallito è, in effetti, un evento normale, quindi non dovrebbe mai essere affermato.

Ci sono molti altri casi in cui vengono usate asserzioni invece di gestire adeguatamente le cose che potrebbero andare storte. Questo è il motivo per cui le asserzioni ottengono una cattiva reputazione e perché le 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) da tutte le funzionalità del programma (tuttavia, i tester sono importanti per le versioni) ogni volta che crei una build.

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

    
risposta data 11.11.2010 - 05:45
fonte
5

Sono entrambi strumenti utilizzati per migliorare la qualità generale del sistema che stai creando. Molto dipende dalla lingua che si sta utilizzando, dal tipo di applicazione che si sta sviluppando e dal momento in cui è meglio impiegare il proprio tempo. Per non parlare di alcune scuole di pensiero a riguardo.

Per iniziare, se stai utilizzando una lingua senza una parola chiave assert , non puoi usare asserzioni (almeno non nel modo in cui stiamo parlando qui). Per molto tempo, Java non aveva una parola chiave assert , e un numero di lingue ancora non lo è. I test unitari diventano quindi un po 'più importanti. In alcune lingue le asserzioni vengono eseguite solo quando viene impostato un flag (di nuovo con Java qui). Quando le protezioni non sono sempre lì, non è una funzionalità molto utile.

C'è una scuola di pensiero che dice che se stai "asserendo" qualcosa, potresti anche scrivere un blocco di eccezione significativo con if / throw . Questo processo di pensiero proviene da molte affermazioni poste all'inizio di un metodo per garantire che tutti i valori siano vincolati. Testare le tue condizioni preliminari è una parte molto importante di avere una post-condizione prevista.

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

Personalmente trovo che ottengo più chilometri con i test unitari rispetto agli sprinkling assert, ma questo è dovuto alle piattaforme che sviluppo per la maggior parte del tempo (Java / C #). Altre lingue hanno un supporto di asserzione più robusto e persino "Design by Contract" (vedi sotto) per fornire ancora più garanzie. Se lavorassi con una di queste lingue, potrei utilizzare il DBC più del test dell'unità.

link

    
risposta data 11.11.2010 - 05:51
fonte

Leggi altre domande sui tag