Более практичный подход к ответу pdr . TDD - это скорее дизайн программного обеспечения, а не тестирование. Вы используете модульные тесты для проверки своей работы.
Таким образом, на уровне юнит-теста вам нужно спроектировать блоки так, чтобы они могли быть протестированы полностью детерминированным способом. Вы можете сделать это, взяв все, что делает единицу недетерминированной (например, генератор случайных чисел) и абстрагировав это. Скажем, у нас есть наивный пример того, как метод решает, хорош ли ход или нет:
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);
Этот метод очень сложно протестировать, и единственное, что вы действительно можете проверить в модульных тестах, это его границы ... но для этого нужно много попыток. Поэтому вместо этого давайте абстрагируем случайную часть, создав интерфейс и конкретный класс, который оборачивает функциональность:
public interface IRandom {
public float random();
}
public class ConcreteRandom implements IRandom {
public float random() {
return Math.random();
}
}
Теперь Decider
класс должен использовать конкретный класс через его абстракцию, то есть интерфейс. Такой способ называется внедрением зависимостей (пример ниже является примером внедрения конструктора, но вы также можете сделать это с помощью установщика):
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);
Вы можете спросить себя, зачем нужен этот «кодовый блат». Ну, во-первых, теперь вы можете посмеяться над поведением случайной части алгоритма, потому что Decider
теперь есть зависимость, которая следует за IRandom
«контрактом». Вы можете использовать для этого фальшивый фреймворк, но этот пример достаточно прост для написания кода:
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;
}
}
Самое приятное то, что это может полностью заменить «фактическую» конкретную реализацию. Код становится легко тестировать следующим образом:
@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));
}
Надеюсь, что это даст вам идеи о том, как спроектировать ваше приложение, чтобы перестановки можно было форсировать, чтобы вы могли протестировать все крайние случаи и еще много чего.