TDD - проблемы и камни преткновения для начинающих

StackOverflow https://stackoverflow.com/questions/2066593

  •  20-09-2019
  •  | 
  •  

Вопрос

Хотя я написал модульные тесты для большей части кода, который я сделал, я только недавно получил в свои руки копию TDD by example Кента Бека.Я всегда сожалел о некоторых принятых мной дизайнерских решениях, поскольку они не позволяли приложению быть "тестируемым".Я прочитал книгу, и хотя кое-что из нее выглядит чуждым, я почувствовал, что смогу с этим справиться, и решил опробовать это в моем текущем проекте, который в основном представляет собой клиент-серверную систему, через которую взаимодействуют две части.USB.Один на гаджете, а другой на хосте.Приложение написано на Python.

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

Основываясь на моем опыте, у меня есть несколько вопросов, которые я хотел бы задать.Я получил кое-какую информацию от Новичок в TDD:Существуют ли примеры приложений с тестами, показывающими, как выполнять TDD? но у меня есть несколько конкретных вопросов, на которые я хотел бы получить ответы / обсудить.

  1. Кент Бек использует список, который он дополняет и вычеркивает из него, чтобы направлять процесс разработки.Как вы составляете такой список?Изначально у меня было несколько пунктов, таких как "сервер должен запуститься", "сервер должен прервать работу, если канал недоступен" и т.д.но они перепутались, и, наконец, теперь это просто что-то вроде "клиент должен иметь возможность подключаться к серверу" (что включало запуск сервера и т.д.).
  2. Как вы справляетесь с переписыванием?Изначально я выбрал полудуплексную систему, основанную на именованных каналах, чтобы я мог разработать логику приложения на своей собственной машине, а затем позже добавить часть связи по USB.Он был перемещен, чтобы стать вещью на основе сокетов, а затем перешел от использования необработанных сокетов к использованию модуля Python SocketServer.Каждый раз, когда что-то менялось, я обнаруживал, что мне приходилось переписывать значительную часть тестов, что раздражало.Я полагал, что тесты будут в какой-то степени неизменным руководством во время моей разработки.Им просто захотелось больше кода для обработки.
  3. Мне нужен был клиент и сервер для связи по каналу, чтобы протестировать обе стороны.Я мог бы поиздеваться над одной из сторон, чтобы протестировать другую, но тогда весь канал не был бы протестирован, и я боюсь, что пропустил бы это.Это отвлекало от общего ритма красного / зеленого / рефакторинга.Это просто недостаток опыта или я делаю что-то не так?
  4. "Притворяйся, пока у тебя это не получится" оставило мне много грязного кода, на рефакторинг и очистку которого я позже потратил много времени.Неужели все так и работает?
  5. В конце сеанса у меня теперь есть клиент и сервер, запущенные примерно с 3 или 4 модульными тестами.На это у меня ушло около недели.Я думаю, я мог бы сделать это за день, если бы использовал модульные тесты after code way.Я не вижу в этом никакой выгоды.

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

P.S.:Пожалуйста, дайте мне знать, должно ли это быть вики сообщества, и я отмечу это так.

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

Обновление 1:Практикуй, практикуй, практикуй!

Это было полезно?

Решение

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

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

"Список" может быть довольно неформальным (так обстоит дело в книге Бека), но когда вы переходите к превращению элементов в тесты, попробуйте записать инструкции в формате "[Когда с этим что-то случится], тогда [это условие должно быть истинным для этого]".Это заставит вас больше задуматься о том, что именно вы проверяете, как бы вы это проверяли, и преобразует непосредственно в тесты - или, если это не так, это должно дать вам представление о том, какая часть функциональности отсутствует.Подумайте о варианте использования / сценарии.Например, "сервер должен запуститься" неясно, потому что никто не инициирует действие.

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

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

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

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

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

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

Я думаю, я мог бы сделать это за день если бы я использовал модульные тесты после code way.Я не вижу в этом никакой выгоды.

Опять же, это требует практики, и со временем вы должны стать быстрее.Кроме того, иногда TDD более продуктивен, чем другие, я обнаружил, что в некоторых ситуациях, когда я точно знаю код, который хочу написать, просто быстрее написать большую часть кода, а затем писать тесты.
Помимо Бека, одна книга, которая мне понравилась, - "Искусство модульного тестирования" Роя Ошерова.Это не книга по TDD, и она ориентирована на .Net, но, возможно, вы все равно захотите ознакомиться с ней:хорошая часть посвящена тому, как писать поддерживаемые тесты, качеству тестов и связанным с этим вопросам.Я обнаружил, что книга перекликается с моим опытом после написания тестов, и иногда мне было трудно сделать это правильно...
Поэтому мой совет таков: не выбрасывайте полотенце слишком быстро и дайте ему немного времени.Возможно, вы также захотите попробовать что-нибудь попроще - тестирование вещей, связанных с взаимодействием с сервером, не похоже на самый простой проект для начала!

Другие советы

  1. Кент Бек использует список...наконец, теперь это просто что-то вроде «клиент должен иметь возможность подключаться к серверу» (что включает в себя запуск сервера и т. д.).

Зачастую это плохая практика.

Отдельные тесты для каждого отдельного слоя архитектуры — это хорошо.

Консолидированные тесты имеют тенденцию скрывать архитектурные проблемы.

Однако тестируйте только общедоступные функции.Не каждая функция.

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

2.Как вы справляетесь с перезаписью?...Я обнаружил, что мне пришлось переписать значительную часть тестов.

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

И

Да, значительные изменения в архитектуре означают значительные изменения в тестировании.

И

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

3.Мне нужен был клиент и сервер для связи через канал, чтобы протестировать обе стороны.Я мог бы высмеять одну из сторон, чтобы протестировать другую, но тогда весь канал не будет протестирован...

Есть юнит-тесты.С издевательствами.

Есть интеграционные тесты, которые проверяют всё целиком.

Не путайте их.

Вы можете использовать инструменты модульного тестирования для проведения интеграционных тестов, но это разные вещи.

И вам нужно сделать и то, и другое.

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

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

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

5.Я не вижу выгоды.

Все настоящие гении обнаруживают, что тестирование их замедляет.

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

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

Q.Кент Бек использует список, который он дополняет и вычеркивает из него, чтобы направлять процесс разработки.Как вы составляете такой список?Изначально у меня было несколько пунктов, таких как "сервер должен запуститься", "сервер должен прервать работу, если канал недоступен" и т.д.но они перепутались, и, наконец, теперь это просто что-то вроде "клиент должен иметь возможность подключаться к серверу" (что включало запуск сервера и т.д.).

Я начинаю с того, что выбираю все, что могу проверить.В вашем примере вы выбрали "запуск сервера".

Server starts

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

Configured server correctly
Server starts

На самом деле, однако, "запуск сервера" зависит от "правильно настроенного сервера", поэтому я проясняю эту ссылку.

Configured server correctly
Server starts if configured correctly

Теперь я ищу варианты.Я спрашиваю: "Что могло пойти не так?" Я мог неправильно настроить сервер.Сколько разных способов это имеет значение?Каждый из них представляет собой тест.Как может сервер не запускаться, даже если я настроил его правильно?Каждый такой случай представляет собой проверку.

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

Когда я меняю поведение, я нахожу разумным изменить тесты, и даже сначала изменить их!Однако, если мне приходится изменять тесты, которые напрямую не проверяют поведение, которое я нахожусь в процессе изменения, это признак того, что мои тесты зависят от слишком большого количества различных моделей поведения.Это интеграционные тесты, которые я считаю мошенничеством.(Google "Интеграционные тесты - это мошенничество")

Q.Мне нужен был клиент и сервер для связи по каналу, чтобы протестировать обе стороны.Я мог бы поиздеваться над одной из сторон, чтобы протестировать другую, но тогда весь канал не был бы протестирован, и я боюсь, что пропустил бы это.Это отвлекало от общего ритма красного / зеленого / рефакторинга.Это просто недостаток опыта или я делаю что-то не так?

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

Q."Притворяйся, пока у тебя это не получится" оставило мне много грязного кода, на рефакторинг и очистку которого я позже потратил много времени.Неужели все так и работает?

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

Q.В конце сеанса у меня теперь есть клиент и сервер, запущенные примерно с 3 или 4 модульными тестами.На это у меня ушло около недели.Я думаю, я мог бы сделать это за день, если бы использовал модульные тесты after code way.Я не вижу в этом никакой выгоды.

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

Кроме того, не измеряйте кривую обучения.Мой первый настоящий опыт работы с TDD состоял в переписывании 3-месячной работы за 9-14-часовой рабочий день.У меня было 125 тестов, выполнение которых заняло 12 минут.Я понятия не имел, что делаю, и это было медленно, но стабильно, а результаты были фантастическими.По сути, я переписал за 3 недели то, на что изначально ушло 3 месяца, чтобы исправить ошибку.Если бы я написал это сейчас, то, вероятно, смог бы сделать это за 3-5 дней.В чем разница?В моем наборе тестов было бы 500 тестов, выполнение которых занимает 1-2 секунды.Это пришло с практикой.

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

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

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

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

Кодирование — это сложно.Пойдем по магазинам.

По первому пункту см. вопрос Я спросил некоторое время назад по поводу вашего первого пункта.

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

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

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

Я рекомендую Блог тестирования Google довольно сильно, потому что некоторые статьи там значительно улучшили мое тестирование проектов TDD.

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

Я начал заниматься TDD, может быть, 6 месяцев назад?Я все еще учусь сам.Могу сказать, что со временем мои тесты и код стали намного лучше, так что продолжайте в том же духе.Я также очень рекомендую книгу «Шаблоны проектирования XUnit».

Как вы составляете такой список для добавления и вычеркивания из него, чтобы направлять процесс разработки?Изначально у меня было несколько пунктов, таких как "сервер должен запуститься ", "сервер должен прервать работу, если канал недоступен"

Элементы в списках задач TDD более мелкозернистые, они нацелены на тестирование только одного поведения одного метода, например:

  • проверьте успешное подключение к клиенту
  • ошибка подключения к тестовому клиенту типа 1
  • ошибка подключения к тестовому клиенту 2-го типа
  • протестируйте успешное общение с клиентом
  • сбой связи с тестовым клиентом при отсутствии подключения

Вы могли бы составить список тестов (положительных и отрицательных) для каждого приведенного вами примера.Более того, при модульном тестировании вы не устанавливаете никакого соединения между сервером и клиентом.Вы просто вызываете методы изолированно, ...Это ответ на вопрос 3.

Как вы справляетесь с переписыванием?

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top