Как вы пишете модульные тесты?

14

Иногда я заканчиваю писать блок-тесты для кода, написанного другими разработчиками. Бывают случаи, когда я действительно не знаю, что пытается сделать разработчик (бизнес-часть), и я просто манипулирую тестовым примером, чтобы получить зеленую линию. Нормально ли это в отрасли?

Какая нормальная тенденция? Должны ли разработчики писать тестовые примеры для кода, который они написали сами?

Винот Кумар СМ
источник
2
"вмятина"? Что значит "вмятина"?
S.Lott

Ответы:

12

Попробуйте прочитать этот пост в блоге: « Написание отличных юнит-тестов: лучшие и худшие практики» .

Но в Интернете есть множество других.

В прямой ответ на ваши вопросы ...

  1. «Нормальная тенденция» - я думаю, это может отличаться от места к месту, что нормально для меня, может быть странным для других.
  2. Я бы сказал (по моему выбору), что разработчик, который пишет код, должен написать тест, в идеале используя такие методы, как TDD, где вы должны написать тест перед кодом. Но у других здесь могут быть разные методы и идеи!

И то, как вы описали написание тестов (в вашем вопросе), совершенно неверно !!


источник
9

Такой подход делает юнит-тест бесполезным.

Вам нужно, чтобы модульный тест не прошел, если какое-то реальное действие не работает как задумано. Если вы не делаете это таким образом, и, возможно, даже пишете тест перед тестированием кода, это все равно, что иметь неработающие дымовые сигнализации.


источник
8
Это не совсем так. Вернее, это так в идеальном мире, но, увы, часто мы далеки от этого. Подумайте о том , унаследованном код без тестов и без спецификаций, и без тех , кто мог бы надежно сказать вам , до мельчайших деталей, что делает определенный кусок кода именно должен делать (это есть реальность в значительной части существующих проектов). Даже в этом случае, возможно, все же стоит написать модульные тесты, чтобы заблокировать текущее состояние кода и убедиться, что вы ничего не нарушите с последующим рефакторингом, исправлением ошибок или расширениями.
Петер Тёрёк
2
Кроме того, я думаю, что вы имели в виду «написать тест после кода для тестирования», не так ли?
Петер Тёрёк
@ Петер, формулировка пошла не так - ты правильно понял. Но если вы решили написать тесты, они должны сделать что-то полезное. Просто слепо вызывающий код, говорящий, что это тест, на мой взгляд, не тестирование.
Шёрн, если вы имеете в виду, что в наших модульных тестах мы должны иметь значимые утверждения, чтобы убедиться, что протестированный код действительно выполняет то, что мы думаем , я полностью согласен.
Петер Тёрёк
3

Если вы не знаете, что делает функция, вы не можете написать для нее модульный тест. Насколько вы знаете, он даже не делает то, что должен. Вы должны выяснить, что он должен делать в первую очередь. ПОТОМ написать тест.

Эдвард Стрендж
источник
3

В реальном мире совершенно нормально писать модульные тесты для чужого кода. Конечно, первоначальный разработчик должен был сделать это уже, но часто вы получаете устаревший код, где это просто не было сделано. Между прочим, не имеет значения, был ли этот унаследованный код десятилетиями назад из далекой-далекой галактики, или один из ваших коллег проверил его на прошлой неделе, или вы написали его сегодня, унаследованный код - это код без тестов

Задайте себе вопрос: почему мы пишем модульные тесты? Going Green, очевидно, является лишь средством для достижения цели, конечная цель - доказать или опровергнуть утверждения о тестируемом коде.

Допустим, у вас есть метод, который вычисляет квадратный корень из числа с плавающей запятой. В Java интерфейс определил бы это как:

public double squareRoot(double number);

Неважно, написали ли вы реализацию или кто-то другой, вы хотите установить несколько свойств squareRoot:

  1. что он может возвращать простые корни, такие как sqrt (4.0)
  2. что он может найти реальный корень как sqrt (2.0) с разумной точностью
  3. что он находит, что sqrt (0.0) равен 0.0
  4. что он выдает исключение 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?
}

Со временем вы получаете тестовый набор, который документирует, как на самом деле ведет себя код, и становится своего рода закодированной спецификацией. Если вы когда-нибудь захотите изменить унаследованный код или заменить его чем-то другим, у вас есть тестовый комплект, чтобы убедиться, что новый код ведет себя так же, или что новый код ведет себя по-разному ожидаемым и контролируемым образом (например, что он на самом деле исправляет ошибку, которую вы ожидаете исправить). Этот жгут не должен быть полным в первый день, на самом деле иметь неполный жгут почти всегда лучше, чем вообще не иметь жгута. Наличие жгута означает, что вы можете писать свой клиентский код с большей легкостью, вы знаете, где ожидать, что что-то сломается, когда вы что-то измените, и где оно сломалось, когда они в конечном итоге это сделали.

Вы должны попытаться избавиться от мыслей о том, что вы должны писать модульные тесты только потому, что должны, как если бы вы заполняли обязательные поля в форме. И вам не следует писать модульные тесты, чтобы красная линия была зеленой. Модульные тесты - не ваши враги, юнит-тесты - ваши друзья.

Валленборн
источник
1

Когда я пишу тестовые случаи (для принтеров), я стараюсь думать о каждом маленьком компоненте .... и что я могу сделать, чтобы сломать его. Допустим, например, сканер, какие команды он использует (на языке задания принтера pjl), что я могу написать, чтобы проверить все функциональные возможности ... Хорошо, теперь, что я могу сделать, чтобы попытаться это сломать.

Я пытаюсь сделать это для каждого из основных компонентов, но когда речь идет о программном обеспечении и не столько аппаратном обеспечении, вы хотите посмотреть на каждый метод / функцию и проверить границы и тому подобное.


источник
1

Похоже, вы работаете с другими разработчиками (или поддерживаете код, написанный другими разработчиками), которые не проводят модульное тестирование. В этом случае, я думаю, вы определенно захотите узнать, что должен делать тестируемый объект или метод, а затем создать для него тест.

Это не будет TDD, потому что вы сначала не написали тест, но могли бы улучшить ситуацию. Возможно, вы также захотите создать копию тестируемых объектов с заглушками, чтобы можно было убедиться, что ваши тесты функционируют должным образом в случае сбоя кода.

vjones
источник