Как сделать Test Driven Development

15

У меня более 2 лет опыта в разработке приложений. В эти два года мой подход к развитию был следующим

  1. Проанализировать требования
  2. Компонент Identity Core / Объекты, Обязательные функции, Поведение, Процесс и их ограничения
  3. Создавать классы, отношения между ними, ограничения на поведение объектов и состояния
  4. Создание функций, обработка с поведенческими ограничениями в соответствии с требованиями
  5. Тестировать приложение вручную
  6. Если требования изменяются, измените компонент / функции, затем протестируйте приложение вручную

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

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

  1. Я делаю это правильно? Если не то, что именно я должен изменить
  2. Есть ли способ определить, достаточно ли написанного вами теста?
  3. Является ли хорошей практикой написание теста для очень простой функциональности, которая может быть эквивалентна 1 + 1 = 2, или это просто переигровка?
  4. Это хорошо, чтобы изменить функциональность и, соответственно, проверить, если требования изменяются?
Yogesh
источник
2
«Я идентифицирую компоненты и просто пишу тесты для них, прежде чем на самом деле пишу компоненты». Я считаю это правильным: сначала вы определяете грубую архитектуру вашей системы, а затем начинаете кодировать. Во время кодирования (TDD) вы прорабатываете детали отдельных компонентов и, возможно, обнаруживаете проблемы с вашей архитектурой, которые вы можете исправить на своем пути. Но я нахожу это нормально, что вы не начинаете кодировать без какого-либо предварительного анализа.
Джорджио
Вы также можете изучить автоматическое модульное / интеграционное тестирование без TDD. Их часто путают, но это не одно и то же.
Андрес Ф.

Ответы:

19

Я делаю это правильно? Если не то, что именно я должен изменить

Это трудно сказать, из этого краткого описания, но я подозреваю , что, нет, вы не делаете это правильно. Примечание: я не говорю, что то, что вы делаете, не работает или в некотором смысле плохо, но вы не делаете TDD. Середина «D» означает «управляемый», тесты управляют всем, процессом разработки, кодом, дизайном, архитектурой, всем .

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

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

Отбросить эту привычку сложно . Действительно, очень сложно. Это кажется особенно трудным для опытных программистов.

Кейт Брейтуэйт создал упражнение, которое он называет TDD, как будто вы это имели в виду . Он состоит из набора правил (основанных на Трех правилах TDD дяди Боба Мартина , но гораздо более строгих), которым вы должны строго следовать и которые направлены на то, чтобы направить вас к более строгому применению TDD. Лучше всего работает с парным программированием (чтобы ваша пара могла убедиться, что вы не нарушаете правила) и с инструктором.

Правила таковы:

  1. Напишите ровно один новый тест, наименьший из возможных тестов, который, кажется, указывает на решение.
  2. Увидеть это не удастся; ошибки компиляции считаются ошибками
  3. Выполните тест из (1), написав наименьший код реализации, который вы можете использовать в методе test .
  4. Рефакторинг для устранения дублирования, а также по мере необходимости для улучшения дизайна. Будьте строги в использовании этих ходов:
    1. вам нужен новый метод - дождитесь времени рефакторинга, затем ... создайте новые (не тестовые) методы, выполнив один из них, и никак иначе:
      • предпочтительнее: сделать метод извлечения для кода реализации, созданного в соответствии с (3), чтобы создать новый метод в тестовом классе, или
      • если вы должны: переместить код реализации согласно (3) в существующий метод реализации
    2. Вы хотите новый класс - подождите до времени рефакторинга, затем ... создайте не тестовые классы, чтобы предоставить место назначения для метода Move и ни по какой другой причине
    3. заполнить классы реализации методами, выполнив Move Method, и никак иначе

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

Когда группа людей реализует что-то вроде игры в крестики-нолики с использованием псевдо-TDD, они, как правило, получают очень похожие конструкции, включающие в себя некоторый Boardкласс с массивом 3 × 3 Integer. И, по крайней мере, часть программистов фактически написала этот класс без тестов для него, потому что они «знают, что им это понадобится» или «нужно что-то, чтобы написать свои тесты». Однако, когда вы заставляете ту же группу применять TDD, как если бы вы это имели в виду, они часто оказываются в широком разнообразии совершенно разных конструкций, часто не используя ничего, даже отдаленно похожего на a Board.

Есть ли способ определить, достаточно ли написанного вами теста?

Когда они охватывают все бизнес-требования. Тесты являются кодировкой системных требований.

Является ли хорошей практикой написание теста для очень простой функциональности, которая может быть эквивалентна 1 + 1 = 2, или это просто переигровка?

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

Это хорошо, чтобы изменить функциональность и, соответственно, проверить, если требования изменяются?

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

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

Йорг Миттаг
источник
1
Действительно, очень четкий ответ! С практической точки зрения гибкая и мощная среда тестирования очень приятна при практике TDD. Независимо от TDD, возможность автоматического запуска тестов неоценима для отладки приложения. Чтобы начать работу с TDD, неинтерактивные программы (в стиле UNIX), вероятно, являются самыми простыми, потому что пример использования может быть протестирован путем сравнения состояния выхода и результата программы с ожидаемым. Конкретный пример такого подхода можно найти в моей библиотеке бензинов для OCaml.
Михаэль Ле Барбье Грюневальд
4
Вы говорите: «Когда вы заставляете ту же группу применять TDD, как если бы вы это имели в виду, они часто заканчивают большим разнообразием очень разных конструкций, часто не используя ничего, даже отдаленно похожего на плату», как если бы это было хорошо , Мне совершенно не ясно, что это хорошо, и может даже быть плохо с точки зрения обслуживания, поскольку кажется, что реализация была бы очень нелогичной для кого-то нового. Не могли бы вы объяснить, почему это разнообразие реализации хорошо или, по крайней мере, неплохо?
Джим Клэй
3
+1 Ответ хорош в том, что он правильно описывает TDD. Тем не менее, это также показывает, почему TDD является ошибочной методологией: необходима тщательная проработка и четкое проектирование, особенно когда сталкиваются с алгоритмическими проблемами Выполнение TDD «вслепую» (как предписывает TDD), притворяясь, что у него нет знания предметной области, приводит к ненужным трудностям и тупикам. См. Печально известный разгром решения судоку (короткая версия: TDD не может превзойти знание предметной области).
Андрес Ф.
1
@AndresF .: На самом деле, сообщение в блоге, на которое вы ссылались, похоже, перекликается с тем, что Кит сделал, когда делал TDD, как будто вы это имели в виду: когда вы выполняете «псевдо-TDD» для Tic-Tac-Toe, они начинают с создания Boardкласса с Массив 3х3 intс (или что-то в этом роде). Принимая во внимание, что если вы заставляете их выполнять TDDAIYMI, они часто заканчивают тем, что создают мини-DSL для сбора знаний о предметной области. Это просто анекдотично, конечно. Статистически и научно обоснованное исследование было бы неплохо, но, как это часто бывает с подобными исследованиями, они либо слишком малы, либо слишком дороги.
Jörg W Mittag
@ JörgWMittag Поправьте меня, если я вас неправильно понял, но вы говорите, что Рон Джеффрис делал «псевдо-TDD»? Разве это не форма ошибки "нет настоящего шотландца"? (Я согласен с вами в отношении необходимости дополнительных научных исследований; блог, на который я ссылаюсь, является просто ярким анекдотом о впечатляющем провале конкретного случая использования TDD. К сожалению, кажется, что евангелисты TDD слишком громки для остальных из нас провести реальный анализ этой методологии и ее предполагаемых преимуществ).
Андрес Ф.
5

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

Итак, как вы можете внести больше «TDD» в ваш процесс разработки? Во-первых, я предполагаю, что ваш реальный процесс разработки не всегда является «нисходящим», как вы описали его выше. После шага 2 вы, вероятно, идентифицировали некоторые компоненты, которые не зависят от других компонентов. Иногда вы решаете сначала реализовать эти компоненты. Детали общедоступного API этих компонентов, вероятно, не соответствуют только вашим требованиям, детали также соответствуют вашим проектным решениям. Это тот момент, когда вы можете начать с TDD: представьте, как вы собираетесь использовать компонент и как вы на самом деле будете использовать API. И когда вы начинаете кодировать такое использование API в форме теста, вы только начинаете с TDD.

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

Док Браун
источник
3

Я делаю это правильно? Если не то, что именно я должен изменить

У тебя все хорошо.

Есть ли способ определить, достаточно ли написанного вами теста?

Да, используйте инструмент тестирования / покрытия кода . Мартин Фаулер предлагает несколько полезных советов по тестированию.

Является ли хорошей практикой написание теста для очень простой функциональности, которая может быть эквивалентна 1 + 1 = 2, или это просто переигровка?

В целом, любая функция, метод, компонент и т. Д., Которые, как вы ожидаете, дадут какой-либо результат при наличии некоторых входных данных, являются хорошим кандидатом для модульного теста. Однако, как и в случае большинства вещей (инженерной) жизни, вам необходимо учитывать свои компромиссы: компенсируется ли усилие путем написания модульного теста более стабильная база кода в долгосрочной перспективе? В общем, сначала решите написать тестовый код для критически важных функций. Позже, если вы обнаружите, что есть ошибки, связанные с некоторой непроверенной частью кода, добавьте больше тестов.

Это хорошо, чтобы изменить функциональность и, соответственно, проверить, если требования изменяются?

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

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

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

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

Еще одна хорошая вещь - смотреть онлайн-видео, где программное обеспечение разрабатывается с использованием TDD с самого начала. Хорошим примером, который я когда-то использовал, чтобы представить себя в TDD, был « Let's Play TDD » Джеймса Шора. Посмотрите, это покажет, как работает новый дизайн, какие вопросы вы должны задать себе во время написания тестов и как создаются, реорганизуются и повторяются новые классы и методы.

Есть ли способ определить, достаточно ли написанного вами теста?

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

Является ли хорошей практикой написание теста для очень простой функциональности, которая может быть эквивалентна 1 + 1 = 2, или это просто переигровка?

Очевидно, это зависит от вашего мнения. Я предпочитаю не писать тесты для проверки параметров на нуль, если метод не является частью общедоступного API, но в противном случае, почему бы вам не подтвердить, что метод Add (a, b) действительно возвращает a + b?

Это хорошо, чтобы изменить функциональность и, соответственно, проверить, если требования изменяются?

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

Павел
источник