Come devo testare la casualità?


127

Considera un metodo per mescolare casualmente gli elementi in un array. Come scriveresti un test unitario semplice ma robusto per assicurarti che funzioni?

Ho escogitato due idee, entrambe con evidenti difetti:

  • Mescola l'array, quindi assicurati che il suo ordine differisca da prima. Questo suona bene, ma fallisce se lo shuffle accade di mescolare nello stesso ordine. (Improbabile, ma possibile.)
  • Mischia l'array con un seme costante e verificalo rispetto all'output predeterminato. Questo si basa sulla funzione casuale che restituisce sempre gli stessi valori dato lo stesso seme. Tuttavia, a volte questo è un presupposto non valido .

Considera una seconda funzione che simula i tiri di dado e restituisce un numero casuale. Come testeresti questa funzione? Come testeresti che la funzione ...

  • non restituisce mai un numero al di fuori dei limiti indicati?
  • restituisce numeri in una distribuzione valida? (Uniforme per un dado, normale per un gran numero di dadi.)

Sto cercando risposte che offrano spunti per testare non solo questi esempi ma elementi casuali di codice in generale. I test unitari sono la soluzione giusta qui? In caso contrario, che tipo di test sono?


Proprio per facilitare la mente di tutti che sto non scrivere il mio generatore di numeri casuali.


35
L'accoppiamento stretto mostra la sua testa. Passa l'oggetto che genera i numeri casuali. Quindi durante il test puoi passare un oggetto che genera un set di numeri specificato per il quale sai come appare il mazzo dopo lo shuffle. Puoi testare la casualità del tuo generatore di numeri casuali separatamente.
Martin York,

1
Vorrei fortemente considerare l'utilizzo di una routine di libreria esistente per shuffle (java Collections.shuffle () o simile). C'è un ammonimento da leggere su developer.com/tech/article.php/616221/… sulla scrittura di un algoritmo shuffle difettoso. Per scrivere una funzione d6 (), si dovrebbe testarlo abbastanza per essere sicuri che non genererà un numero fuori range e quindi fare un test chi quadrato sulla distribuzione (chi quadrato è piuttosto sensibile alle sequenze pseudo casuali). Guarda anche il coefficiente di correlazione seriale.

"Questo si basa sulla funzione casuale che restituisce sempre gli stessi valori dato lo stesso seme. Tuttavia, a volte questo è un presupposto non valido." Ho seguito il collegamento e non vedo l'assunto non valido. Dice chiaramente: "Se lo stesso seme viene usato ripetutamente, viene generata la stessa serie di numeri".
Kyralessa,

@Kyralessa "L'implementazione del generatore di numeri casuali nella classe Random non è garantita per rimanere la stessa nelle principali versioni di .NET Framework." Quindi non una grande preoccupazione, ma ancora qualcosa da considerare.
dlras2,

4
@Kyralessa Ho perso la metà importante di quella citazione: "Di conseguenza, il codice dell'applicazione non dovrebbe presumere che lo stesso seed provocherà la stessa sequenza pseudo-casuale in diverse versioni di .NET Framework".
dlras2,

Risposte:


102

Non credo che i test unitari siano lo strumento giusto per testare la casualità. Un unit test dovrebbe chiamare un metodo e testare il valore restituito (o lo stato dell'oggetto) rispetto a un valore previsto. Il problema con il test della casualità è che non esiste un valore atteso per la maggior parte delle cose che vorresti testare. Puoi testare con un determinato seme, ma questo verifica solo la ripetibilità . Non ti dà alcun modo per misurare quanto sia casuale la distribuzione o se è addirittura casuale.

Fortunatamente, ci sono molti test statistici che puoi eseguire, come la Diehard Battery of Tests of Randomness . Guarda anche:

  1. Come testare un generatore di numeri pseudo casuali?

    • Steve Jessop consiglia di trovare un'implementazione testata dello stesso algoritmo RNG che si sta utilizzando e confrontare il suo output con i seed selezionati con la propria implementazione.
    • Greg Hewgill raccomanda la suite ENT di test statistici.
    • John D. Cook rimanda i lettori al suo articolo CodeProject Simple Random Number Generation , che include un'implementazione del test Kolmogorov-Smirnov menzionato nel volume 2 di Donald Knuth, Algorithms seminumerici.
    • Diverse persone raccomandano di verificare che la distribuzione dei numeri generati sia uniforme, il test Chi-quadrato e di verificare che la media e la deviazione standard rientrino nell'intervallo previsto. (Si noti che testare la distribuzione da sola non è sufficiente. [1,2,3,4,5,6,7,8] è una distribuzione uniforme, ma non è certamente casuale.)
  2. Unit test con funzioni che restituiscono risultati casuali

    • Brian Genisio sottolinea che deridere il tuo RNG è un'opzione per rendere ripetibili i tuoi test e fornisce un codice di esempio C #.
    • Ancora una volta, molte più persone indicano di utilizzare valori di seme fissi per la ripetibilità e semplici test per una distribuzione uniforme, Chi-quadrato, ecc.
  3. Unit Testing Randomness è un articolo wiki che parla di molte delle sfide già affrontate quando si tenta di testare ciò che, per sua natura, non è ripetibile. Un aspetto interessante che ho raccolto è stato il seguente:

    Ho visto winzip usato come uno strumento per misurare la casualità di un file di valori prima (ovviamente, più piccolo è in grado di comprimere il file, meno casuale è).


Un'altra buona suite di test per la casualità statistica è 'ent' trovata su fourmilab.ch/random .

1
Puoi riassumere alcuni dei link che hai pubblicato, per completezza della risposta?
dlras2,

@DanRasmussen Certo, avrò tempo per farlo durante il fine settimana.
Bill the Lizard,

4
"Il problema con ... la casualità è che non esiste un valore atteso ..." - che ironia, dato che il "valore atteso" è un termine ben definito nelle statistiche. E mentre questo non è ciò che intendevi, suggerisce la giusta soluzione: utilizzare proprietà note di distribuzioni statistiche, associate a campionamenti casuali e test statistici , per determinare se un algoritmo funziona con probabilità molto alte. Sì, questo non è un test unitario classico, ma ho voluto menzionarlo poiché nel caso più semplice guarda solo alla distribuzione di ... il valore atteso .
Konrad Rudolph,

2
Esiste una versione aggiornata del famoso Diehard Battery of Tests of Randomness at Dieharder, che include la Statistical Test Suite (STS) sviluppata dal National Institute for Standards and Technology (NIST). È disponibile pronto per l'esecuzione in Ubuntu e presumibilmente altre distro: phy.duke.edu/~rgb/General/dieharder.php
nealmcb

21

1. Unit test del tuo algoritmo

Per la prima domanda, creerei una classe falsa che alimenta una sequenza di numeri casuali per i quali conosci il risultato del tuo algoritmo. In questo modo ti assicuri che l'algoritmo che crei sulla tua funzione casuale funzioni. Quindi qualcosa sulla falsariga di:

Random r = new RandomStub([1,3,5,3,1,2]);
r.random(); //returns 1
r.random(); //returns 3
...

2. Verifica se la tua funzione casuale ha senso

Al test unitario è necessario aggiungere un test che viene eseguito più volte e afferma che i risultati

  • sono entro i limiti impostati (quindi, un tiro di dado è compreso tra 1 e 6) e
  • mostra una distribuzione ragionevole (esegui più test e vedi se la distribuzione è entro il x% di quello che ti aspettavi, ad es. per il tiro di dadi dovresti vedere un 2aumento tra il 10% e il 20% (1/6 = 16,67%) del tempo dato che l'hai arrotolato 1000 volte).

3. Test di integrazione per l'algoritmo e la funzione casuale

Quanto spesso ti aspetti che l'array sia ordinato nell'ordinamento originale? Ordina un paio di centinaia di volte e asserisci che solo l'x% delle volte l'ordinamento non cambia.

In realtà questo è già un test di integrazione, stai testando l'algoritmo insieme alla funzione casuale. Una volta che si utilizza la funzione casuale reale, non è più possibile cavarsela con singole prove.

Per esperienza (ho scritto un algoritmo genetico) direi che combinare il test unitario del tuo algoritmo, il test di distribuzione della tua funzione casuale e il test di integrazione è la strada da percorrere.


14

Un aspetto dei PRNG che sembra dimenticato è che tutte le sue proprietà sono di natura statistica: non puoi aspettarti che la mescolanza di un array comporterà una permutazione diversa da quella con cui hai iniziato. Fondamentalmente, se si utilizza un PRNG normale, l'unica cosa che si garantisce è che non utilizza un modello semplice (si spera) e che ha una distribuzione uniforme tra l'insieme di numeri che restituisce.

Un test adeguato per un PRNG comporta l'esecuzione di almeno 100 volte e quindi il controllo della distribuzione dell'output (che è una risposta diretta alla seconda parte della domanda).

Una risposta alla prima domanda è quasi la stessa: esegui il test circa 100 volte con {1, 2, ..., n} e conta il numero di volte in cui ciascun elemento è stato in ciascuna posizione. Dovrebbero essere quasi tutti uguali se il metodo shuffle è buono.

Una questione completamente diversa è come testare PRNG di livello crittografico. Questa è una questione in cui probabilmente non dovresti soffermarti, a meno che tu non sappia davvero cosa stai facendo. Le persone sono state conosciute per distruggere (leggi: aprire buchi catastrofici in) buoni sistemi crittografici con solo alcune "ottimizzazioni" o modifiche banali.

EDIT: ho riletto a fondo la domanda, la risposta migliore e la mia. Mentre i punti che faccio valgono ancora, secondo la risposta di Bill The Lizard. I test unitari sono booleani nella loro natura - o falliscono, o hanno successo, e quindi non sono adatti per testare "quanto sono buone" le proprietà di un PRNG (o un metodo che utilizza un PRNG), dal momento che qualsiasi risposta a questa domanda sarebbe quantitativa , piuttosto che polare.


1
Penso che tu intenda che il numero di volte in cui ciascun elemento si trova in ciascuna posizione dovrebbe essere approssimativamente uguale. Se sono sempre esattamente uguali, qualcosa è molto sbagliato.
ottobre

@octern Grazie, non so come avrei potuto scrivere che ... era completamente sbagliato fino ad ora ...
K.Steff

6

Ci sono due parti in questo: testare la randomizzazione e testare cose che usano la randomizzazione.

Testare la randomizzazione è relativamente semplice. Verifichi che il periodo del generatore di numeri casuali sia come previsto (per alcuni campioni utilizzando alcuni semi un po 'casuali, entro una certa soglia) e che la distribuzione dell'output su una grande dimensione del campione è come previsto essere (entro una certa soglia).

Il test delle cose che usano la randomizzazione è fatto meglio con un generatore di numeri psuedo-casuale deterministico. Poiché l'output della randomizzazione è noto in base al seed (i suoi input), è possibile testare l'unità come normale in base agli input rispetto agli output previsti. Se il tuo RNG non è deterministico, allora prendilo in giro con uno che è deterministico (o semplicemente non casuale). Prova la randomizzazione in isolamento dal codice che la consuma.


6

Lascialo funzionare un sacco di volte e visualizza i tuoi dati .

Ecco un esempio di un riordino di Coding Horror , puoi vedere che l'algoritmo è OK o no:

inserisci qui la descrizione dell'immagine

È facile vedere che ogni possibile articolo viene restituito almeno una volta (i confini sono OK) e che la distribuzione è OK.


1
La visualizzazione +1 è la chiave. Mi è sempre piaciuto l'esempio con l'immagine di un pinguino nella sezione BCE dell'articolo di cifratura a blocchi ). Un software automatizzato raramente è in grado di rilevare tali regolarità
Maksee

Eh? Il punto di quella visualizzazione è mostrare che la distribuzione non va bene. L'algoritmo shuffle ingenuo rende alcuni ordini molto più probabili di altri. Notate quanto più a destra si estendono le barre 2341, 2314, 2143 e 1342?
hvd,

4

Puntatori generali che ho trovato utili quando ho a che fare con il codice che accetta input randomizzati: controlla i casi limite della casualità prevista (valori max e min e i valori max + 1 e min-1 se applicabile). Controlla i luoghi (sopra, sopra e sotto) in cui i numeri hanno punti di flesso (cioè -1, 0, 1 o maggiore di 1, minore di 1 e non negativo per i casi in cui un valore frazionario potrebbe confondere la funzione). Controllare alcuni punti completamente al di fuori dell'ingresso consentito. Controlla alcuni casi tipici. Puoi anche aggiungere un input casuale, ma per un unit test che ha l'effetto indesiderato che lo stesso valore non è sotto test ogni volta che viene eseguito il test (può comunque funzionare un approccio seed, testare i primi 1.000 numeri casuali dal seed S o somesuch).

Per testare l'output di una funzione casuale, è importante identificare l'obiettivo. Nel caso delle carte, l'obiettivo è testare l'uniformità del generatore casuale 0-1, per determinare se tutte le 52 carte compaiono nel risultato o qualche altro obiettivo (forse tutto questo elenco e altro)?

Nell'esempio specifico, devi supporre che il tuo generatore di numeri casuali sia opaco (proprio come non ha senso testare l'unità syscall o malloc del sistema operativo, a meno che tu non scriva sistemi operativi). Può essere utile misurare il generatore di numeri casuali, ma il tuo obiettivo non è quello di scrivere un generatore casuale, solo per vedere che ottieni 52 carte ogni volta e che cambiano ordine.

Questo è un lungo modo di dire che ci sono davvero due compiti di test qui: testare che l'RNG sta producendo la giusta distribuzione e verificare che il codice di shuffle della tua carta stia usando quell'RNG per produrre risultati casuali. Se stai scrivendo l'RNG, usa l'analisi statistica per provare la tua distribuzione, se stai scrivendo lo shuffler di carte, assicurati che ci siano 52 carte non ripetute in ogni uscita (è un caso migliore per il test tramite ispezione che stai usando il RNG).


4

Puoi contare su generatori di numeri casuali sicuri

Ho appena avuto un pensiero orribile: non stai scrivendo il tuo generatore di numeri casuali, vero?

Supponendo che non lo sia, dovresti testare il codice di cui sei responsabile , non il codice di altre persone (come l' SecureRandomimplementazione per il tuo framework).

Test del tuo codice

Per verificare che il codice risponda correttamente, è normale utilizzare un metodo a bassa visibilità per produrre numeri casuali in modo che possa essere facilmente ignorato da una classe di test unitari. Questo metodo ignorato prende in giro in modo efficace il generatore di numeri casuali e ti dà il controllo completo su ciò che viene prodotto e quando. Di conseguenza puoi esercitare pienamente il tuo codice che è l'obiettivo del test unitario.

Ovviamente verificherai le condizioni dei bordi e ti assicurerai che il mescolamento avvenga esattamente come prescritto dall'algoritmo dato gli input appropriati.

Test del generatore di numeri casuali sicuro

Se non si è sicuri che il generatore di numeri casuali sicuri per la propria lingua non sia realmente casuale o sia errato (fornisce valori fuori intervallo ecc.), È necessario eseguire un'analisi statistica dettagliata dell'output su diverse centinaia di milioni di iterazioni. Traccia la frequenza di occorrenza di ciascun numero e dovrebbe apparire con uguale probabilità. Se i risultati si inclinano in un modo o nell'altro, è necessario segnalare i risultati ai progettisti del framework. Saranno sicuramente interessati a risolvere il problema poiché i generatori di numeri casuali sicuri sono fondamentali per molti algoritmi di crittografia.


1

Bene, non sarai mai sicuro al 100%, quindi il meglio che puoi fare è che è probabile che i numeri siano casuali. Scegli una probabilità: supponi che un campione di numeri o elementi verrà generato x volte dato un milione di campioni, entro un margine di errore. Esegui la cosa un milione di volte e vedi se è all'interno del margine. Fortunatamente, i computer rendono questo genere di cose facile da fare.


Ma i test unitari come questo sono considerati buone pratiche ...? Ho sempre pensato che un test unitario dovrebbe essere il più semplice possibile: nessun loop, ramo o qualsiasi altra cosa che possa essere evitata.
dlras2,

4
I test unitari dovrebbero essere corretti . Se ci vogliono ramificazione, loop, ricorsione - questo è il prezzo. Non è possibile eseguire test unitari di classi estremamente sofisticate e altamente ottimizzate con test unitari a una linea. Ho implementato l'algoritmo di Dijkstra per testare una volta una classe.
K.Steff,

3
@ K.Steff, wow. Hai testato l'unità del test unitario per verificare che l'algoritmo Dijkstra fosse corretto?
Winston Ewert,

Un buon punto di fatto, sì, ma questa volta con test "banali". Erano anche test unitari per il programma originale (A *). Penso che sia davvero una buona pratica: testare algoritmi veloci contro implementazioni scadenti (ma corrette).
K.Steff,

1

Per testare che una fonte di numeri casuali sta generando qualcosa che almeno ha l'aspetto della casualità, vorrei che il test generasse una sequenza abbastanza grande di byte, li scrivessi in un file temporaneo e poi passassi allo strumento ent di Fourmilab . Fornire l'interruttore -t (terse) in modo da generare un CSV di facile analisi. Quindi controlla i vari numeri per vedere che sono "buoni".

Per decidere quali numeri sono buoni, utilizzare una fonte nota di casualità per calibrare il test. Il test dovrebbe quasi sempre passare quando viene dato un buon set di numeri casuali. Poiché anche una sequenza veramente casuale ha una probabilità di generare una sequenza che sembra non casuale, non è possibile ottenere un test che sicuramente supererà. Scegli solo le soglie che rendono improbabile che una sequenza casuale provochi un fallimento del test. La casualità non è divertente?

Nota: non è possibile scrivere un test che mostri che un PRNG genera una sequenza "casuale". Puoi solo scrivere un test che, se superato, indica una probabilità che la sequenza generata dal PRNG sia "casuale". Benvenuti nella gioia della casualità!


1

Caso 1: test di uno shuffle:

Considera un array [0, 1, 2, 3, 4, 5], mescolalo, cosa può andare storto? Le solite cose: a) non mescolare affatto, b) mescolare 1-5 ma non 0, mescolare 0-4 ma non 5, mescolare e generare sempre lo stesso schema, ...

Un test per catturarli tutti:

Mescola 100 volte, aggiungi i valori in ogni slot. La somma di ogni slot dovrebbe essere simile a ogni altro slot. Avg / Stddev può essere calcolato. (5 + 0) /2=2.5, 100 * 2.5 = 25. Il valore atteso è di circa 25, ad esempio.

Se i valori sono fuori intervallo, c'è una piccola possibilità che si ottenga un falso negativo. Puoi calcolare quanto è grande questa possibilità. Ripeti il ​​test. Bene - ovviamente c'è una piccola possibilità che il test fallisca 2 volte di seguito. Ma non hai una routine che cancella automaticamente la tua fonte, se il test unitario fallisce, vero? Eseguilo di nuovo!

Può fallire 3 volte di seguito? Forse dovresti tentare la fortuna alla lotteria.

Caso 2: tira un dado

La domanda del lancio del dado è la stessa domanda. Lancia i dadi 6000 volte.

for (i in 0 to 6000) 
    ++slot [Random.nextInt (6)];
return (slot.max - slot.min) < threshold;
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.