Dati casuali nei test unitari?


136

Ho un collega che scrive unit test per oggetti che riempiono i loro campi di dati casuali. La sua ragione è che offre una gamma più ampia di test, dal momento che testerà molti valori diversi, mentre un test normale utilizza solo un singolo valore statico.

Gli ho dato una serie di ragioni diverse per questo, le principali sono:

  • valori casuali indicano che il test non è realmente ripetibile (il che significa anche che se il test può fallire casualmente, può farlo sul server di compilazione e interrompere la compilazione)
  • se è un valore casuale e il test fallisce, dobbiamo a) riparare l'oggetto eb) forzarci a testare quel valore ogni volta, quindi sappiamo che funziona, ma poiché è casuale non sappiamo quale sia il valore

Un altro collega ha aggiunto:

  • Se sto testando un'eccezione, i valori casuali non assicureranno che il test finisca nello stato previsto
  • i dati casuali vengono utilizzati per scaricare un sistema e test di carico, non per test unitari

Qualcun altro può aggiungere ulteriori motivi che posso dargli per farlo smettere di farlo?

(O in alternativa, è un metodo accettabile per scrivere test unitari e io e il mio collega abbiamo sbagliato?)


32
"valori casuali indicano che il test non è realmente ripetibile" non è vero, poiché le virgole useranno numeri pseudo casuali. Fornire lo stesso seme iniziale, ottenere la stessa sequenza di test "casuali".
Raedwald,

11
Aneddoto: una volta ho scritto una classe di esportazione CSV e test casuali hanno rivelato un bug quando i caratteri di controllo venivano posizionati alla fine di una cella. Senza test casuali, non avrei mai pensato di aggiungerlo come test case. Ha sempre fallito? No. È un test perfetto? No. Mi ha aiutato a catturare e correggere un bug? Sì.
Tyzoid,

1
I test possono anche servire da documentazione, per spiegare quando il codice si aspetta come input e cosa ci si aspetta come output. Avere un test con chiari dati arbitrari può essere più semplice e più esplicativo del codice che genera dati casuali.
splintore

Se il test unitario fallisce a causa di un valore generato casualmente e questo valore non fa parte di un'asserzione, buona fortuna con il debug del test unitario.
eriksmith200

Risposte:


72

C'è un compromesso. Il tuo collega è in realtà su qualcosa, ma penso che stia sbagliando. Non sono sicuro che i test totalmente casuali siano molto utili, ma non sono certamente validi.

Una specifica di programma (o unità) è un'ipotesi che esista un programma che lo soddisfi. Il programma stesso è quindi la prova di tale ipotesi. Quale test unitario dovrebbe essere un tentativo di fornire contro-prove per confutare che il programma funziona secondo le specifiche.

Ora puoi scrivere i test unitari a mano, ma è davvero un compito meccanico. Può essere automatizzato. Tutto quello che devi fare è scrivere le specifiche e una macchina può generare un sacco di test unitari che tentano di violare il codice.

Non so quale lingua stai usando, ma vedi qui:

Java http://functionaljava.org/

Scala (o Java) http://github.com/rickynils/scalacheck

Haskell http://www.cs.chalmers.se/~rjmh/QuickCheck/

.NET: http://blogs.msdn.com/dsyme/archive/2008/08/09/fscheck-0-2.aspx

Questi strumenti prenderanno le tue specifiche ben formate come input e genereranno automaticamente tutte le unit test che desideri, con i dati generati automaticamente. Usano strategie "restringenti" (che è possibile modificare) per trovare il caso di test più semplice possibile per violare il codice e assicurarsi che copra bene i casi limite.

Buon test!


1
+1 a questo. ScalaCheck svolge un lavoro fenomenale nel generare dati di test casuali minimizzati in modo ripetibile.
Daniel Spiewak,

17
Non è casuale. È arbitrario. Grande differenza :)
Apocalisp,

reducetiotest.org ora sembra esistere più a lungo e Google non mi ha indicato altrove. Qualche idea su dove sia adesso?
Raedwald,

Ora fa parte della libreria Functional Java. Link modificato. Ma userei Scalacheck per testare il codice Java.
Apocalisp,

ScalaCheck si è trasferito in GitHub. Link aggiornato in risposta. È anche utile per Java, non solo per Scala. (Il vecchio link era code.google.com/p/scalacheck )
RobertB,

38

Questo tipo di test si chiama test Monkey . Se fatto bene, può fumare insetti dagli angoli davvero bui.

Per rispondere alle vostre preoccupazioni sulla riproducibilità: il modo giusto di affrontare questo problema è registrare le voci del test non riuscite, generare un unit test, che analizza l' intera famiglia del bug specifico; e includere nell'unità test un input specifico (dai dati casuali) che ha causato l'errore iniziale.


26

C'è una casa a metà strada qui che ha qualche utilità, che è di seminare il tuo PRNG con una costante. Ciò consente di generare dati "casuali" che sono ripetibili.

Personalmente penso che ci siano posti in cui i dati (costanti) casuali sono utili nei test - dopo aver pensato di aver fatto tutti i tuoi angoli attentamente studiati, l'uso degli stimoli di un PRNG a volte può trovare altre cose.


4
Ho visto che funziona bene su un sistema che ha molti blocchi e thread. Il seme "casuale" è stato scritto in un file ad ogni esecuzione, quindi se un'esecuzione ha avuto esito negativo, potremmo capire il percorso seguito dal codice e scrivere un test unitario scritto a mano per quel caso che ci eravamo persi.
Ian Ringrose,

Cosa significa PRNG?
systemovich,

Pseudo Random Number Generator
Will Dean

16

Nel libro Beautiful Code , c'è un capitolo chiamato "Beautiful Tests", in cui passa attraverso una strategia di test per l' algoritmo di ricerca binaria . Un paragrafo è chiamato "Atti casuali di test", in cui crea array casuali per testare accuratamente l'algoritmo. Puoi leggere alcuni di questi online su Google Libri, pagina 95 , ma è un ottimo libro che vale la pena avere.

Quindi, in pratica, ciò dimostra che la generazione di dati casuali per i test è un'opzione praticabile.


16

Sono a favore di test casuali e li scrivo. Tuttavia, se sono appropriati in un particolare ambiente di costruzione e in quali suite di test dovrebbero essere inclusi è una domanda più sfumata.

Esegui localmente (ad es. Durante la notte sulla tua casella di sviluppo) i test randomizzati hanno riscontrato bug sia ovvi che oscuri. Gli oscuri sono abbastanza arcani che penso che i test casuali siano stati davvero i soli realistici per stanarli. Come test, ho preso un bug difficile da trovare scoperto tramite test randomizzati e ho avuto una mezza dozzina di sviluppatori di crack che hanno esaminato la funzione (circa una dozzina di righe di codice) in cui si è verificato. Nessuno è stato in grado di rilevarlo.

Molti dei tuoi argomenti contro i dati randomizzati sono esempi di "il test non è riproducibile". Tuttavia, un test randomizzato ben scritto catturerà il seme utilizzato per avviare il seme randomizzato e lo produrrà in caso di fallimento. Oltre a consentire di ripetere il test manualmente, ciò consente di creare banalmente un nuovo test che verifica il problema specifico codificando il seme per quel test. Ovviamente, probabilmente è meglio scrivere a mano un test esplicito che copre quel caso, ma la pigrizia ha le sue virtù, e questo ti consente persino di generare automaticamente nuovi casi di test da un seme fallito.

L'unico punto da sottolineare che non posso discutere, tuttavia, è che rompe i sistemi di compilazione. La maggior parte dei test di integrazione continua e build si aspettano che i test facciano sempre la stessa cosa. Quindi un test che fallisce casualmente creerà caos, fallendo casualmente e puntando le dita su cambiamenti che erano innocui.

Una soluzione quindi, è ancora eseguire i test randomizzati come parte dei test build e CI, ma eseguirlo con un seed fisso, per un numero fisso di iterazioni . Quindi il test fa sempre la stessa cosa, ma esplora ancora un sacco di spazio di input (se lo esegui per più iterazioni).

A livello locale, ad esempio, quando si cambia la classe interessata, si è liberi di eseguirla per più iterazioni o con altri semi. Se i test randomizzati diventeranno sempre più popolari, potresti persino immaginare una specifica suite di test che sono noti per essere casuali, che potrebbero essere eseguiti con semi diversi (quindi con una copertura crescente nel tempo) e dove i fallimenti non significherebbero la stessa cosa come sistemi di CI deterministici (ovvero, le esecuzioni non sono associate 1: 1 alle modifiche del codice e quindi non si punta un dito su una particolare modifica quando le cose falliscono).

C'è molto da dire per i test randomizzati, specialmente quelli ben scritti, quindi non essere troppo veloce per scartarli!


14

Se stai facendo TDD, direi che i dati casuali sono un approccio eccellente. Se il test è scritto con costanti, allora puoi garantire che il tuo codice funzioni solo per il valore specifico. Se il test fallisce in modo casuale il server di compilazione, è probabile che vi sia un problema con la modalità di scrittura del test.

I dati casuali aiuteranno a garantire che qualsiasi refactoring futuro non si baserà su una costante magica. Dopotutto, se i tuoi test sono la tua documentazione, la presenza di costanti non implica che debba funzionare solo per quelle costanti?

Sto esagerando, tuttavia preferisco inserire dati casuali nel mio test come segno che "il valore di questa variabile non dovrebbe influire sull'esito di questo test".

Dirò però che se usi una variabile casuale, biforca il tuo test basandoti su quella variabile, allora è un odore.


10

Un vantaggio per chi guarda i test è che i dati arbitrari non sono chiaramente importanti. Ho visto troppi test che hanno coinvolto dozzine di dati e può essere difficile dire cosa deve essere in quel modo e cosa succede in quel modo. Ad esempio, se un metodo di convalida dell'indirizzo viene testato con un codice postale specifico e tutti gli altri dati sono casuali, si può essere abbastanza sicuri che il codice postale sia l'unica parte importante.


9
  • se è un valore casuale e il test fallisce, dobbiamo a) riparare l'oggetto eb) forzarci a testare quel valore ogni volta, quindi sappiamo che funziona, ma poiché è casuale non sappiamo quale sia il valore

Se il tuo test case non registra accuratamente ciò che sta testando, forse devi ricodificare il test case. Voglio sempre avere registri a cui posso fare riferimento per i casi di test in modo da sapere esattamente cosa ha causato il fallimento se si utilizzano dati statici o casuali.


9

Il tuo collega sta eseguendo il test fuzz , anche se non lo sa. Sono particolarmente utili nei sistemi server.


2
ma non è una cosa sostanzialmente diversa dai test unitari? e fatto in un altro momento?
endolith

1
@endolith non esiste una legge della fisica che ti costringa a eseguire particolari test in determinati momenti
user253751

1
@immibis Ma ci sono buoni motivi per fare test particolari in determinati momenti. Non si esegue una batteria di test unitari ogni volta che un utente fa clic sul pulsante "Ok".
endolith

5

Puoi generare alcuni dati casuali una volta (intendo esattamente una volta, non una volta per test), quindi utilizzarli in seguito in tutti i test?

Posso sicuramente vedere il valore nella creazione di dati casuali per testare quei casi a cui non hai pensato, ma hai ragione, avere test unitari che possono passare o fallire casualmente è una cosa negativa.


5

Dovreste chiedervi qual è l'obiettivo del vostro test.
I test unitari riguardano la verifica della logica, del flusso di codice e delle interazioni degli oggetti. L'uso di valori casuali cerca di raggiungere un obiettivo diverso, riducendo così l'attenzione e la semplicità del test. È accettabile per motivi di leggibilità (generazione di UUID, ID, chiavi, ecc.).
In particolare per i test unitari, non riesco a ricordare nemmeno una volta che questo metodo è riuscito a trovare problemi. Ma ho visto molti problemi di determinismo (nei test) che cercavano di essere intelligenti con valori casuali e principalmente con date casuali.
Il Fuzz Test è un approccio valido per test di integrazione e test end-to-end .


Aggiungerei che l'uso dell'input casuale per il fuzzing è un cattivo sostituto del fuzzing guidato dalla copertura quando è possibile.
Gobenji,

1

Se si utilizza un input casuale per i test, è necessario registrare gli input in modo da poter vedere quali sono i valori. In questo modo, se si verifica un caso limite, è possibile scrivere il test per riprodurlo. Ho sentito le stesse ragioni dalle persone per non aver usato input casuali, ma una volta che hai una visione dei valori effettivi utilizzati per una determinata esecuzione di test, non è un problema.

La nozione di dati "arbitrari" è anche molto utile come modo per indicare qualcosa che non è importante. Abbiamo alcuni test di accettazione che vengono in mente dove ci sono molti dati sul rumore che non sono rilevanti per il test a portata di mano.


0

A seconda del tuo oggetto / app, i dati casuali avrebbero un posto nel test di carico. Penso che sarebbe più importante utilizzare i dati che testano esplicitamente le condizioni al contorno dei dati.


0

Ci siamo appena imbattuti in questo oggi. Volevo pseudo-casuale (quindi sembrerebbe dati audio compressi in termini di dimensioni). TODO avrei voluto anche deterministico . rand () era diverso su OSX che su Linux. E a meno che non mi sia ripresentato, potrebbe cambiare in qualsiasi momento. Quindi l'abbiamo modificato in modo da essere deterministico ma comunque psueo-casuale: il test è ripetibile, tanto quanto l'utilizzo di dati fissi (ma più convenientemente scritti).

Questo NON era testato da qualche forza bruta casuale attraverso percorsi di codice. Questa è la differenza: ancora deterministico, ancora ripetibile, usando ancora dati che sembrano input reali per eseguire una serie di controlli interessanti su casi limite in una logica complessa. Prove unitarie.

Che qualifica ancora è casuale? Parliamo della birra. :-)


0

Posso prevedere tre soluzioni al problema dei dati di test:

  • Test con dati fissi
  • Test con dati casuali
  • Genera una volta dati casuali , quindi usali come dati fissi

Consiglierei di fare tutto quanto sopra . Ossia, scrivi test unitari ripetibili sia con alcuni casi limite elaborati usando il tuo cervello, sia con alcuni dati randomizzati che generi una sola volta. Quindi scrivi una serie di test randomizzati che esegui pure .

I test randomizzati non dovrebbero mai aspettarsi di catturare qualcosa che manca ai test ripetibili. Dovresti mirare a coprire tutto con test ripetibili e considerare i test randomizzati un bonus. Se trovano qualcosa, dovrebbe essere qualcosa che non avresti potuto ragionevolmente prevedere; una vera strana palla.


-2

Come può il tuo ragazzo eseguire nuovamente il test quando non riesce a vedere se lo ha riparato? Cioè perde la ripetibilità dei test.

Mentre penso che probabilmente ci sia un certo valore nel lanciare un carico di dati casuali durante i test, come menzionato in altre risposte, rientra più nella voce dei test di carico che altro. È praticamente una pratica "test per speranza". Penso che, in realtà, il tuo ragazzo non stia semplicemente pensando a ciò che sta cercando di provare, e compensando quella mancanza di pensiero sperando che la casualità alla fine intrappoli qualche misterioso errore.

Quindi l'argomento che vorrei usare con lui è che è pigro. Oppure, per dirla in altro modo, se non si prende il tempo di capire cosa sta cercando di provare, probabilmente mostra che non capisce davvero il codice che sta scrivendo.


3
È possibile registrare i dati casuali o il seme casuale in modo da poter riprodurre il test.
cbp
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.