Dati casuali nei test unitari?

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

Gli ho dato una serie di motivi diversi contro questo, i principali sono:

  • valori casuali significa che il test non è veramente ripetibile (il che significa anche che se il test può fallire casualmente, può farlo sul server di build e interrompere la compilazione)
  • se è un valore casuale e il test fallisce, dobbiamo a) correggere l’object eb) forzarci a testare quel valore ogni volta, quindi sappiamo che funziona, ma dal momento che è casuale non sappiamo quale fosse il valore

Un altro collaboratore ha aggiunto:

  • Se sto testando un’eccezione, i valori casuali non garantiranno che il test finisca nello stato previsto
  • i dati casuali vengono utilizzati per lo svuotamento di un sistema e il test di carico, non per i test unitari

Qualcun altro può aggiungere ulteriori motivi che posso dargli per fargli smettere di fare questo?

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

C’è un compromesso. Il tuo collega è in realtà su qualcosa, ma penso che stia sbagliando. Non sono sicuro che il test totalmente casuale sia molto utile, ma certamente non è valido.

Una specifica del programma (o dell’unità) è un’ipotesi che esista un programma che lo soddisfi. Il programma stesso è quindi la prova di quell’ipotesi. Quale unità di prova 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 infrangere il tuo 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 formulate come input e genereranno automaticamente tutti i test di unità che desideri, con i dati generati automaticamente. Usano strategie di “restringimento” (che puoi modificare) per trovare il caso di test più semplice ansible per infrangere il tuo codice e per assicurarti che copra bene i casi limite.

Test felici!

Questo tipo di test è chiamato test di scimmia . Se fatto bene, può eliminare gli insetti dagli angoli veramente bui.

Per rispondere alle vostre preoccupazioni sulla riproducibilità: il modo giusto per avvicinarsi a questo, è quello di registrare le voci di test non riuscite, generare un test unitario, che sonda per l’ intera famiglia del bug specifico; e includere nel test unitario l’input specifico (dai dati casuali) che ha causato l’errore iniziale.

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

Personalmente penso che ci siano dei luoghi in cui i dati casuali (costanti) sono utili nei test: dopo aver pensato di aver fatto tutti gli angoli attentamente studiati, l’uso di stimoli da un PRNG può a volte trovare altre cose.

Nel libro Beautiful Code , c’è un capitolo intitolato “Beautiful Tests”, in cui passa attraverso una strategia di test per l’algoritmo di ricerca binaria . Un paragrafo è chiamato “Random Acts of Testing”, in cui crea array casuali per testare a fondo l’algoritmo. Puoi leggerne un po ‘online su Google Libri, a pagina 95 , ma è un libro che vale la pena avere.

Quindi in pratica questo dimostra che generare dati casuali per i test è un’opzione praticabile.

Se stai facendo TDD, allora direi che i dati casuali rappresentano un approccio eccellente. Se il tuo test è scritto con costanti, puoi solo garantire che il tuo codice funzioni per il valore specifico. Se il test fallisce casualmente il server di build, è probabile che si sia verificato 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, allora la presenza di costanti non implica che debba funzionare solo per quelle costanti?

Sto esagerando tuttavia preferisco iniettare dati casuali nel mio test come segno che “il valore di questa variabile non dovrebbe influenzare l’esito di questo test”.

Dirò però che se si utilizza una variabile casuale, si biforca il test in base a tale variabile, allora si tratta di un odore.

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 uno specifico codice postale e tutti gli altri dati sono casuali, puoi essere certo che il codice postale è l’unica parte importante.

  • se è un valore casuale e il test fallisce, dobbiamo a) correggere l’object eb) forzarci a testare quel valore ogni volta, quindi sappiamo che funziona, ma dal momento che è casuale non sappiamo quale fosse il valore

Se il tuo caso di test non registra accuratamente ciò che sta testando, forse hai bisogno di ricodificare il caso di test. Voglio sempre avere registri a cui posso riferire per i casi di test in modo che io sappia esattamente cosa ha causato il fallimento se si utilizzano dati statici o casuali.

Il tuo collega sta facendo test fuzz , anche se non lo sa. Sono particolarmente preziosi nei sistemi server.

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

Esegui localmente (ad esempio, durante la notte sulla tua casella di sviluppo) i test randomizzati hanno rilevato bug sia ovvi che oscuri. Quelle oscure sono abbastanza arcane da ritenere che il test casuale sia stato l’unico realistico a scovarle. Come test, ho preso un bug difficile da trovare scoperto tramite test randomizzati e ho avuto una mezza dozzina di sviluppatori di crack per rivedere la funzione (circa una dozzina di righe di codice) dove si è verificata. Nessuno è stato in grado di rilevarlo.

Molti dei tuoi argomenti contro i dati randomizzati sono aromi di “il test non è riproducibile”. Tuttavia, un test randomizzato ben scritto catturerà il seme utilizzato per avviare il seed randomizzato e lo stamperà in caso di fallimento. Oltre a permetterti di ripetere il test a mano, questo ti permette di creare banalmente un nuovo test che verifica il problema specifico codificando il seme per quel test. Ovviamente, è probabilmente più bello codificare manualmente un test esplicito che copre questo caso, ma la pigrizia ha i suoi pregi, e questo ti consente anche di generare automaticamente nuovi casi di test da un seed in errore.

L’unico punto che fai che io non possa discutere, tuttavia, è che rompe i sistemi di costruzione. La maggior parte dei test di integrazione di build e continui si aspetta che i test facciano sempre la stessa cosa. Quindi un test che fallisce casualmente creerà caos, fallendo casualmente e puntando le dita a cambiamenti che sono innocui.

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

A livello locale, ad esempio, quando si modifica la class interessata, si è liberi di eseguirla per più iterazioni o con altri semi. Se i test randomizzati diventano sempre più popolari, puoi persino immaginare una serie specifica 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 significano la stessa cosa come sistemi CI deterministici (cioè, le esecuzioni non sono associate 1: 1 con le modifiche al codice e quindi non si punta un dito a un particolare cambiamento quando le cose falliscono).

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

Puoi generare alcuni dati casuali una volta (intendo esattamente una volta, non una volta per ogni prova), quindi usarlo in tutti i test successivi?

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.

Dovresti chiederti qual è l’objective del tuo test.
I test unitari riguardano la verifica della logica, il stream del codice e le interazioni con gli oggetti. L’uso di valori casuali cerca di raggiungere un objective diverso, riducendo quindi 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 ha avuto successo nel trovare i problemi. Ma ho visto molti problemi di determinismo (nei test) che cercano di essere intelligenti con valori casuali e principalmente con date casuali.
Il test fuzz è un approccio valido per test di integrazione e test end-to-end .

Se stai usando un input casuale per i tuoi test, devi registrare gli input in modo da poter vedere quali sono i valori. In questo modo se c’è qualche caso limite che incontri, puoi scrivere il test per riprodurlo. Ho sentito le stesse ragioni per le persone che non usano input casuali, ma una volta che hai una visione dei valori reali usati per una particolare esecuzione di test, allora non è tanto un problema.

La nozione di dati “arbitrari” è anche molto utile come mezzo per indicare qualcosa che non è importante. Abbiamo alcuni test di accettazione che vengono in mente dove ci sono molti dati sul rumore che non hanno rilevanza per il test in esame.

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

Ci siamo appena imbattuti in questo oggi. Volevo pseudo-casuale (quindi sembrerebbe dati audio compressi in termini di dimensioni). I TODO’d che volevo anche deterministico . rand () era diverso su OSX che su Linux. E a meno che non abbia re-seeding, potrebbe cambiare in qualsiasi momento. Quindi lo abbiamo modificato per essere deterministico ma ancora psuedo-random: il test è ripetibile, tanto quanto usare i dati in scatola (ma più comodamente scritto).

Questo NON è stato testato da una forza bruta casuale attraverso percorsi di codice. Questa è la differenza: ancora deterministica, ancora ripetibile, che utilizza ancora dati che sembrano input reali per eseguire una serie di controlli interessanti sui casi limite in una logica complessa. Ancora test unitari.

Quello ancora qualifica è casuale? Parliamo di birra. 🙂

Posso immaginare tre soluzioni per il problema dei dati di test:

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

Consiglierei di fare tutto quanto sopra . Cioè, scrivi unit test ripetibili con entrambi i casi limite elaborati usando il tuo cervello, e alcuni dati randomizzati che generi solo una volta. Quindi scrivi una serie di test randomizzati eseguiti anche tu.

Non ci si dovrebbe mai aspettare che i test randomizzati catturino qualcosa a cui mancano i 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; un vero bizzarro.

Come può il tuo ragazzo eseguire nuovamente il test quando non è riuscito a vedere se l’ha risolto? Ad esempio, perde la ripetibilità dei test.

Mentre penso che ci sia probabilmente un certo valore nel lanciare un carico di dati casuali ai test, come menzionato in altre risposte, è più che altro il titolo del test di carico. È praticamente una pratica di “test-by-hope”. Penso che, in realtà, il tuo ragazzo semplicemente non pensi a quello che sta cercando di testare, e rimedia a quella mancanza di pensiero sperando che la casualità finisca per intrappolare qualche misterioso errore.

Quindi l’argomento che userei con lui è che è pigro. O, per dirla in altro modo, se non si prende il tempo per capire cosa sta cercando di testarlo probabilmente mostra che non capisce veramente il codice che sta scrivendo.