Когда я делаю TDD, зачем мне делать "достаточно", чтобы пройти тест?

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

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

Ответы

Ответ 1

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

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

Ответ 2

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

Ответ 3

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

(*) Я обычно переводил это как "Ты слишком глуп, чтобы знать, что понадобится в будущем", но это другая тема......

Ответ 4

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

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

Ответ 5

Dan North предположил, что нет такой вещи, как тест-ориентированный дизайн, потому что дизайн на самом деле не изгоняется тестированием - что эти модульные тесты становятся только тестами, когда функциональность реализована, но на этапе проектирования вы действительно проектирование на примере.

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

Некоторые другие ответы говорят о том, что это основано на YAGNI. Это отчасти верно.

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

Если вы пишете 10 тестов для рассмотрения случаев, когда param1 имеет значение null, param2 равно null, строка1 пуста, int1 отрицательна, а текущий день недели - выходные, а затем перейдите к реализации, вам нужно жонглировать сложностью сразу. Это открывает пространство для введения ошибок, и становится очень сложно разобраться, почему тесты терпят неудачу.

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

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

Ответ 6

Это отличный вопрос. Вам нужно найти баланс между написанием всех тестов во вселенной возможных тестов и наиболее вероятными сценариями пользователя. Один тест - IMHO, недостаточно, и мне обычно нравится писать 3 или 4 теста, которые представляют собой наиболее распространенное использование этой функции. Я также хотел бы написать лучший тестовый пример и худший случай.

Написание многих тестов поможет вам предвидеть и понять потенциальное использование вашей функции.

Ответ 7

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

Ответ 8

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

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

Как и в случае ссылки, которую вы указали в своем вопросе, если бы они сначала написали все тесты, я уверен, что они не закончили бы простым оператором if/else, но, вероятно, довольно сложной рекурсивной частью код.

Ответ 9

Причина принципа проста. Как практично это придерживаться - это отдельный вопрос.

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

Если вы напишете следующий тест, чтобы "догнать" производственный код, вы только что написали тест, который вы не видели. Тест можно назвать "TestNextFeature", но он может также return true для всех доказательств, которые у вас есть.

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

Ответ 10

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

Ответ 11

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

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

Ответ 12

Много хороших ответов выше - YAGNI - первый ответ, который подскакивает.

Другая важная вещь в руководстве "просто получить тест прохождения" - это то, что TDD на самом деле является трехэтапным процессом:

Красный > Зеленый > Рефакторинг

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

Трудно попасть в эту привычку, но придерживайтесь ее, поскольку это странно удовлетворительный способ работы после того, как вы войдете в цикл.