Как правильно настроить API с помощью TDD?

12

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

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

Когда вы улучшаете существующий код, TDD кажется действительно подходящим вариантом, но когда вы пишете все с нуля, API, для которого вы пишете тесты, выглядит немного «размытым», если вы не делаете большой дизайн заранее. Что вы делаете, когда у вас уже есть 30 тестов для метода, чья сигнатура (и для этой части, поведение) изменилась? Это много тестов, которые нужно изменить, как только они сложатся.

Витаутас Маконис
источник
3
30 тестов по одному методу? Похоже, этот метод слишком сложен, или вы пишете слишком много тестов.
Minthos
Ну, возможно, я немного преувеличил, чтобы выразить свою точку зрения. После проверки кода у меня обычно меньше 10 методов на тест, большинство из которых ниже 5. Но вся часть «возвращаясь и меняя их вручную» довольно разочаровывает.
Витаутас Маконис
6
@Minthos: Я могу вспомнить 6 тестов, которые могут привести к тому, что любой метод, использующий строку, будет часто давать сбой или работать плохо при первом наброске записи (ноль, пустой, слишком длинный, неправильно локализован, плохое масштабирование перфорации) , Аналогично для методов, принимающих коллекцию. Для нетривиального метода 30 звучит крупно, но не слишком нереально.
Стивен Эверс

Ответы:

13

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

Вы не можете вырастить архитектуру из юнит-тестов. Даже дядя Боб говорит это.

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

http://s3.amazonaws.com/hanselminutes/hanselminutes_0171.pdf , стр. 4

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

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

Роберт Харви
источник
Дело в том, что даже при «разумном планировании» вы ожидаете, что многое изменится. Я обычно оставляю около 80% моей первоначальной архитектуры нетронутыми с некоторыми изменениями между ними. Эти 20% меня беспокоят.
Витаутас Маконис
2
Я думаю, что это просто природа разработки программного обеспечения. Вы не можете ожидать, чтобы получить всю архитектуру прямо с первой попытки.
Роберт Харви
2
+1, и это не противоречит TDD. TDD начинается, когда вы начинаете писать код, именно тогда, когда заканчивается дизайн. TDD может помочь вам увидеть то, что вы упустили в своем проекте, что позволит вам реорганизовать проект и реализацию и продолжить.
Стивен Эверс
2
На самом деле, по словам Боба (и я с ним полностью согласен), написание кода тоже дизайн. Наличие архитектуры высокого уровня определенно необходимо, но дизайн не заканчивается, когда вы пишете свой код.
Майкл Браун
Действительно хороший ответ, который ударяет гвоздь по голове. Я вижу так много людей, как за, так и против TDD, которые, кажется, не воспринимают «не большой дизайн заранее», как «вообще не проектируют, просто пишут код», когда на самом деле это удар по безумным стадиям проектирования водопадов прошлого. Дизайн всегда является хорошим вложением времени и имеет решающее значение для успеха любого нетривиального проекта.
Сара
3

Если вы делаете TDD. Вы не можете изменить сигнатуру и поведение, не пройдя тесты. Таким образом, 30 неудачных тестов были либо удалены в процессе, либо изменены / реорганизованы вместе с кодом. Или они теперь устарели, безопасны для удаления.

Вы не можете игнорировать 30 раз красный в вашем цикле красный-зеленый-рефактор?

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

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

Иногда вы можете создать несколько достойных интеграционных тестов, сохранить их и удалить остальные. Это в какой-то степени зависит от того, работаете ли вы наизнанку или сверху вниз, и насколько большими шагами вы занимаетесь.

Joppe
источник
1

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

Попытайтесь спроектировать (или «смоделировать») свою систему достаточно абстрактным («общим», «расплывчатым») способом. Например, если вам нужно смоделировать автомобиль, просто используйте класс автомобиля с некоторыми неопределенными методами и полями, такими как startEngine () и int seat. То есть: опишите, что вы хотите представить публике , а не то, как вы хотите это реализовать. Попытайтесь раскрыть только основные функции (чтение, запись, запуск, остановка и т. Д.) И оставьте детальный код клиента на нем (prepareMyScene (), killTheEnemy () и т. Д.).

Запишите свои тесты, предполагая, что этот простой общедоступный интерфейс.

Измените внутреннее поведение ваших классов и методов, когда вам это нужно.

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

Нет ничего необычного в том, чтобы изменить API. Большинство систем в их версии 1.0 явно предупреждают программистов / пользователей о возможных изменениях в их API. Несмотря на это, непрерывный неконтролируемый поток изменений API является явным признаком плохого (или полностью отсутствующего) дизайна.

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

AlexBottoni
источник
0

Я смотрю на это с точки зрения пользователя. Например, если ваши API-интерфейсы позволяют мне создавать объект Person с именем и возрастом, лучше использовать конструктор Person (строковое имя, int age) и методы доступа для имени и возраста. Создать новые тестовые примеры для новых людей с именем и возрастом просто.

Дуг

SnoopDougieDoug
источник