Test di una funzione che utilizza un generatore di numeri casuali

1

Ho un metodo che fa parte dell'interfaccia che sto implementando. Questo metodo chiama un altro metodo privato che utilizza un generatore di numeri casuali per produrre l'output e restituirlo al metodo di chiamata. Voglio testare il metodo di chiamata. Come lo posso fare? Questo è il metodo sotto test:

 @Override
 public String generate(int wordCount) {
    StringBuilder sentence = new StringBuilder();

    List<String> selectedStrings = selectRandomStringsFromInternalVocabulary(wordCount, new Random());
    selectedStrings.sort(Comparator.<String>naturalOrder());

    swapOddIndexedStringsWithEvenIndexedStrings(selectedStrings);

    for (String word: selectedStrings)
        sentence.append(word)
                .append(" ");


    return sentence.toString().trim();
}

Questo è il metodo che usa il generatore di numeri casuali:

private List<String> selectRandomStringsFromInternalVocabulary(int wordCount, Random random) {
    List<String> selectedStrings = new ArrayList<>();
    int wordCountInVocabulary = internalVocabulary.size();

    while (wordCount-- != 0) {
        int stringIndex = random.nextInt(wordCountInVocabulary);
        selectedStrings.add(internalVocabulary.get(stringIndex));
    }

    return selectedStrings;
}

Ci sono alcune cose che ho pensato di poter fare: 1. Rendi privato il secondo pacchetto di metodi e testalo. Ma non voglio testare un metodo privato se posso evitarlo. 2. Aggiungi Casuale come parametro alla funzione chiamante e passa una simulazione durante il test. Tuttavia, la sua parte dell'interfaccia e altre classi che la implementano non usano RNG. Inoltre, non voglio che i clienti sappiano dei dettagli di implementazione.

Ho seguito queste domande: 1. Metodi di test unitari con output indeterminato 2. Unit Test di una funzione con comportamento casuale

Ma i suggerimenti sono simili a quelli che ho citato sopra.

    
posta sayeed 29.08.2017 - 11:19
fonte

4 risposte

10

La domanda che dovresti porci è "Che cosa sto cercando di provare ?" (NOTA: "dimostra" non è usato in senso matematico, ma semplicemente per verificare la validità di qualcosa). I test unitari servono per testare il funzionamento dell'applicazione nei parametri progettati. Diversi test di correttezza richiedono approcci diversi:

  • Puoi verificare che tutte le parole nella stringa restituita esistano nel vocabolario globale
  • Puoi verificare che ci siano o non ci siano ripetizioni di parole in quella stringa restituita
  • Puoi verificare che non ci sia spazio bianco estraneo all'inizio e alla fine
  • Puoi verificare che il numero di parole imponga numeri positivi
  • Puoi verificare che l'elenco di 0 parole fornisca una stringa vuota

Potrebbe essere sufficiente per le tue esigenze. Questi tipi di test sono anche abbastanza robusti. Non si romperanno se l'implementazione del generatore di numeri casuali cambia. Stai ancora utilizzando l'interfaccia esterna per ottenere le funzionalità interne.

Penso che in questo caso risulterebbe fragile e solo marginalmente utile per testare sequenze specifiche di stringhe. Nel tuo esempio non ci sono molti percorsi ramificati di cui preoccuparsi. Pensa ai contratti e in che cosa hai bisogno per assicurarti e abbracciare la casualità.

La cosa importante da notare è che non stai dimostrando casualità. Stai assicurando che il tuo metodo si comporti come previsto.

Se si testasse la casualità, sarebbe necessario fare qualche analisi statistica per dimostrare che la diffusione di numeri casuali era come previsto, ecc. Quelli tipi di test richiederebbero una "prova" nel senso matematico della parola. I test unitari non sono lo strumento giusto per quel lavoro.

    
risposta data 29.08.2017 - 15:49
fonte
6

Usa l'iniezione di dipendenza. Creare un'interfaccia IRandomNumberGenerator e inserirla nella classe (come argomento del costruttore) o funzione (come parametro) che ne ha bisogno. Può essere semplice come la seguente:

interface IRandomNumberGenerator
{
     int GetRandomNumber();
}

Ora crea due classi che implementano quell'interfaccia. La tua classe di generatore di numeri reali e una simulazione. La tua implementazione fittizia restituirà un numero predefinito, la tua implementazione reale restituirà un numero casuale reale. Prova con l'implementazione fittizia.

class MockRandomNumberGenerator : IRandomNumberGenerator
{
     private int _fakeRandomNumber;

     public MockRandomNumberGenerator(int fakeRandomNumber)
     {
          _fakeRandomNumber = fakeRandomNumber
     }

     public int GetRandomNumber()
     {
          return _fakeRandomNumber;
     }
}

In assenza di test condizione con numeri casuali reali generati, i risultati del test non saranno più riproducibili in quel caso. Sto usando la sintassi C #, dato che non ho familiarità con Java.

    
risposta data 29.08.2017 - 16:22
fonte
3

Durante il test:

Utilizza il generatore di numeri casuali con un seme specifico specificato. Allora otterrai sempre la stessa sequenza. Questo lo rende testabile.

    
risposta data 29.08.2017 - 11:35
fonte
1

Rendi casuale un membro della classe sottoposta a test. Inietta un valore di seme / prestabilito per il test e uno reale per il codice di produzione. Utilizza Iniezione di dipendenza .

class someclassname {
    private Random _rng;

    constructor(Random rng)

    public String generate(int wordCount)

    private selectRandomStringsFromInternalVocabulary(int wordCount) //words would be a better name here btw
}

Potresti voler rendere anche il vocabolario una dipendenza.

Inoltre, potresti avere un bug che questo risolverà comunque. Non conosco Java, ma passare a new Random() in un metodo chiamato molto frequentemente (ad esempio in un ciclo veloce) può andare male quando il costruttore predefinito utilizza un seme basato sul tempo.

    
risposta data 29.08.2017 - 12:35
fonte

Leggi altre domande sui tag