Как люди, занимающиеся TDD, справляются с потерей работы при проведении крупного рефакторинга

37

Некоторое время я пытался научиться писать модульные тесты для своего кода.

Первоначально я начал делать настоящий TDD, где я не буду писать никакого кода, пока сначала не напишу провальный тест.

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

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

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

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

GazTheDestroyer
источник
6
Совершенство достигается не тогда, когда нечего добавить, а когда нечего отнять. - Антуан де Сент-Экзюпери
mouviciel
12
Как это возможно, что все ваши тесты не соответствуют действительности? Пожалуйста, объясните, как изменение в реализации делает недействительным каждый написанный вами тест.
S.Lott
6
@ S.Lott: тесты не были неправильными, они просто больше не были актуальны. Скажем, вы решаете часть проблемы, используя простые числа, поэтому вы пишете класс для генерации простых чисел и пишете тесты для этого класса, чтобы убедиться, что он работает. Теперь вы найдете другое решение вашей проблемы, которое никак не связано с простыми числами. Этот класс и его тесты теперь излишни. Это была моя ситуация только с 10-х классов, а не только один.
GazTheDestroyer
5
@GazTheDestroyer мне кажется, что различие между тестовым кодом и функциональным кодом является ошибкой - все это является частью одного и того же процесса разработки. Справедливо отметить, что TDD имеет накладные расходы, которые обычно восстанавливаются далее в процессе разработки, и кажется, что эти накладные расходы ничего не выиграли в этом случае. Но в равной степени, насколько тесты помогли вам понять недостатки архитектуры? Также важно отметить, что вам разрешено (нет, рекомендуется ) обрезать свои тесты с течением времени ... хотя это, вероятно, немного экстремально (-:
Murph
10
Я собираюсь быть семантически педантичным и согласен с @ S.Lott здесь; то, что вы сделали, это не рефакторинг, если это приведет к выбрасыванию множества классов и тестов для них. Это ре-архитектура . Рефакторинг, особенно в смысле TDD, означает, что тесты были зелеными, вы изменили некоторый внутренний код, повторно запустили тесты, и они остались зелеными.
Эрик Кинг,

Ответы:

33

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

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

Другая проблема - ИМХО с вашим восприятием. Мы все совершаем ошибки, и в ретроспективе так легко увидеть, что мы должны были сделать по-другому. Это просто способ, которым мы учимся. Запишите свои инвестиции в модульные тесты, как цену изучения того, что прототипирование может быть важным, и преодолейте это. Просто старайся не повторять одну и ту же ошибку дважды :-)

Петер Тёрёк
источник
2
Я знал, что это будет трудная проблема, и мой код будет несколько исследовательским, но я был в восторге от моих недавних успехов в TDD, поэтому я продолжал писать тесты, как это было, потому что это то, что все литература TDD подчеркивает так много. Так что да, теперь я знаю, что правила могут быть нарушены (о чем и был мой вопрос на самом деле), я, вероятно, объясню это на опыте.
GazTheDestroyer
3
«Я продолжал писать тесты так же, как и раньше, потому что именно это подчеркивает вся литература по TDD». Вы, вероятно, должны обновить вопрос с источником вашей идеи, что все тесты должны быть написаны перед любым кодом.
S.Lott
1
У меня нет такой идеи, и я не уверен, как вы поняли это из комментария.
GazTheDestroyer
1
Я собирался написать ответ, но проголосовал вместо тебя. Да, миллион раз да: если вы еще не знаете, как выглядит ваша архитектура, сначала напишите одноразовый прототип и не беспокойтесь о написании модульных тестов во время прототипирования.
Роберт Харви
1
@WarrenP, конечно, есть люди, которые думают, что TDD - это Единый Истинный Путь (все, что угодно, может быть превращено в религию, если ты будешь достаточно стараться ;-). Я предпочитаю быть прагматичным, хотя. Для меня TDD - это один из инструментов в моем наборе инструментов, и я использую его только тогда, когда он помогает, а не мешает решению проблем.
Петер Тёрёк
8

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

Килиан Фот
источник
3
Мои методы не были большими, они просто стали неактуальными, учитывая новую архитектуру, которая не имела никакого сходства со старой архитектурой. Отчасти потому, что новая архитектура была намного проще.
GazTheDestroyer
Хорошо, если на самом деле нет ничего повторного использования, вы можете только сократить свои потери и двигаться дальше. Но обещание TDD состоит в том, что он позволяет быстрее достигать тех же целей, даже если вы пишете тестовый код в дополнение к коду приложения. Если это правда, и я твердо уверен, что это так, то, по крайней мере, вы достигли той точки, когда вы поняли, как сделать архитектуру за «пару недель», а не вдвое больше.
Килиан Фот
1
@Kilian, «обещание TDD состоит в том, что оно позволяет вам быстрее достичь тех же целей» - на какие цели вы ссылаетесь? Совершенно очевидно, что написание модульных тестов вместе с самим рабочим кодом изначально делает вас медленнее , чем просто создание кода. Я бы сказал, что TDD окупится только в долгосрочной перспективе благодаря улучшению качества и снижению затрат на техническое обслуживание.
Петер Тёрёк
@ PéterTörök - Есть люди, которые настаивают на том, что TDD никогда не платит, потому что он окупается к тому времени, как вы написали код. Это, конечно, не так для меня, но Киллиан, кажется, верит в это сам.
PSR
Ну ... если вы не верите этому, на самом деле, если вы не верите, что TDD имеет существенную отдачу, а не стоимость, тогда нет никакого смысла делать это вообще, не так ли? Не только в конкретной ситуации, описанной Газом, но и вообще . Боюсь, что теперь я полностью
отклонил
6

Брукс сказал: «Планируй выбросить один, все равно будешь». Мне кажется, что вы делаете именно это. Тем не менее, вы должны написать свои модульные тесты, чтобы проверить блок кода, а не большой объем кода. Это более функциональные тесты, и, следовательно, они должны проходить через любую внутреннюю реализацию.

Например, если я хочу написать решатель уравнений в частных производных (PDE), я бы написал несколько тестов, пытаясь решить задачи, которые я могу решить математически. Это мои первые «модульные» тесты - читай: функциональные тесты выполняются как часть фреймворка xUnit. Они не изменятся в зависимости от того, какой алгоритм я использую для решения PDE. Все, что меня волнует, это результат. Второе модульное тестирование будет сосредоточено на функциях, используемых для кодирования алгоритма, и, следовательно, будет зависеть от алгоритма - скажем, Рунге-Кутта. Если бы я узнал, что Рунге-Кутта не подходит, то у меня все равно были бы те тесты высшего уровня (включая те, которые показали, что Рунге-Кутта не подходит). Таким образом, вторая итерация будет по-прежнему иметь те же тесты, что и первая.

Ваша проблема может быть в дизайне, а не в коде. Но без подробностей сложно сказать.

Сардатрион - Восстановить Монику
источник
Это только периферийное устройство, но что такое PDE?
CVn
1
@ MichaelKjörling Я полагаю, это дифференциальное уравнение с частными производными
foraidt
2
Разве Брукс не отказывался от этого утверждения в своем втором издании?
Симон
Как вы думаете, у вас все еще будут тесты, которые показывают, что Рунге-Кутта не подходит? Как выглядят эти тесты? Вы имеете в виду, что вы сохранили написанный вами алгоритм Рунге-Кутты, прежде чем обнаружили, что он не подходит, и выполнение сквозных тестов с RK в миксе не удастся?
moteutsch
5

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

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

BenR
источник
1
Я не думаю, что мог бы объяснить себя очень хорошо. Я пишу тесты итеративно. Вот так я и получил несколько сотен тестов для кода, который внезапно стал избыточным.
GazTheDestroyer
1
Как и выше - я думаю, что это следует понимать как «тесты и код», а не «тесты для кода»
Murph
1
+1: «Не пытайтесь писать все тесты за один раз»,
С.Лотт
4

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

То, чему я научился, сравнивая реальные проекты TDD, с которыми я работал (не так много на самом деле, всего 3, охватывающих 2 года работы) с тем, что я усвоил теоретически, это то, что Automated Testing! = Unit Testing (конечно, не будучи взаимно эксклюзивные).

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

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

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

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

GFK
источник
3
хорошо сказано - это TDD, а не UTDD
Стивен А. Лоу
Отличный ответ. Из моего опыта работы с TDD важно, чтобы письменные тесты были сосредоточены на функциональном поведении программного обеспечения, а не на модульном тестировании. Сложнее думать о поведении, которое вам нужно от класса, но это приводит к чистым интерфейсам и потенциально упрощает итоговую реализацию (вы не добавляете функциональность, которая вам на самом деле не нужна).
JohnTESlade
4
Как люди, которые практикуют TDD, правильно справляются с такими ситуациями?
  1. рассматривая, когда прототип против против, когда кодировать
  2. понимая, что модульное тестирование не совпадает с TDD
  3. по написанию тестов TDD для проверки функции / истории, а не функционального блока

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

  • модульное тестирование связано с проверкой каждого отдельного модуля и функции в реализации ; в UT вы увидите акцент на таких вещах, как показатели покрытия кода и тесты, которые выполняются очень быстро
  • разработка через тестирование касается проверки каждой функции / истории в требованиях ; в TDD вы будете делать упор на такие вещи, как написание теста вначале, обеспечение того, чтобы написанный код не выходил за рамки предполагаемого объема, и рефакторинг для качества

В итоге: модульное тестирование ориентировано на реализацию, TDD - на требования. Они не одно и то же.

Стивен А. Лоу
источник
«TDD сфокусирован на требованиях». Я полностью с этим не согласен. Тесты, которые вы пишете в TDD, являются модульными тестами. Они делают проверять каждую функцию / метод. TDD делает есть акцент на покрытии кода и делает заботу о тестах , которые выполняются быстро (и они лучше сделать, так как вы запускать тесты каждые 30 секунд). Может быть, вы думали ATDD или BDD?
guillaume31
1
@ ian31: прекрасный пример объединения UT и TDD. Должен не согласиться и направить вас к некоторому исходному материалу en.wikipedia.org/wiki/Test-driven_development - целью тестов является определение требований к коду . BDD это здорово. Никогда не слышал об ATDD, но с первого взгляда все выглядит так, как я применяю масштабирование TDD .
Стивен А. Лоу
Вы можете прекрасно использовать TDD для разработки технического кода, который не имеет прямого отношения к требованию или пользовательской истории. Вы найдете бесчисленные примеры этого в Интернете, в книгах, конференциях, в том числе от людей, которые инициировали TDD и популяризировали его. TDD - это дисциплина, метод написания кода, он не перестанет быть TDD в зависимости от контекста, в котором вы его используете.
guillaume31
Кроме того, из статьи в Википедии, которую вы упомянули: «Передовые методы разработки на основе тестов могут привести к ATDD, где критерии, заданные заказчиком, автоматически превращаются в приемочные тесты, которые затем приводят в действие процесс традиционной разработки на основе модульных тестов (UTDD). [ ...] С ATDD у команды разработчиков теперь есть конкретная цель - приемочные тесты, которые постоянно фокусируют их на том, что клиент действительно хочет от этой истории пользователя ». Что, по-видимому, подразумевает, что ATDD в основном сосредоточен на требованиях, а не на TDD (или UTDD, как они выразились).
guillaume31
@ ian31: вопрос ОП о «выбрасывании нескольких сотен модульных тестов» указывает на путаницу в масштабе. Вы можете использовать TDD, чтобы построить сарай, если хотите. : D
Стивен А. Лоу
3

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

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

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

Dibbeke
источник
2

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

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

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

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

S.Robins
источник
1

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

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

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

Борис Калленс
источник
0

Для меня модульные тесты также являются поводом для «реального» использования интерфейса (ну, так же реально, как идут юнит-тесты!).

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

Это не избегает изменений в дизайне, скорее это выставляет потребность в них. Да, полное переписывание - это боль. Чтобы (попытаться) избежать этого, я обычно создаю (один или несколько) прототипов, возможно на Python (с окончательной разработкой на c ++).

Конечно, у вас не всегда есть время для всех этих вкусностей. Это как раз те случаи, когда вам потребуется БОЛЬШЕ времени для достижения ваших целей ... и / или чтобы держать все под контролем.

Francesco
источник
0

Добро пожаловать в цирк креативных разработчиков .


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

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

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

И теперь, если вы увидите UML, похожий на приведенный выше, вы сможете сказать:
«Босс, я начну с TDD для этого…».
Это еще одна новая вещь?
«Босс, я бы попробовал кое-что, прежде чем определиться с тем, как я буду кодировать…» С

наилучшими пожеланиями от PARIS
Claude

сл-р
источник