Test unitari di algoritmi intrinsecamente casuali / non deterministici


41

Il mio attuale progetto, in breve, prevede la creazione di "eventi casualmente vincolanti". Fondamentalmente sto generando un programma di ispezioni. Alcuni di essi si basano su rigidi vincoli di pianificazione; esegui un'ispezione una volta alla settimana il venerdì alle 10:00. Altre ispezioni sono "casuali"; ci sono requisiti di base configurabili come "un'ispezione deve avvenire 3 volte a settimana", "l'ispezione deve avvenire tra le 9:00 e le 21:00" e "non devono esserci due ispezioni nello stesso periodo di 8 ore", ma all'interno di qualunque vincolo sia stato configurato per un determinato insieme di ispezioni, le date e gli orari risultanti non dovrebbero essere prevedibili.

I test unitari e TDD, IMO, hanno un grande valore in questo sistema in quanto possono essere utilizzati per costruirlo in modo incrementale mentre il suo set completo di requisiti è ancora incompleto e assicurarsi che non lo stia "progettando troppo" per fare le cose che faccio attualmente so che ho bisogno. I rigidi programmi erano un pezzo per TDD. Tuttavia, trovo difficile definire veramente ciò che sto testando quando scrivo test per la parte casuale del sistema. Posso affermare che tutti i tempi prodotti dallo scheduler devono rientrare nei vincoli, ma potrei implementare un algoritmo che supera tutti questi test senza che i tempi effettivi siano molto "casuali". In realtà è esattamente quello che è successo; Ho riscontrato un problema in cui i tempi, sebbene non prevedibili esattamente, rientravano in un piccolo sottoinsieme degli intervalli di data / ora consentiti. L'algoritmo superava ancora tutte le affermazioni che ritenevo di poter ragionevolmente fare e non potevo progettare un test automatizzato che avrebbe fallito in quella situazione, ma che avrebbe superato risultati "più casuali". Ho dovuto dimostrare che il problema è stato risolto ristrutturando alcuni test esistenti per ripetersi più volte e verificare visivamente che i tempi generati rientrassero nell'intero intervallo consentito.

Qualcuno ha qualche suggerimento per la progettazione di test che dovrebbero aspettarsi comportamenti non deterministici?


Grazie a tutti per i suggerimenti. L'opinione principale sembra essere che ho bisogno di un test deterministico per ottenere risultati deterministici, ripetibili, asseribili . Ha senso.

Ho creato una serie di test "sandbox" che contengono algoritmi candidati per il processo vincolante (il processo mediante il quale un array di byte che potrebbe essere lungo diventa un lungo tra un minimo e un massimo). Quindi eseguo quel codice attraverso un ciclo FOR che fornisce all'algoritmo diversi array di byte noti (valori da 1 a 10.000.000 solo all'inizio) e ha l'algoritmo vincolato a un valore compreso tra 1009 e 7919 (sto usando numeri primi per garantire un l'algoritmo non passerebbe da alcuni GCF casuali tra gli intervalli di input e output). Vengono contati i valori vincolati risultanti e viene prodotto un istogramma. Per "passare", tutti gli input devono essere riflessi nell'istogramma (sanità mentale per garantire che non abbiamo "perso" alcuno) e la differenza tra due secchi nell'istogramma non può essere maggiore di 2 (dovrebbe essere <= 1 , ma rimanete sintonizzati). L'algoritmo vincente, se presente, può essere tagliato e incollato direttamente nel codice di produzione e messo a punto un test permanente per la regressione.

Ecco il codice:

    private void TestConstraintAlgorithm(int min, int max, Func<byte[], long, long, long> constraintAlgorithm)
    {
        var histogram = new int[max-min+1];
        for (int i = 1; i <= 10000000; i++)
        {
            //This is the stand-in for the PRNG; produces a known byte array
            var buffer = BitConverter.GetBytes((long)i);

            long result = constraintAlgorithm(buffer, min, max);

            histogram[result - min]++;
        }

        var minCount = -1;
        var maxCount = -1;
        var total = 0;
        for (int i = 0; i < histogram.Length; i++)
        {
            Console.WriteLine("{0}: {1}".FormatWith(i + min, histogram[i]));
            if (minCount == -1 || minCount > histogram[i])
                minCount = histogram[i];
            if (maxCount == -1 || maxCount < histogram[i])
                maxCount = histogram[i];
            total += histogram[i];
        }

        Assert.AreEqual(10000000, total);
        Assert.LessOrEqual(maxCount - minCount, 2);
    }

    [Test, Explicit("sandbox, does not test production code")]
    public void TestRandomizerDistributionMSBRejection()
    {
        TestConstraintAlgorithm(1009, 7919, ConstrainByMSBRejection);
    }

    private long ConstrainByMSBRejection(byte[] buffer, long min, long max)
    {
        //Strip the sign bit (if any) off the most significant byte, before converting to long
        buffer[buffer.Length-1] &= 0x7f;
        var orig = BitConverter.ToInt64(buffer, 0);
        var result = orig;
        //Apply a bitmask to the value, removing the MSB on each loop until it falls in the range.
        var mask = long.MaxValue;
        while (result > max - min)
        {
            mask >>= 1;
            result &= mask;
        }
        result += min;

        return result;
    }

    [Test, Explicit("sandbox, does not test production code")]
    public void TestRandomizerDistributionLSBRejection()
    {
        TestConstraintAlgorithm(1009, 7919, ConstrainByLSBRejection);
    }

    private long ConstrainByLSBRejection(byte[] buffer, long min, long max)
    {
        //Strip the sign bit (if any) off the most significant byte, before converting to long
        buffer[buffer.Length - 1] &= 0x7f;
        var orig = BitConverter.ToInt64(buffer, 0);
        var result = orig;

        //Bit-shift the number 1 place to the right until it falls within the range
        while (result > max - min)
            result >>= 1;

        result += min;
        return result;
    }

    [Test, Explicit("sandbox, does not test production code")]
    public void TestRandomizerDistributionModulus()
    {
        TestConstraintAlgorithm(1009, 7919, ConstrainByModulo);
    }

    private long ConstrainByModulo(byte[] buffer, long min, long max)
    {
        buffer[buffer.Length - 1] &= 0x7f;
        var result = BitConverter.ToInt64(buffer, 0);

        //Modulo divide the value by the range to produce a value that falls within it.
        result %= max - min + 1;

        result += min;
        return result;
    }

... ed ecco i risultati:

inserisci qui la descrizione dell'immagine

Il rifiuto dell'LSB (spostando bit il numero fino a quando non rientra nell'intervallo) era TERRIBILE, per una ragione molto facile da spiegare; quando dividi qualsiasi numero per 2 fino a quando è inferiore a un massimo, esci non appena lo è, e per qualsiasi intervallo non banale, che distorcerà i risultati verso il terzo superiore (come si è visto nei risultati dettagliati dell'istogramma ). Questo era esattamente il comportamento che ho visto dalle date finite; tutte le volte erano nel pomeriggio, in giorni molto specifici.

Il rifiuto di MSB (rimuovendo il bit più significativo dal numero uno alla volta fino a quando non rientra nell'intervallo) è meglio, ma ancora una volta, poiché stai tagliando numeri molto grandi con ogni bit, non è distribuito uniformemente; è improbabile che si ottengano numeri nelle estremità superiore e inferiore, quindi si ottiene un orientamento verso il terzo medio. Ciò potrebbe giovare a qualcuno che cerca di "normalizzare" i dati casuali in una curva a campana, ma una somma di due o più numeri casuali più piccoli (simile al lancio di dadi) ti darebbe una curva più naturale. Per i miei scopi, fallisce.

L'unico che ha superato questo test è stato quello di limitare la divisione del modulo, che si è rivelata anche la più veloce delle tre. Modulo, per definizione, produrrà il più uniforme possibile una distribuzione dati gli input disponibili.


2
Quindi, alla fine, vuoi un programma che guardi l'output di un generatore di numeri casuali e decida se è casuale? Come in "5,4,10,31,120,390,2,3,4" era casuale ma "49,39,1,10,103,12,4,189" non lo era?
psr

No, ma evitare l'introduzione di distorsioni tra il PRNG reale e il risultato finale sarebbe bello.
KeithS,

Quindi sembra che deridere il PRNG sia OK Non hai bisogno di valori casuali effettivi per verificare che non manchi i valori. Se hai un bug che comprime valori casuali in un sottoinsieme troppo piccolo di intervalli consentiti, devi sbagliare alcuni valori specifici.
psr

Dovresti anche testare le combinazioni. Avere ispezioni attese all'incirca uguali all'ora non proteggerà dal caso in cui, per esempio, un'ispezione di martedì alle 11 è sempre seguita da un sabato alle 14 e un venerdì alle 10.
David Thornley,

Questo è più un test del PRNG stesso; il test dei meccanismi vincolanti, come strutturato sopra, fallirebbe sempre un test del genere perché viene fornito un set di dati completamente non casuale. Supponendo che il meccanismo di vincolo non stia tentando di "ordinare" i dati casuali che definirei "test esterni", che è qualcosa che un test unitario non dovrebbe fare.
KeithS

Risposte:


17

Ciò che in realtà vuoi testare qui, suppongo, è che dato un set specifico di risultati dal randomizzatore, il resto del tuo metodo funziona correttamente.

Se è quello che stai cercando, prendi in giro il randomizzatore, per renderlo deterministico all'interno dei regni del test.

In genere ho oggetti finti per tutti i tipi di dati non deterministici o imprevedibili (al momento della stesura del test), inclusi generatori GUID e DateTime.Now.

Modifica, dai commenti: devi deridere il PRNG (quel termine mi è sfuggito ieri sera) al livello più basso possibile - cioè. quando genera l'array di byte, non dopo averli trasformati in Int64s. O anche su entrambi i livelli, in modo da poter testare la conversione in una matrice di Int64 funziona come previsto e quindi verificare separatamente che la conversione in una matrice di DateTimes funzioni come previsto. Come diceva Jonathon, potresti farlo semplicemente assegnandogli un seme, oppure puoi restituire l'array di byte.

Preferisco quest'ultimo perché non si romperà se l'implementazione del framework di un PRNG cambia. Tuttavia, un vantaggio nel dargli il seme è che se trovi un caso in produzione che non ha funzionato come previsto, devi solo aver registrato un numero per poterlo replicare, al contrario dell'intero array.

Detto questo, devi ricordare che si chiama Pseudo Random Number Generator per un motivo. Potrebbero esserci dei pregiudizi anche a quel livello.


1
No. In questo caso, ciò che voglio testare è lo stesso randomizzatore e asserire che i valori "casuali" generati dal randomizzatore rientrano nei vincoli specificati pur essendo "casuali", in quanto non distorti verso una distribuzione irregolare attraverso l'ammissibile intervalli di tempo. Posso e testare deterministicamente che una determinata data / ora passa correttamente o fallisce un determinato vincolo, ma il vero problema che ho riscontrato era che le date prodotte dal randomizzatore erano distorte e quindi prevedibili.
KeithS,

L'unica cosa a cui riesco a pensare è che il randomizzatore sputi un mucchio di date e crei un istogramma, quindi asserisce che i valori sono distribuiti in modo relativamente uniforme. Ciò sembra estremamente pesante, e ancora non deterministico poiché qualsiasi set di dati veramente casuale può mostrare un'apparente propensione a confutare un set più grande.
KeithS,

1
Questo è un test che si interromperà di tanto in tanto e imprevedibilmente. Non lo vuoi. Penso che tu abbia frainteso il mio punto, a dire il vero. Da qualche parte all'interno di quello che stai chiamando il randomizzatore, ci deve essere una riga di codice che genera un numero casuale, no? Quella linea è ciò a cui mi riferisco come randomizzatore, e il resto di ciò che stai chiamando randomizzatore (la distribuzione di date basata su dati "casuali") è ciò che vuoi testare. Oppure mi sfugge qualcosa?
pdr,

le metriche correttamente statistiche delle sequenze casuali (correlazione, correlazione a blocchi, media, deviazioni standard, ecc.) non riusciranno a soddisfare la gamma di aspettative solo se si eseguono campioni veramente piccoli. Aumenta i tuoi set di campionamento e / o aumenta le barre di errore consentite
lurscher

1
"un po 'di pregiudizio anche a quel livello" Se usi un buon PRNG, non sarai in grado di trovare alcun test (con limiti computazionali realistici) in grado di distinguerlo dalla casualità reale. Quindi in pratica si può presumere che un buon PRNG non abbia alcun pregiudizio.
CodesInChaos,

23

Sembrerà una risposta stupida, ma la lancerò là perché è così che l'ho vista fare prima:

Disaccoppia il tuo codice dal PRNG - passa il seme di randomizzazione in tutto il codice che usa la randomizzazione. Quindi puoi determinare i valori "funzionanti" da un singolo seme (o più semi che ti farebbero sentire meglio). Questo ti darà la possibilità di testare adeguatamente il tuo codice senza dover fare affidamento sulla legge di grandi numeri.

Sembra folle, ma è così che fanno i militari (o quello o usano un 'tavolo casuale' che non è affatto casuale)


Esatto: se non riesci a testare un elemento di un algoritmo, sottraggilo e deridilo
Steve Greatrex,

Non ho specificato un valore seme deterministico; invece, ho rimosso del tutto l'elemento "random" in modo da non dover nemmeno fare affidamento sullo specifico algoritmo PRNG. Sono stato in grado di testare che, data una vasta gamma di numeri distribuiti uniformemente, l'algoritmo con cui sono andato poteva limitare quelli a un intervallo più piccolo senza introdurre distorsioni. Il PRNG stesso dovrebbe essere adeguatamente testato da chiunque lo abbia sviluppato (sto usando RNGCryptoServiceProvider).
KeithS

Per quanto riguarda l'approccio "tabella casuale", è anche possibile utilizzare un'implementazione di test che contiene un algoritmo di generazione numerica "reversibile". Ciò consente di "riavvolgere" il PRNG o anche di interrogarlo per vedere quali erano gli ultimi N output. Consentirebbe un debug molto più approfondito in determinati scenari.
Darien,

Questo non è così stupido - è lo stesso metodo utilizzato da Google per riprodurre l'iniezione di errori nei test di Spanner secondo il loro articolo :)
Akshat Mahajan

6

"È casuale (abbastanza)" risulta essere una domanda incredibilmente sottile. La risposta breve è che un test unitario tradizionale non lo taglierà: dovrai generare un sacco di valori casuali e sottoporli a vari test statistici che ti danno la certezza che sono abbastanza casuali per le tue esigenze.

Ci sarà uno schema: dopotutto stiamo usando generatori di numeri psuedo casuali. Ma a un certo punto le cose saranno "abbastanza buone" per la tua applicazione (dove abbastanza bene varia MOLTO tra i giochi ad una estremità, dove sono sufficienti generatori relativamente semplici, fino alla crittografia in cui hai davvero bisogno di sequenze per essere impossibile determinare da un attaccante determinato e ben equipaggiato).

L'articolo di Wikipedia http://en.wikipedia.org/wiki/Randomness_tests e i suoi collegamenti di follow-up contengono ulteriori informazioni.


Persino i PRNG mediocri non mostreranno schemi in nessun test statistico. Per buoni PRNG è praticamente impossibile distinguerli da numeri casuali reali.
CodesInChaos,

4

Ho due risposte per te.

=== PRIMA RISPOSTA ===

Non appena ho visto il titolo della tua domanda, sono venuto a saltare e proporre la soluzione. La mia soluzione era la stessa di quella proposta da molti altri: deridere il tuo generatore di numeri casuali. Dopotutto, ho creato diversi programmi diversi che hanno richiesto questo trucco per scrivere buoni test unitari e ho iniziato a rendere l'accesso deridibile a numeri casuali una pratica standard in tutta la mia codifica.

Ma poi ho letto la tua domanda. E per il problema particolare che descrivi, questa non è la risposta. Il tuo problema non era che dovevi rendere prevedibile un processo che utilizzava numeri casuali (quindi sarebbe testabile). Piuttosto, il tuo problema era verificare che il tuo algoritmo mappasse l'output uniformemente casuale dal tuo RNG all'output uniforme all'interno dei vincoli dal tuo algoritmo - che se l'RNG sottostante fosse uniforme avrebbe comportato tempi di ispezione uniformemente distribuiti (soggetto al vincoli di problema).

Questo è un problema davvero difficile (ma abbastanza ben definito). Ciò significa che è un problema INTERESSANTE. Immediatamente ho iniziato a pensare ad alcune idee davvero grandiose su come risolverlo. Quando ero un programmatore di hotshot avrei potuto iniziare a fare qualcosa con queste idee. Ma non sono più un programmatore di hotshot ... Mi piace il fatto di essere più esperto e più abile ora.

Quindi, invece di immergermi nel difficile problema, ho pensato a me stesso: qual è il valore di questo? E la risposta è stata deludente. Il tuo bug è già stato risolto e in futuro sarai diligente su questo problema. Le circostanze esterne non possono innescare il problema, ma solo modifiche all'algoritmo. L'unica ragione per affrontare questo interessante problema era al fine di soddisfare le pratiche di TDD (Test Driven Design). Se c'è una cosa che ho imparato è che aderire ciecamente a qualsiasi pratica quando non è preziosa causa problemi. Il mio suggerimento è questo: basta non scrivere un test per questo e andare avanti.


=== SECONDA RISPOSTA ===

Wow ... che bel problema!

Quello che devi fare qui è scrivere un test che verifichi che il tuo algoritmo per la selezione delle date e degli orari di ispezione produrrà un output distribuito uniformemente (entro i limiti del problema) se l'RNG che utilizza produce numeri distribuiti uniformemente. Ecco alcuni approcci, ordinati per livello di difficoltà.

  1. Puoi applicare la forza bruta. Esegui l'algoritmo solo un intero gruppo di volte, con un vero RNG come input. Ispezionare i risultati di output per vedere se sono distribuiti uniformemente. Il test dovrà avere esito negativo se la distribuzione varia da perfettamente uniforme per più di una determinata soglia e, per garantire la cattura di problemi, la soglia non può essere impostata TROPPO bassa. Ciò significa che avrai bisogno di un ENORME numero di esecuzioni per essere sicuro che la probabilità di un falso positivo (un fallimento del test per caso) sia molto piccola (ben <1% per una base di codice di medie dimensioni; ancor meno per una grande base di codice).

  2. Considera il tuo algoritmo come una funzione che accetta la concatenazione di tutti gli output RNG come input, quindi produce i tempi di ispezione come output. Se sai che questa funzione è continua a tratti, c'è un modo per testare la tua proprietà. Sostituisci l'RNG con un RNG beffardo ed esegui l'algoritmo numerose volte, producendo un output RNG uniformemente distribuito. Quindi, se il tuo codice richiede 2 chiamate RNG, ciascuna nell'intervallo [0..1], potresti avere il test che esegue l'algoritmo 100 volte, restituendo i valori [(0.0,0.0), (0.0,0.1), (0.0, 0,2), ... (0,0,0,9), (0,1,0,0), (0,1,0,1), ... (0,9,0,9)]. Quindi è possibile verificare se l'output delle 100 esecuzioni è stato (approssimativamente) distribuito uniformemente nell'intervallo consentito.

  3. Se devi VERAMENTE verificare l'algoritmo in modo affidabile e non puoi fare ipotesi sull'algoritmo O eseguire un gran numero di volte, puoi comunque attaccare il problema, ma potresti aver bisogno di alcuni vincoli su come programmare l'algoritmo . Dai un'occhiata a PyPy e al loro approccio allo spazio oggetti come esempio. È possibile creare uno spazio oggetti che, invece di eseguire effettivamente l'algoritmo, calcola semplicemente la forma della distribuzione di output (supponendo che l'input RNG sia uniforme). Naturalmente, ciò richiede la creazione di un tale strumento e la creazione dell'algoritmo in PyPy o in qualche altro strumento in cui è facile apportare modifiche drastiche al compilatore e utilizzarlo per analizzare il codice.


3

Per i test unitari, sostituire il generatore casuale con una classe che genera risultati prevedibili che coprano tutti i casi angolari . Ad esempio, assicurati che il tuo pseudo-randomizzatore generi il valore più basso possibile e il valore più alto possibile e lo stesso risultato più volte di seguito.

Non si desidera che i test delle unità trascurino, ad esempio, i bug off-by-one che si verificano quando Random.nextInt (1000) restituisce 0 o 999.


3

Potresti dare un'occhiata a Sevcikova et al: "Test automatizzati di sistemi stocastici: un approccio statisticamente fondato" ( PDF ).

La metodologia è implementata in vari casi di test per la piattaforma di simulazione UrbanSim .


Questa è roba buona, lì.
KeithS

2

Un semplice approccio con istogramma è un buon primo passo, ma non è sufficiente per dimostrare la casualità. Per un PRNG uniforme dovresti anche (almeno) generare un grafico a dispersione bidimensionale (dove x è il valore precedente e y è il nuovo valore). Questa trama dovrebbe anche essere uniforme. Questo è complicato nella tua situazione perché ci sono non linearità intenzionali nel sistema.

Il mio approccio sarebbe:

  1. convalidare (o dare per scontato) che il PRNG di origine sia sufficientemente casuale (utilizzando misure statistiche standard)
  2. verificare che una conversione da PRNG a datetime non vincolata sia sufficientemente casuale nello spazio di output (ciò verifica una mancanza di parzialità nella conversione). Il tuo semplice test di uniformità del primo ordine dovrebbe essere sufficiente qui.
  3. verificare che i casi vincolati siano sufficientemente uniformi (un semplice test di uniformità del primo ordine sui contenitori validi).

Ognuno di questi test è statistico e richiede un gran numero di punti campione per evitare falsi positivi e falsi negativi con un alto grado di sicurezza.

Per quanto riguarda la natura dell'algoritmo di conversione / vincolo:

Dato: metodo per generare un valore pseudo-casuale p dove 0 <= p <= M

Esigenza: uscita y nell'intervallo (possibilmente discontinuo) 0 <= y <= N <= M

Algoritmo:

  1. calcola r = floor(M / N), cioè, il numero di intervalli di output completi che rientrano nell'intervallo di input.
  2. calcola il valore massimo accettabile per p :p_max = r * N
  3. genera valori per p fino a quando non p_maxviene trovato un valore minore o uguale a
  4. calcolare y = p / r
  5. se y è accettabile, restituirlo, altrimenti ripetere con il passaggio 3

La chiave è scartare valori inaccettabili piuttosto che piegarli in modo non uniforme.

nello pseudo-codice:

# assume prng generates non-negative values
def randomInRange(min, max, prng):
    range = max - min
    factor = prng.max / range

    do:
        value = prng()
    while value > range * factor
    return (value / factor) + min

def constrainedRandom(constraint, prng):
    do:
        value = randomInRange(constraint.min, constraint.max, prng)
    while not constraint.is_acceptable(value)

1

Oltre a convalidare che il tuo codice non fallisce, o getta le giuste eccezioni nei posti giusti, puoi creare coppie di input / risposte valide (anche calcolandolo manualmente), alimentare l'input nel test e assicurarti che restituisca la risposta prevista. Non eccezionale, ma è praticamente tutto ciò che puoi fare, imho. Tuttavia, nel tuo caso non è davvero casuale, una volta creato il tuo programma puoi verificare la conformità delle regole - devi avere 3 ispezioni a settimana, tra 9-9; non vi è alcuna reale necessità o capacità di testare i tempi esatti in cui si è verificata l'ispezione.


1

Non c'è davvero modo migliore di eseguirlo un sacco di volte e vedere se ottieni la distribuzione che desideri. Se hai 50 possibili piani di ispezione, esegui il test 500 volte e assicurati che ogni programma venga utilizzato quasi 10 volte. Puoi controllare i semi del tuo generatore casuale per renderlo più deterministico, ma ciò renderà anche i tuoi test più strettamente accoppiati ai dettagli di implementazione.


Ma se è veramente casuale, a volte qualche programma non verrà usato affatto; e occasionalmente, alcuni programmi verranno utilizzati più di 20 volte. Non so come intendi testare che ogni programma viene utilizzato "quasi 10 volte", ma qualunque condizione tu test qui, avrai un test che a volte fallisce quando il programma sta lavorando alle specifiche.
Dawood dice di reintegrare Monica il

@DawoodibnKareem con una dimensione del campione sufficiente (e limiti ragionevoli di uniformità) è possibile ridurre la probabilità che il test non superi 1 miliardo. E generalmente statistiche del genere sono esponenziali con n, quindi ci vuole meno di quanto ci si aspetti per arrivare a quei numeri.
mbrig

1

Non è possibile testare una condizione nebulosa che non ha una definizione concreta. Se le date generate superano tutti i test, in teoria la tua applicazione funziona correttamente. Il computer non è in grado di dirti se le date sono "abbastanza casuali" perché non è in grado di riconoscere i criteri per tale test. Se tutti i test vengono superati ma il comportamento dell'applicazione non è ancora adatto, la copertura del test è empiricamente inadeguata (dal punto di vista TDD).

A mio avviso, la cosa migliore è implementare alcuni vincoli arbitrari di generazione della data in modo che la distribuzione superi un test dell'olfatto umano.


2
Puoi assolutamente determinare la casualità tramite test automatizzati. È sufficiente generare un numero sufficientemente ampio di campioni e applicare test standard di casualità per rilevare i pregiudizi nel sistema. Questo è un esercizio di programmazione universitaria piuttosto standard.
Frank Szczerba,

0

Basta registrare l'output del tuo randomizzatore (sia pseudo o quantico / caotico o mondo reale). Quindi salva e riproduci quelle sequenze "casuali" che si adattano alle tue esigenze di test o che espongono potenziali problemi e bug, mentre costruisci i tuoi casi di test unitari.


0

Questo caso sembra ideale per i test basati sulle proprietà .

In breve, è una modalità di test in cui il framework di test genera input per il codice testato e le asserzioni di test convalidano le proprietà degli output. Il framework può quindi essere abbastanza intelligente da "attaccare" il codice in prova e provare ad angolarlo in un errore. Il framework di solito è anche abbastanza intelligente da dirottare il seme del generatore di numeri casuali. In genere è possibile configurare il framework per generare al massimo N casi di test o eseguire al massimo N secondi e ricordare casi di test falliti dell'ultima esecuzione e rieseguirli prima con la versione di codice più recente. Ciò consente un ciclo di iterazione rapido durante lo sviluppo e test lenti e completi fuori banda / in CI.

Ecco un esempio (stupido, non riuscito) che verifica la sumfunzione:

@given(lists(floats()))
def test_sum(alist):
    result = sum(alist)
    assert isinstance(result, float)
    assert result > 0
  • il framework di test genererà un elenco alla volta
  • il contenuto dell'elenco sarà rappresentato da numeri in virgola mobile
  • sum viene chiamato e le proprietà del risultato vengono convalidate
  • il risultato è sempre float
  • il risultato è positivo

Questo test troverà un sacco di "bug" in sum(commenta se sei stato in grado di indovinare tutti questi da solo):

  • sum([]) is 0 (int, non un float)
  • sum([-0.9]) è negativo
  • sum([0.0]) non è strettamente positivo
  • sum([..., nan]) is nan che non è positivo

Con le impostazioni predefinite, hpythesisinterrompe il test dopo aver trovato 1 input "errato", il che è positivo per TDD. Ho pensato che fosse possibile configurarlo per segnalare molti / tutti input "cattivi", ma non riesco a trovare quelle opzioni ora.

Nel caso OP, le proprietà convalidate saranno più complesse: tipo di ispezione A presente, tipo di ispezione A tre volte a settimana, tempo di ispezione B sempre alle 12:00, tipo di ispezione C dalle 9 alle 9, [il programma indicato è per una settimana] ispezioni dei tipi A, B, C tutti i presenti, ecc.

La libreria più conosciuta è QuickCheck per Haskell, vedere la pagina Wikipedia in basso per un elenco di tali librerie in altre lingue:

https://en.wikipedia.org/wiki/QuickCheck

Ipotesi (per Python) ha una buona descrizione di questo tipo di test:

https://hypothesis.works/articles/what-is-property-based-testing/


-1

ciò che è possibile testare l'unità è la logica determinare se le date casuali sono valide o se è necessario selezionare un'altra data casuale.

Non c'è modo di testare un generatore di date casuale a corto di ottenere un sacco di date e decidere se sono adeguatamente casuali.


-1

Il tuo obiettivo non è scrivere unit test e superarli, ma assicurarti che il tuo programma soddisfi i suoi requisiti. L'unico modo in cui puoi farlo è innanzitutto definire con precisione i tuoi requisiti. Ad esempio, hai citato "tre ispezioni settimanali a orari casuali". Direi che i requisiti sono: (a) 3 ispezioni (non 2 o 4), (b) a volte non prevedibili da parte di persone che non vogliono essere ispezionate inaspettatamente, e (c) non troppo vicine tra loro - due ispezioni distanti cinque minuti sono probabilmente inutili, forse neanche troppo distanti.

Quindi scrivi i requisiti più precisamente di me. (a) e (c) sono facili. Per (b), potresti scrivere un codice quanto più intelligente possibile che cerca di prevedere l'ispezione successiva e, per superare il test unitario, quel codice non deve essere in grado di prevedere meglio della pura ipotesi.

E ovviamente devi essere consapevole del fatto che se le tue ispezioni sono veramente casuali, qualsiasi algoritmo di previsione potrebbe essere corretto per puro caso, quindi devi essere sicuro che tu e i tuoi test unitari non vi facciate prendere dal panico se ciò accade. Forse eseguire qualche altro test. Non mi preoccuperei di testare il generatore di numeri casuali, perché alla fine è il programma di ispezione che conta e non importa come è stato creato.


No semplicemente no. I test unitari dimostrano che il programma soddisfa i suoi requisiti, quindi i due sono la stessa cosa. E non mi occupo di scrivere software predittivo per decodificare algoritmi casuali. Se lo fossi, non te lo direi, farei un sito Web sicuro e crackando, prevedendo le loro chiavi e vendendo i segreti al miglior offerente. La mia azienda sta scrivendo un programmatore che crea tempi vincolanti ma imprevedibili all'interno dei vincoli, e ho bisogno di test deterministici per dimostrare di averlo fatto, non di quelli probabilistici che dicono che sono abbastanza sicuro.
KeithS
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.