Мне было интересно узнать об общем использовании заглушек для модульных тестов по сравнению с использованием реальных (производственных) реализаций, а также о том, не сталкиваемся ли мы с довольно неприятной проблемой при использовании заглушек, как показано здесь:
Предположим, у нас есть этот (псевдо) код:
public class A {
public int getInt() {
if (..) {
return 2;
}
else {
throw new AException();
}
}
}
public class B {
public void doSomething() {
A a = new A();
try {
a.getInt();
}
catch(AException e) {
throw new BException(e);
}
}
}
public class UnitTestB {
@Test
public void throwsBExceptionWhenFailsToReadInt() {
// Stub A to throw AException() when getInt is called
// verify that we get a BException on doSomething()
}
}
Теперь предположим, что в какой-то момент позже, когда мы напишем еще сотни тестов, мы поймем, что A на самом деле не должен генерировать AException, а вместо этого AOtherException. Мы исправляем это:
public class A {
public int getInt() {
if (..) {
return 2;
}
else {
throw new AOtherException();
}
}
}
Теперь мы изменили реализацию A на исключение AOtherException и запустили все наши тесты. Они проходят. Что не так хорошо, так это то, что модульное тестирование на B проходит, но не так. Если мы соберем вместе A и B в производство на этом этапе, B распространит AOtherException, потому что его реализация думает, что A выдает AException.
Если бы вместо этого мы использовали реальную реализацию A для нашего теста throwsBExceptionWhenFailsToReadInt, то он бы потерпел неудачу после изменения A, поскольку B больше не генерировал бы BException.
Это просто пугающая мысль, что если бы у нас была тысяча тестов, структурированных как в приведенном выше примере, и мы изменили одну крошечную вещь, то все модульные тесты по-прежнему работали бы, даже если поведение многих модулей было бы неправильным! Возможно, я что-то упускаю, и я надеюсь, что некоторые из вас, умные люди, могли бы объяснить мне, что это такое.