Tradičný unit test končí overením, či sa očakávaná hodnota zhoduje s vypočítanou, ale čo keď je vypočítaná hodnota náhodná, pretože závisí na generátore náhodných čísiel?
Zoberme si hlúpy generátor náhodných slov:
import java.util.*;
public class RandomWordGenerator {
private List<String> word = Arrays.asList("axolotl", "wolverine", "anteater", "platypus", "dormouse");
private Random random = new Random();
public String getRandomWord() {
int randomIndex = random.nextInt(word.size());
return word.get(randomIndex);
}
}
Generovanie náhodných čísiel rieši klasická trieda java.util.Random
s mnohými metódami na integery, longy a podobne.
Unit test by mohol vyzerať:
public class RandomWordGeneratorTest {
@Test
public void test() {
RandomWordGenerator generator = new RandomWordGenerator();
Assert.assertEquals(???????, generator.getRandomWord());
}
}
Čo uviesť namiesto otáznikov, keď hodnota môže byť pestrá?
Máme dve možnosti:
- buď vytvoriť vlastný nenáhodný náhodný generátor čísiel
- alebo využiť finty so seedmi.
V oboch prípadoch budeme musieť dodať do triedy generátora slov konštruktor, cez ktorý dotlačíme vlastnú implementáciu triedy Random
. Najlepšie to vyriešime dodaním druhého konštruktora (ktorý môže byť pokojne protected
, pretože stačí k nemu pristupovať z rovnakého balíčka). Druhá verzia RandomWordGenerator
a teda je:
public class RandomWordGenerator {
private List<String> word = Arrays.asList("axolotl", "wolverine", "anteater", "platypus", "dormouse");
private Random random;
public RandomWordGenerator() {
this(new Random());
}
protected RandomWordGenerator(Random random) {
this.random = random;
}
public String getRandomWord() {
int randomIndex = random.nextInt(word.size());
return word.get(randomIndex);
}
}
Vytvorenie vlastnej implementácie Random
Jedna možnosť je implementovať vlastnú podtriedu java.util.Random
. Dokumentácia tvrdí, že stačí prekryť metódu:
protected int next(int bits)
To je veľká výhoda: nemusíme prekrývať všetkých osem (či koľko) metód typu nextXXX()
, čo uľahčí implementáciu. (Tieto metódy totiž závisia na uvedenej metóde.)
Jednoduchá trieda, ktorá vždy vráti rovnaké číslo, môže vyzerať nasledovne:
import java.util.Random;
public class ConstantRandom extends Random {
private int constant;
public ConstantRandom(int constant) {
this.constant = constant;
}
@Override
protected int next(int bits) {
return constant;
}
}
Skúsme si ju otestovať:
import static org.junit.Assert.*;
import org.junit.Test;
public class ConstantRandomTest {
@Test
public void testFiveGeneratedNumbers() {
int constant = 1;
ConstantRandom constantRandom = new ConstantRandom(constant);
for (int i = 0; i < 5; i++) {
int random = constantRandom.nextInt();
if(random != constant) {
fail("Unexpected random number: " + i + ", but should be " + constant);
}
}
}
}
Unit test prejde, ak sa vygeneruje päť jednotiek: čo sa naozaj stane.
Použitie v generátore náhodných čísiel
Ukážme si použitie na unit teste generátora náhodných slov:
public class RandomWordGeneratorTest {
@Test
public void test() {
ConstantRandom constantRandom = new ConstantRandom(1);
RandomWordGenerator generator = new RandomWordGenerator(constantRandom);
Assert.assertEquals("wolverine", generator.getRandomWord());
}
}
Test by mal vždy vrátiť rosomáka, ten je totiž v zozname slov druhým prvkom (prvkom s indexom 1).
Využitie seedov
Nenechajte sa oklamať: trieda Random
je len deterministický pseudonáhodný generátor čísiel. Prečo deterministický? Postupnosť vygenerovaných náhodných čísiel možno totiž predvídať: stačí, že počiatočný seed je rovnaký.
Vyskúšajte si to:
Random random1 = new Random(1);
Random random2 = new Random(2);
for(int i = 0; i < 10; i++) {
System.out.println(random1.nextInt() + " " + random2.nextInt());
}
Ukážkový kód vypľuje desať dvojíc s rovnakými prvkami. Túto vlastnosť môžeme využiť pri testovaní.
public class RandomWordGeneratorTest {
@Test
public void testWithConstantSeedRandom() {
Random random = new Random(1);
RandomWordGenerator generator = new RandomWordGenerator(random);
Assert.assertEquals("axolotl", generator.getRandomWord());
}
}
Ak by sme generovali povedzme päť slov so seedom 1, získali by sme vždy axolotla, vtákopyska, mravcoleva, ešte jedného vtákopyska a plcha.
Algoritmy garantujú, že rovnaký seed garantuje rovnakú postupnosť: a to bez ohľadu na použitú architektúru či platformu, kde beží Java.
Kryptograficky silné generátory
Ak sa zdá, že takéto generovanie čísiel je smiešne, môžete sa obrátiť na triedu java.security.SecureRandom
. Tá dedí od Random
u a rozhodne negeneruje predvídateľný tok čísiel.