TDD - начинающие проблемы и камни преткновения - PullRequest
23 голосов
/ 14 января 2010

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

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

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

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

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

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

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

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

Ответы [ 7 ]

10 голосов
/ 17 января 2010

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

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

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

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

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

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

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

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

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

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

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

8 голосов
/ 14 января 2010
  1. Кент Бек использует список ... наконец, теперь это просто что-то вроде "клиент должен иметь возможность подключиться к серверу" (что подразумевает запуск сервера и т. Д.).

Часто плохая практика.

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

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

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

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

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

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

И

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

И

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

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

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

Существуют интеграционные тесты, которые проверяют все это.

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

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

И вам нужно сделать оба.

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

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

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

5. Я не вижу усиления.

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

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

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

3 голосов
/ 20 января 2010

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 модульными тестами. Это заняло у меня около недели. Я думаю, что мог бы сделать это за день, если бы использовал модульные тесты после кода. Я не вижу усиления.

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

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

2 голосов
/ 10 февраля 2010

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

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

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

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

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

1 голос
/ 15 января 2010

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

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

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

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

Как вы справляетесь с перезаписью?

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

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

1 голос
/ 14 января 2010

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

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

1 голос
/ 14 января 2010

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

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

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

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

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

...