В реальном мире совершенно нормально писать модульные тесты для чужого кода. Конечно, первоначальный разработчик должен был сделать это уже, но часто вы получаете устаревший код, где это просто не было сделано. Между прочим, не имеет значения, был ли этот унаследованный код десятилетиями назад из далекой-далекой галактики, или один из ваших коллег проверил его на прошлой неделе, или вы написали его сегодня, унаследованный код - это код без тестов
Задайте себе вопрос: почему мы пишем модульные тесты? Going Green, очевидно, является лишь средством для достижения цели, конечная цель - доказать или опровергнуть утверждения о тестируемом коде.
Допустим, у вас есть метод, который вычисляет квадратный корень из числа с плавающей запятой. В Java интерфейс определил бы это как:
public double squareRoot(double number);
Неважно, написали ли вы реализацию или кто-то другой, вы хотите установить несколько свойств squareRoot:
- что он может возвращать простые корни, такие как sqrt (4.0)
- что он может найти реальный корень как sqrt (2.0) с разумной точностью
- что он находит, что sqrt (0.0) равен 0.0
- что он выдает исключение IllegalArgumentException при подаче отрицательного числа, т. е. на sqrt (-1.0)
Итак, вы начинаете писать их как отдельные тесты:
@Test
public void canFindSimpleRoot() {
assertEquals(2, squareRoot(4), epsilon);
}
К сожалению, этот тест уже не проходит:
java.lang.AssertionError: Use assertEquals(expected, actual, delta) to compare floating-point numbers
Вы забыли об арифметике с плавающей точкой. Хорошо, вы вводите double epsilon=0.01
и идете:
@Test
public void canFindSimpleRootToEpsilonPrecision() {
assertEquals(2, squareRoot(4), epsilon);
}
и добавьте другие тесты: наконец
@Test
@ExpectedException(IllegalArgumentException.class)
public void throwsExceptionOnNegativeInput() {
assertEquals(-1, squareRoot(-1), epsilon);
}
и ой, опять
java.lang.AssertionError: expected:<-1.0> but was:<NaN>
Вы должны были проверить:
@Test
public void returnsNaNOnNegativeInput() {
assertEquals(Double.NaN, squareRoot(-1), epsilon);
}
Что мы здесь сделали? Мы начали с нескольких предположений о том, как должен вести себя метод, и обнаружили, что не все из них были правдой. Затем мы сделали тестовый набор Green, чтобы записать доказательство того, что метод ведет себя в соответствии с нашими исправленными предположениями. Теперь клиенты этого кода могут полагаться на это поведение. Если бы кто-то обменялся фактической реализацией squareRoot с чем-то другим, например с тем, что, например, действительно выдало исключение вместо возврата NaN, наши тесты сразу бы это уловили.
Этот пример тривиален, но часто вы наследуете большие куски кода, где неясно, что он на самом деле делает. В этом случае нормально проложить тестовый ремень вокруг кода. Начните с нескольких основных предположений о том, как должен вести себя код, напишите для них модульные тесты, протестируйте. Если Грин, хорошо, напишите больше тестов. Если красный, то теперь у вас есть несостоятельное утверждение, что вы можете противостоять спецификации. Может быть, есть ошибка в унаследованном коде. Может быть, спецификация неясна в отношении этого конкретного входа. Может быть, у вас нет спецификации. В этом случае перепишите тест так, чтобы он документировал неожиданное поведение:
@Test
public void throwsNoExceptionOnNegativeInput() {
assertNotNull(squareRoot(-1)); // Shouldn't this fail?
}
Со временем вы получаете тестовый набор, который документирует, как на самом деле ведет себя код, и становится своего рода закодированной спецификацией. Если вы когда-нибудь захотите изменить унаследованный код или заменить его чем-то другим, у вас есть тестовый комплект, чтобы убедиться, что новый код ведет себя так же, или что новый код ведет себя по-разному ожидаемым и контролируемым образом (например, что он на самом деле исправляет ошибку, которую вы ожидаете исправить). Этот жгут не должен быть полным в первый день, на самом деле иметь неполный жгут почти всегда лучше, чем вообще не иметь жгута. Наличие жгута означает, что вы можете писать свой клиентский код с большей легкостью, вы знаете, где ожидать, что что-то сломается, когда вы что-то измените, и где оно сломалось, когда они в конечном итоге это сделали.
Вы должны попытаться избавиться от мыслей о том, что вы должны писать модульные тесты только потому, что должны, как если бы вы заполняли обязательные поля в форме. И вам не следует писать модульные тесты, чтобы красная линия была зеленой. Модульные тесты - не ваши враги, юнит-тесты - ваши друзья.