Adotando uma abordagem mais prática da resposta do pdr . TDD é tudo sobre design de software, e não testes. Você usa testes de unidade para verificar seu trabalho à medida que avança.
Portanto, em um nível de teste de unidade, é necessário projetar as unidades para que possam ser testadas de maneira completamente determinística. Você pode fazer isso pegando qualquer coisa que torne a unidade não determinística (como um gerador de números aleatórios) e abstraindo-a. Digamos que temos um exemplo ingênuo de um método que decide se uma jogada é boa ou não:
class Decider {
public boolean decide(float input, float risk) {
float inputRand = Math.random();
if (inputRand > input) {
float riskRand = Math.random();
}
return false;
}
}
// The usage:
Decider d = new Decider();
d.decide(0.1337f, 0.1337f);
Esse método é muito difícil de testar e a única coisa que você realmente pode verificar nos testes de unidade são seus limites ... mas isso exige muitas tentativas para chegar aos limites. Então, em vez disso, vamos abstrair a parte aleatória criando uma interface e uma classe concreta que agrupa a funcionalidade:
public interface IRandom {
public float random();
}
public class ConcreteRandom implements IRandom {
public float random() {
return Math.random();
}
}
A Decider
classe agora precisa usar a classe concreta através de sua abstração, ou seja, a Interface. Essa maneira de fazer as coisas é chamada injeção de dependência (o exemplo abaixo é um exemplo de injeção de construtor, mas você pode fazer isso com um setter também):
class Decider {
IRandom irandom;
public Decider(IRandom irandom) { // constructor injection
this.irandom = irandom;
}
public boolean decide(float input, float risk) {
float inputRand = irandom.random();
if (inputRand > input) {
float riskRand = irandom.random();
}
return false;
}
}
// The usage:
Decider d = new Decider(new ConcreteRandom);
d.decide(0.1337f, 0.1337f);
Você pode se perguntar por que esse "inchaço do código" é necessário. Bem, para iniciantes, agora você pode zombar do comportamento da parte aleatória do algoritmo porque o Decider
agora possui uma dependência que segue o IRandom
"contrato" do s. Você pode usar uma estrutura de simulação para isso, mas este exemplo é simples o suficiente para se codificar:
class MockedRandom() implements IRandom {
public List<Float> floats = new ArrayList<Float>();
int pos;
public void addFloat(float f) {
floats.add(f);
}
public float random() {
float out = floats.get(pos);
if (pos != floats.size()) {
pos++;
}
return out;
}
}
A melhor parte é que isso pode substituir completamente a implementação concreta "real". O código se torna fácil de testar assim:
@Before void setUp() {
MockedRandom mRandom = new MockedRandom();
Decider decider = new Decider(mRandom);
}
@Test
public void testDecisionWithLowInput_ShouldGiveFalse() {
mRandom.addFloat(0f);
assertFalse(decider.decide(0.1337f, 0.1337f));
}
@Test
public void testDecisionWithHighInputRandButLowRiskRand_ShouldGiveFalse() {
mRandom.addFloat(1f);
mRandom.addFloat(0f);
assertFalse(decider.decide(0.1337f, 0.1337f));
}
@Test
public void testDecisionWithHighInputRandAndHighRiskRand_ShouldGiveTrue() {
mRandom.addFloat(1f);
mRandom.addFloat(1f);
assertTrue(decider.decide(0.1337f, 0.1337f));
}
Espero que isso lhe dê idéias sobre como projetar seu aplicativo para que as permutações possam ser forçadas, para que você possa testar todos os casos extremos e outros enfeites.