TDD - Когда можно написать тест без сбоев? - PullRequest
8 голосов
/ 11 декабря 2008

Из того, что я понимаю, в TDD сначала нужно написать провальный тест, затем написать код для его прохождения, затем выполнить рефакторинг. Но что, если ваш код уже учитывает ситуацию, которую вы хотите проверить?

Например, допустим, я использую алгоритм сортировки TDD (это только гипотетически). Я мог бы написать модульные тесты для пары случаев:

вход = 1, 2, 3
выход = 1, 2, 3

вход = 4, 1, 3, 2
выход = 1, 2, 3, 4
так далее...

Чтобы пройти тесты, я использую быструю и грязную пузырьковую сортировку. Затем я делаю рефакторинг и заменяю его более эффективным алгоритмом сортировки слиянием. Позже я понимаю, что нам нужно, чтобы он был стабильным, поэтому я тоже пишу тест для этого. Конечно, тест никогда не будет неудачным, потому что сортировка слиянием - это стабильный алгоритм сортировки! Несмотря на это, мне все еще нужен этот тест, если кто-то снова проведет его рефакторинг, чтобы использовать другой, возможно, нестабильный алгоритм сортировки.

Разве это нарушает мантру TDD всегда писать неудачные тесты? Я сомневаюсь, что кто-нибудь порекомендует мне потратить время на реализацию нестабильного алгоритма сортировки, просто чтобы проверить контрольный пример, а затем переопределить сортировку слиянием. Как часто вы сталкиваетесь с подобной ситуацией и чем занимаетесь?

Ответы [ 12 ]

16 голосов
/ 12 декабря 2008

Есть две причины для написания сначала неудачных тестов, а затем их запуска;

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

Вторая и самая важная причина - не дать вам писать слишком много тестов. Тесты отражают ваш дизайн, а ваш дизайн отражает ваши требования и изменения требований. Вам не нужно переписывать много тестов, когда это происходит. Хорошее эмпирическое правило заключается в том, чтобы каждый тест проваливался только по одной причине, и по этой причине должен быть только один тест. TDD пытается обеспечить это путем повторения стандартного цикла «красный-зеленый-рефакторинг» для каждого теста, каждой функции и каждого изменения в вашей кодовой базе.

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

5 голосов
/ 15 декабря 2008

Я сомневаюсь, что кто-нибудь порекомендует мне тратить время для реализации нестабильной алгоритм сортировки только для проверки тестовый случай, затем переопределить Сортировка слиянием. Как часто ты приходишь через аналогичную ситуацию и что делать Вы делаете?

Позвольте мне быть тем, кто рекомендует это тогда. :)

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

Продолжая гипотетический пример ...

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

Если, с другой стороны, вы «тестируете тест», ломая функцию и наблюдая за ее провалом, вы снижаете риск теста.

И, подстановочный знак, вы можете получить некоторые важные знания. Например, пытаясь закодировать «плохую» сортировку и заставить тест провалиться, вы можете глубже подумать об ограничениях сравнения для сортируемого вами типа и обнаружить, что вы использовали «x == y» в качестве Предикат класса эквивалентности для сортировки, но на самом деле "! (x

Так что я говорю «ошибка» на стороне «потрать дополнительное время, чтобы заставить его выйти из строя, даже если это означает преднамеренное разрушение системы, чтобы на мгновение получить красную точку на экране», потому что в то время как каждый из этих маленьких » отводы »требуют некоторого времени, каждый раз вы экономите огромный пакет (например, упс, ошибка в тесте означает, что я никогда не тестировал самое важное свойство моей системы, или упс , весь наш дизайн для предикатов неравенства испорчен). Это все равно, что играть в лотерею, за исключением того, что в конечном итоге шансы на вашу пользу; каждую неделю вы тратите 5 долларов на билеты и обычно проигрываете, но раз в три месяца вы выигрываете джекпот в 1000 долларов.

4 голосов
/ 12 декабря 2008

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

Например, я однажды видел, как в нашей базе кода C ++ кто-то проверял в тесте:

assertTrue(x = 1);

Очевидно, что они не программировали, так что сначала тест не удался, поскольку это вообще ничего не проверяет.

3 голосов
/ 15 декабря 2008

Простое правило TDD: вы пишете тесты, которые могут дать сбой.

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

т.е. Новая функция «Список X должен содержать до 10 элементов» вместо «до 5 элементов» потребует нового теста. Тест пройдет, когда фактическая реализация List X разрешит 2 ^ 32 элемента, но вы точно не будете знать, пока не запустите новый тест.

2 голосов
/ 12 декабря 2008

Существуют причины для написания тестов в TDD, выходящих за рамки просто "разработки в первую очередь".

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

Позже, если вы решите воспользоваться этим дополнительным поведением, вам нужно написать тест, чтобы все, кто придет и рефакторинг, не сломали это дополнительное поведение, на которое вы теперь полагаетесь.

2 голосов
/ 12 декабря 2008

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

Однако вы можете захотеть сохранить некоторый код с помощью методов TDD; в этом случае сначала необходимо написать тесты как тесты для определения характеристик (которые по определению не будут никогда не пройдены, поскольку они выполняются для рабочих интерфейсов), затем рефакторинг.

2 голосов
/ 12 декабря 2008

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

1 голос
/ 26 января 2009

Как уже говорили другие, мантра TDD - «нет нового кода без провального модульного теста». Я никогда не слышал, чтобы практики TDD говорили: «Нет новых тестов без пропущенного кода». Новые тесты всегда приветствуются, даже если они «случайно» проходят «случайно». Нет необходимости менять код, чтобы он сломался, а затем снова изменить его, чтобы пройти тест.

1 голос
/ 11 января 2009

В качестве примера вы привели IMO один из подходящих моментов для написания теста, который проходит первую попытку. Целью надлежащих тестов является документирование ожидаемого поведения системы. Можно написать тест, не меняя реализацию, чтобы уточнить дальнейшее поведение.

приписка

Насколько я понимаю, вот причина, по которой вы хотите, чтобы тест не прошел, прежде чем он пройдет:

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

1 голос
/ 12 декабря 2008

э-э ... я читаю цикл TDD как

  • сначала напишите тест, который не пройдёт, потому что код просто заглушка
  • напишите код, чтобы тест прошел
  • рефакторинг по необходимости

Нет необходимости продолжать писать тесты, которые не пройдены, первый - неудачен, потому что нет кода, который мог бы что-то делать. смысл первого теста - выбрать интерфейс!

РЕДАКТИРОВАТЬ: Кажется, есть некоторое недопонимание мантры "красный-зеленый-рефактор". Согласно статье TDD Википедии

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

Другими словами, обязательный тест предназначен для новой функции , а не для дополнительного покрытия!

РЕДАКТИРОВАТЬ: если вы не говорите о написании регрессионного теста, чтобы воспроизвести ошибку, конечно!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...