Tomando un enfoque más práctico para la respuesta de pdr . TDD tiene que ver con el diseño de software más que con las pruebas. Utiliza pruebas unitarias para verificar su trabajo a medida que avanza.
Entonces, en un nivel de prueba de unidad, debe diseñar las unidades para que puedan probarse de una manera completamente determinista. Puede hacer esto tomando cualquier cosa que haga que la unidad no sea determinista (como un generador de números aleatorios) y abstraiga eso. Digamos que tenemos un ejemplo ingenuo de un método para decidir si un movimiento es bueno o no:
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);
Este método es muy difícil de probar y lo único que realmente puede verificar en las pruebas unitarias son sus límites ... pero eso requiere muchos intentos para llegar a los límites. Por lo tanto, abstraigamos la parte aleatoria creando una interfaz y una clase concreta que envuelva la funcionalidad:
public interface IRandom {
public float random();
}
public class ConcreteRandom implements IRandom {
public float random() {
return Math.random();
}
}
La Decider
clase ahora necesita usar la clase concreta a través de su abstracción, es decir, la interfaz. Esta forma de hacer las cosas se llama inyección de dependencia (el ejemplo a continuación es un ejemplo de inyección de constructor, pero también puede hacerlo con un setter):
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);
Puede preguntarse por qué es necesaria esta "acumulación de código". Bueno, para empezar, ahora puede burlarse del comportamiento de la parte aleatoria del algoritmo porque Decider
ahora tiene una dependencia que sigue el IRandom
"contrato" de s. Puede usar un marco de imitación para esto, pero este ejemplo es lo suficientemente simple como para codificarse:
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;
}
}
La mejor parte es que esto puede reemplazar completamente la implementación concreta "real". El código se vuelve fácil de probar así:
@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 esto le brinde ideas sobre cómo diseñar su aplicación para que las permutaciones se puedan forzar para que pueda probar todos los casos límite y demás.