Дизайн приложения среднего размера при выполнении TDD?
Я хорошо разбираюсь в модульном тестировании, DI, mocks и всем принципам дизайна, которые необходимы для того, чтобы как можно ближе к полному охвату кода, по возможности по-человечески (руководитель единой ответственности, подумайте, "как я буду тестировать это", как я код, и т.д.).
Мое последнее приложение, я не кодировал выполнение истинного TDD. Я продолжал модульное тестирование в виду, когда я закодировал, и написал свои тесты после написания кода, рефакторинга и т.д. Я делал TDD, когда это было "легко"... однако у меня не было так хорошо, как понять Теперь я... Это был первый проект, в котором я полностью использовал ДИ, насмешливые рамки и т.д., И первый, у которого был полный охват кода, - и я многому научился у него, когда я шел. Я испытываю желание назначить свой следующий проект, чтобы я мог полностью его кодировать TDD с нуля.
Я знаю, что это широкий вопрос, и я уже заказал TDD на примере и XP Unleashed, но я надеюсь получить краткий обзор того, как вы все проектируете/пишете большое приложение, делающее TDD.
Вы пишете все приложение, используя ничего, кроме прочеркнутого кода? (например, записывайте все сигнатуры функций, интерфейсы, структуры и пишите все приложение, но без написания какой-либо реальной реализации)? Я мог бы представить, что он работает с небольшими размерами, но разве это возможно при больших приложениях?
Если нет, то как вы могли бы написать свой первый unit test для функции самого высокого уровня в вашей системе? Скажем, например, - в веб-службе, где у вас есть функция под названием DoSomethingComplicated (param1,..., param6), открытая миру. Очевидно, что сначала написать тест для простой функции, такой как AddNumbers(), тривиально, но когда функция находится в верхней части стека вызовов, например this?
Вы все еще разрабатываете дизайн? Очевидно, что вы все еще хотите сделать "архитектуру" - например, блок-схему, показывающую, что IE разговаривает с IIS, который разговаривает с службой Windows через WCF, которая разговаривает с SQL-базой данных... ERD, которая показывает все ваши SQL-таблицы и их поля, и т.д., но как насчет дизайна класса? Взаимодействие между классами и т.д.? Вы разрабатываете этот фронт или просто продолжаете писать код заглушки, рефакторинг взаимодействия, когда вы идете вперед, пока все это не свяжется и похоже, что оно будет работать?
Любые советы очень ценятся
Ответы
Ответ 1
Я пишу все приложение, используя только пропущенный код?
Нет, не в малейшем смысле - это похоже на очень расточительный подход. Мы всегда должны помнить, что основной причиной для TDD является быстрая обратная связь. Автоматизированный набор тестов может рассказать нам, если мы сломаем что-нибудь намного быстрее, чем может провести ручной тест. Если мы дождаемся, пока все подключатся до последнего момента, мы не получим быстрой обратной связи - хотя мы можем получить обратную связь от наших модульных тестов, мы не будем знать, работает ли приложение в целом. Модульные тесты - это только одна форма теста, которую мы должны выполнить для проверки приложения.
Лучший подход - начать с самой важной функции и проделать свой путь оттуда, используя подход за пределами. Это часто означает запуск с некоторого интерфейса.
Как я это делаю, создавая желаемый интерфейс. Поскольку мы обычно не можем разработать интерфейс с TDD, я просто создаю View с помощью технологии выбора. Никаких тестов нет, но я подключаю пользовательский интерфейс к некоторому API (желательно с использованием декларативной привязки данных) и что при начале тестирования.
Вначале я бы затем TDD мои модели ViewModels/Presentation и соответствующие контроллеры, возможно, жестко кодируя некоторые ответы, чтобы увидеть, что пользовательский интерфейс работает. Как только у меня есть что-то, что не взорвалось при запуске, я проверяю код (помните, что многие небольшие инкрементные проверки).
Впоследствии я использую эту функцию по вертикали и убедитесь, что этот конкретный фрагмент пользовательского интерфейса может полностью пройти к источнику данных (или что-то еще), игнорируя все остальные функции.
Когда функция будет выполнена, я могу начать с следующей функции. Способ, которым я представляю этот процесс, заключается в том, что я заполняю приложение, делая один вертикальный срез за раз, пока все функции не будут выполнены.
Начинать приложение с зеленым полем, таким образом, всегда требуется дополнительное долгое время для первой функции, так как здесь вы должны подключить все, поэтому выберите что-то простое (например, начальный вид приложения), чтобы держать вещи как можно более простыми. После того, как первая функция будет выполнена, следующие становятся намного проще, поскольку основы теперь находятся на месте.
Я все еще разрабатываю дизайн?
Не много, нет. У меня обычно есть общий дизайн, прежде чем я начну, и когда я работаю в команде, мы нарисуем эту общую архитектуру на доске или слайдовой колоде, прежде чем мы начнем.
Это более или менее ограничено
- Число и имена слоев (пользовательский интерфейс, презентационная логика, модель домена, доступ к данным и т.д.).
- Используемые технологии (WPF, ASP.NET MVC, SQL Server,.NET 3.5 или еще что-то)
- Как мы структурируем производственный код и тестовый код и какие технологии тестирования мы используем
- Требования к качеству кода (парное программирование, статический анализ кода, стандарты кодирования и т.д.).
Остальное мы выясняем, когда мы идем, но мы используем много специальных сессий дизайна на доске, когда мы идем.
Ответ 2
Конечно, да. Перед вами большое приложение. У вас должно быть некоторое представление о структуре, которую она будет иметь, прежде чем вы начнете писать тесты и код. Вам не нужно все это детально прорабатывать, но вы должны иметь базовое представление о слоях, компонентах и интерфейсах. Например, если вы работаете в системе веб-сервисов, вы должны знать, что такое услуги верхнего уровня, и иметь хорошее первое приближение их подписей.
- Вы пишете все приложение, используя только пропущенный код?
Нет. Вы заглушаете вещи только в том случае, если их действительно трудно контролировать в тесте. Например, мне нравится заглушить базу данных и пользовательский интерфейс. Я также отключу сторонние интерфейсы. Иногда я удаляю один из моих собственных компонентов, если он значительно увеличивает время тестирования, или это заставляет меня создавать тестовые данные, которые слишком сложны. Но большую часть времени я позволяю своим тестам работать на довольно хорошо интегрированной системе.
Я должен сказать, что мне действительно не нравится стиль тестирования, который в значительной степени зависит от насмешек и заглушек. Не поймите меня неправильно, я думаю, что макеты и заглушки очень полезны для развязки от вещей, которые трудно проверить. Но мне не нравится писать вещи, которые трудно проверить, и поэтому я не использую много макетов и заглушек.
- Как вы пишете свой первый unit test для функции высокого уровня?
Большинство функций высокого уровня имеют вырожденное поведение. Например, логин является довольно высокой функцией уровня и может быть очень сложным. Но если вы попытаетесь войти в систему без имени пользователя и пароля, ответ от системы будет довольно простым. Написание этих тестов также будет очень простым. Итак, вы начинаете с вырожденных случаев. Как только вы исчерпали их, вы переходите к следующему уровню сложности. Например, что делать, если пользователь пытается войти в систему с именем пользователя, но не с паролем? Понемногу вы поднимаетесь по лестнице сложности, никогда не занимаясь более сложными аспектами, пока все более сложные аспекты не пройдут.
Замечательно, насколько хорошо эта стратегия работает. Вы могли бы подумать, что вы просто будете лазать по краям все время и никогда не попадаете в мясо; но это не то, что происходит. Вместо этого вы создаете внутреннюю структуру кода на основе всех вырожденных и исключительных случаев. Когда вы, наконец, обходитесь в основном потоке, вы обнаружите, что структура кода, над которым вы работаете, имеет хорошее отверстие только правой формы, чтобы подключить основной поток.
- Не создавайте свой пользовательский интерфейс.
Пользовательские интерфейсы вводят в заблуждение. Они заставляют вас сосредоточиться на неправильных аспектах системы. Вместо этого представьте, что ваша система должна иметь множество разных пользовательских интерфейсов. Некоторые из них будут веб, некоторые будут толстым клиентом, некоторые будут чистым. Создайте свою систему для правильной работы независимо от пользовательского интерфейса. Получите все бизнес-правила, работающие сначала, со всеми прохождением тестов. Затем подключите интерфейс позже. Я знаю, что это летит перед лицом многих обычных мудрости, но я бы не сделал этого иначе.
- Пожалуйста, сначала не создавайте базу данных.
Базы данных - это детали. Сохраните детали позже. Скорее, создайте свою систему так, как будто вы понятия не имели, какую базу данных вы использовали. Храните любое понятие схемы, таблиц, строк и столбцов в ядре системы. Реализуйте свои бизнес-правила, как будто все данные хранятся в памяти все время. Затем добавьте базу данных позже, как только вы получите все рабочие правила. Опять же, я знаю, что это летит перед лицом какой-то общепринятой мудрости, но системы связи с базами данных слишком рано - источник множества сильно искаженных конструкций.
Ответ 3
+1 Хороший вопрос
Я действительно не знаю ответа, но я бы начал с создания блоков классов, которые я мог бы тестировать, а затем встраивать в приложение, а не с материала верхнего уровня. И да, у меня был бы грубоватый дизайн интерфейсов, иначе я думаю, что вы обнаружите, что эти интерфейсы меняются так часто, как вы реорганизуете, что это было бы реальным препятствием.
TDD По примеру не поможет, я не думаю. IIRC - это простой пример. Я читаю Roy Osherove Art of Unit Testing, и, хотя он, похоже, всесторонне охватывает инструменты и методы, такие как mocks и stub, пример пока выглядит довольно простым, и я не вижу, чтобы он рассказывал вам, как подойти к большому проекту.
Ответ 4
- Вы пишете все приложение, используя только пропущенный код?
Для тестирования наших систем мы в основном проводим тестирование модулей, интеграции и удаленных служб. В модульных тестах мы завершаем все длительные, длительные и внешние сервисы, т.е. Операции с базами данных, подключение к веб-службам или любое подключение к внешним службам. Это делается для того, чтобы наши тесты были быстрыми, независимыми и не полагались на ответ любой внешней службы, чтобы обеспечить нам быструю обратную связь. Мы усвоили это с трудом, потому что у нас есть некоторые тесты, которые выполняют операции с базой данных, что делает его очень медленным, что противоречит принципу "Устройства должны быть быстрыми для запуска"
В тестах интеграции мы тестируем операции с базой данных, но все же не являемся веб-службами и внешними службами, потому что это может сделать проверку хрупкой в зависимости от их доступности, и мы используем автотест для запуска тестов в фоновом режиме все время, пока мы кодируем.
Однако для проверки любых удаленных сервисов у нас есть тесты, которые подключаются к внешним службам, выполняют на них операцию и получают ответ. Что важно для теста, так это ответ и его конечное состояние, если это важно для теста. Важно то, что мы проводим эти тесты в другом каталоге с именем remote (это соглашение, которое мы создали и следуем), и эти удаленные тесты выполняются только нашим сервером CI (непрерывной интеграции), когда мы объединяем любой код в master/соединительной линии и нажмите/скопируйте его на репо, чтобы мы быстро узнали, произошли ли какие-либо изменения в тех внешних сервисах, которые могут повлиять на наше приложение.
- Я все еще разрабатываю дизайн?
Да, но мы не делаем большой дизайн, в основном, что сказал дядя Боб (Robert C. Martin).
Кроме того, мы добираемся до доски, прежде чем погрузиться в кодирование и создадим несколько диаграмм совместной работы для всего мира, чтобы дать понять, что все в команде находятся на одной странице, что также помогает нам разделить работу среди членов команды.