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


50

Я пытаюсь обернуть голову вокруг TDD, особенно в части разработки. Я посмотрел некоторые книги, но те, которые я нашел, в основном касаются части тестирования - История NUnit, почему тестирование хорошо, Red / Green / Refactor и как создать String Calculator.

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

Для иллюстрации представьте эти 3 требования:

  • Каталог должен иметь список продуктов
  • Каталог должен помнить, какие продукты просматривал пользователь
  • Пользователи должны иметь возможность искать продукт

На этом этапе многие книги вытаскивают волшебного кролика из шляпы и просто погружаются в «Тестирование ProductService», но они не объясняют, как они пришли к выводу, что это ProductService. Это та часть «развития» в TDD, которую я пытаюсь понять.

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

Согласно SOLID , мне потребуется UserService, но если я спроектирую систему без TDD, у меня может получиться целый набор Single-Method Services. Разве TDD не предназначен, чтобы заставить меня открыть мой дизайн в первую очередь?

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


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

3
@gnat: это вопрос о том, почему книги TDD не делают процесс разработки более понятным.
Роберт Харви

4
@gnat: Это была твоя редакция, а не моя. :) Смотрите мои изменения в названии вопроса и тела.
Роберт Харви

9
Если вы читали работу Роберта С. Мартина или, возможно, смотрели одно из его видео, вы увидите, что он часто задумывает дизайн, но не женат на нем. Он полагает, что его предвзятое представление о правильном дизайне возникнет из его испытаний, но он не принуждает его. И в конце концов, иногда этот дизайн делает, а иногда нет. Я хочу сказать, что ваш собственный опыт поможет вам, но тесты должны вас подвести. Тесты должны быть в состоянии разработать или опровергнуть ваш дизайн.
Энтони Пеграм

3
Так что дело не в тестировании, а в дизайне. Только это на самом деле не помогает вам в дизайне, а скорее помогает в проверке дизайна. Но разве это не тестирование?
Эрик Реппен

Ответы:


17

Идея TDD - начать с тестирования и работать с этого. Таким образом, для примера «Каталог должен иметь список продуктов» можно рассматривать как тест «Проверка продуктов в каталоге», и, таким образом, это первый тест. Теперь, что держит каталог? Что держит продукт? Это следующие части, и идея состоит в том, чтобы собрать воедино несколько кусочков, которые были бы чем-то вроде ProductService, который будет рожден после прохождения первого теста.

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


Test Driven Development Tutorial, где слайды 20-22 являются ключевыми. Идея состоит в том, чтобы узнать, что функциональность должна делать в результате, написать тест для нее и затем построить решение. Часть дизайна будет варьироваться в зависимости от того, что требуется, это может или не может быть так просто сделать. Ключевой момент заключается в том, чтобы использовать TDD с самого начала, а не пытаться вводить поздно в проект. Если вы начнете с тестов в первую очередь, это может помочь и, вероятно, в некотором смысле стоит отметить. Если вы попытаетесь добавить тесты позже, это может быть отложено или отложено. Более поздние слайды также могут быть полезны.


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


Роберт Харви добавил это в комментариях, которые стоит отметить в ответе:

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


31
@MichaelStum: К сожалению, я думаю, что это распространенное заблуждение о TDD: вы не можете развить программную архитектуру, просто написав модульные тесты и заставив их пройти. Написание модульных тестов влияет на дизайн, но не создает дизайн. Вы должны сделать это.
Роберт Харви

4
@RobertHarvey, JimmyHoffa: если бы я мог проголосовать за ваши комментарии 100 раз, я бы!
Док Браун

9
@ Роберт Харви: Я рад, что вы написали об этом распространенном заблуждении: я слишком часто слышу, что нужно просто сесть и написать всевозможные юнит-тесты, и дизайн просто «появится» самопроизвольно. И если ваш дизайн плохой, это потому, что вы не написали достаточно юнит-тестов. Я согласен с вами, что тесты - это инструмент для определения и проверки требований к вашему дизайну, но «вы должны сделать» проект самостоятельно. Я абсолютно согласен.
Джорджио

4
@ Джорджо, РобертХарви: +1000 к Роберту Харви от меня. К сожалению, это заблуждение достаточно распространено, и некоторые «опытные» специалисты по TDD / Agile считают, что это правда. Как, например, они притворяются, что вы можете «развить» решатель судоку из TDD без знания предметной области или какого-либо анализа . Интересно, если Рон Джеффрис когда-либо опубликовал продолжение ограничений TDD или объяснил, почему он внезапно прекратил свой эксперимент без каких-либо выводов или извлеченных уроков.
Андрес Ф.

3
@Andres F: Я знаю историю о судоку, и я думаю, что это очень интересно. Я думаю, что некоторые разработчики делают ошибку, думая, что инструмент (например, TDD или SCRUM) может заменить знания предметной области и свои собственные усилия, и ожидают, что при механическом применении определенного метода хорошее программное обеспечение волшебным образом «появится». Это часто люди, которые не любят тратить слишком много времени на анализ и проектирование и предпочитают напрямую что-то кодировать. Для них следование определенной методике - это алиби за то, что она не занимается надлежащим дизайном. Но это ИМХО злоупотребление TDD.
Джорджио

8

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

Как это сделать?

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

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

Это действительно так просто. Это не волшебный инструмент дизайна.


6

Я пытаюсь обернуть голову вокруг TDD ... Для иллюстрации представьте эти 3 требования:

  • Каталог должен иметь список продуктов
  • Каталог должен помнить, какие продукты просматривал пользователь

Эти требования должны быть пересмотрены с точки зрения человека. Кто хочет знать, какие продукты ранее просматривал пользователь? Пользователь? Продавец?

  • Пользователи должны иметь возможность искать продукт

Как? По имени? По бренду? Первым шагом в разработке через тестирование является определение теста, например:

browse to http://ourcompany.com
enter "cookie" in the product search box
page should show "chocolate-chip cookies" and "oatmeal cookies"

>

На этом этапе многие книги вытаскивают волшебного кролика из шляпы и просто погружаются в «Тестирование ProductService», но они не объясняют, как они пришли к выводу, что это ProductService.

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

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


ОК, ProductServiceтогда нет . Но как TDD сказал вам, что вам нужна база данных и ORM?
Роберт Харви

4
@ Роберт: это не так. Это дизайнерское решение, основанное на моем мнении о наиболее эффективном способе удовлетворения требований. Но решение может измениться.
Кевин Клайн

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

Я думаю, что тест "добавить товар; перезагрузите компьютер и перезагрузите систему; добавленный продукт должен все еще быть видимым. ' показывает, откуда возникает необходимость в какой-либо базе данных (но это может быть простой файл или XML).
yatima2975

3

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

Несколько примеров, когда я сталкивался с этим в прошлом:

  • Возьмите кучу подрядчиков, специализирующихся на технических работах, и скажите, что их команда - Agile и Test First. У них часто нет привычки, кроме как работать над спецификацией, и они не беспокоятся о качестве работы, пока она длится достаточно долго, чтобы завершить проект.

  • Попробуйте сначала сделать что-то новое, потратьте много времени на копирование тестов, так как вы обнаружите, что различные подходы и интерфейсы бесполезны.

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

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

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

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


1

Я убежден, что TDD является очень ценным подходом к детальному проектированию системы - то есть API и объектной модели. Тем не менее, чтобы перейти к тому моменту, когда вы начинаете использовать TDD, вам нужно иметь общую картину дизайна, уже смоделированную каким-то образом, и вам необходимо иметь общую картину архитектуры, уже смоделированной каким-то образом. @ user414076 перефразирует Роберта Мартина как имеющего в виду идею дизайна, но не состоящую в браке с ней. Именно так. Вывод - TDD - не единственное мероприятие по проектированию, это то, как детали проекта раскрываются. TDD должен предшествовать другие действия по проектированию и вписываться в общий подход (такой как Agile), который учитывает, как создается и развивается общий дизайн.

К вашему сведению - две книги, которые я рекомендую по теме, которые дают реальные и реалистичные примеры:

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

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


0

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

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

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


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

1
Правильно, высокоуровневая архитектура, такая как шаблон MVC, не возникнет только из TDD. Но то, что может появиться из TDD, это код, разработанный для удобства тестирования, что само по себе является конструктивным соображением.
Даниэль Перейра

0

В вопросе указано:

... многие книги вытаскивают волшебного кролика из шляпы и просто погружаются в "Тестирование ProductService", но они не объясняют, как они пришли к выводу, что это ProductService.

Они пришли к такому выводу, подумав о том, как они собираются протестировать этот продукт. "Какой продукт это делает?" «Ну, мы могли бы создать сервис». «Хорошо, давайте напишем тест для такой услуги»


0

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

Что касается дизайна, я имею в виду книги Роберта К. Мартина (Agile Development), а также «Шаблоны архитектуры корпоративных приложений и дизайн драйверов доменов» Мартина Фаулера. Последнее особенно систематично в извлечении сущностей и отношений из требований.

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


0

Разве TDD не предназначен, чтобы заставить меня открыть мой дизайн в первую очередь?

Нет.

Как вы можете проверить то, что вы не спроектировали в первую очередь?

Для иллюстрации представьте эти 3 требования:

  • Каталог должен иметь список продуктов
  • Каталог должен помнить, какие продукты просматривал пользователь
  • Пользователи должны иметь возможность искать продукт

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

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

Требование будет что-то вроде:

  • Покупатель может заказать определенное количество товара, если его достаточно на складе.

Так что, если это единственное требование, у вас может быть такой класс:

public class Product {

  private int quantity;

  public Product(int initialQuantity) {
    this.quantity = initialQuantity;
  }

  public void order(int quantity) {
    // To be implemented.
  }

}

Затем, используя TDD, вы должны написать тестовый пример перед реализацией метода order ().

public void ProductTest() {

    public void testCorrectOrder() {

        Product p = new Product(10);
        p.order(3);
        p.order(4);

    }

    @Expect(ProductOutOfStockException)
    public void testIncorrectOrder() {

        Product p = new Product(10);
        p.order(7);
        p.order(4);

    }

}

Таким образом, второй тест не пройден, тогда вы можете реализовать метод order () так, как вам нравится.


0

Вы совершенно правильно TDD приведете к хорошей реализации данного дизайна. Это не поможет вашему процессу проектирования.


Однако это дает вам сеть безопасности для улучшения дизайна без нарушения рабочего кода. Это рефакторинг, который пропускает большинство людей.
Адриан Шнайдер

-3

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.