Разработка через тестирование - Test-driven development

Разработка программного обеспечения
Активность ядер
Парадигмы и модели
Методологии и рамки
Вспомогательные дисциплины
Практики
Инструменты
Стандарты и свод знаний
Глоссарии
Контуры

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

Американский инженер-программист Кент Бек, которому приписывают разработку или "повторное открытие"[1] техника, заявленная в 2003 году, что TDD поощряет простой дизайн и внушает доверие.[2]

Разработка через тестирование связана с концепциями программирования, ориентированными на тестирование. экстремальное программирование, начатое в 1999 г.,[3] но в последнее время сам по себе вызвал более широкий интерес.[4]

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

Цикл разработки через тестирование

Графическое представление жизненного цикла разработки через тестирование

Следующая последовательность основана на книге Разработка через тестирование на примере:[2]

1. Добавьте тест
При разработке через тестирование каждая новая функция начинается с написания теста. Напишите тест, который определяет функцию или улучшения функции, который должен быть очень кратким. Чтобы написать тест, разработчик должен четко понимать спецификацию и требования функции. Разработчик может добиться этого с помощью сценарии использования и пользовательские истории чтобы охватить требования и условия исключения, и может написать тест в любой среде тестирования, соответствующей программной среде. Это может быть модифицированная версия существующего теста. Это отличительная черта разработки через тестирование по сравнению с написанием модульных тестов. после то код написано: это заставляет разработчика сосредоточиться на требованиях перед написание кода - тонкое, но важное отличие.
2. Запустите все тесты и посмотрите, не прошел ли новый тест.
Это подтверждает, что испытательная привязь работает правильно, показывает, что новый тест не проходит, не требуя нового кода, потому что требуемое поведение уже существует, и исключает возможность того, что новый тест ошибочен и всегда будет проходить. Новый тест должен завершиться неудачно по ожидаемой причине. Этот шаг повышает уверенность разработчика в новом тесте.
3. Напишите код
Следующим шагом будет написание кода, который заставит тест пройти. Новый код, написанный на этом этапе, несовершенен и может, например, неэлегантно пройти тест. Это приемлемо, потому что на шаге 5 это будет улучшено и отточено.
На этом этапе единственная цель написанного кода - пройти тест. Программист не должен писать код, выходящий за рамки проверяемых тестом функций.
4. Запустите тесты.
Если все тестовые примеры теперь пройдены, программист может быть уверен, что новый код соответствует требованиям тестирования и не нарушит и не ухудшит какие-либо существующие функции. Если они этого не сделают, новый код необходимо откорректировать, пока они не сделают этого.
5. Рефакторинг кода
Растущая кодовая база должна быть очищен регулярно во время разработки через тестирование. Новый код можно переместить с того места, где он был удобен для прохождения теста, туда, где он более логичен. Дублирование необходимо удалить. Объект, учебный класс, модуль, Переменная и метод имена должны четко представлять их текущее назначение и использование по мере добавления дополнительных функций. По мере добавления функций тела методов могут становиться длиннее, а другие объекты - больше. Они извлекают выгоду из того, что они разделены, а их части тщательно названы для улучшения читаемость и ремонтопригодность, который будет становиться все более ценным позже в жизненный цикл программного обеспечения. Иерархии наследования может быть изменен, чтобы быть более логичным и полезным, и, возможно, извлечь пользу из признанных шаблоны проектирования. Существуют конкретные и общие рекомендации по рефакторингу и созданию чистого кода.[6][7] Постоянно повторяя тестовые примеры на каждом этапе рефакторинга, разработчик может быть уверен, что процесс не изменяет существующие функциональные возможности.
Концепция удаления дублирования - важный аспект любого дизайна программного обеспечения. В этом случае, однако, это также относится к удалению любого дублирования между тестовым кодом и производственным кодом, например магические числа или строки, повторяющиеся в обоих, чтобы тест прошел на шаге 3.
Повторение
Начиная с другого нового теста, цикл повторяется, чтобы продвинуть функциональность. Размер шагов всегда должен быть небольшим, от 1 до 10 изменений между каждым запуском теста. Если новый код быстро не удовлетворяет новому тесту или другие тесты неожиданно завершаются неудачно, программист должен отменить или вернуться к чрезмерному отладка. Непрерывная интеграция помогает, предоставляя обратимые контрольные точки. При использовании внешних библиотек важно не делать приращений, которые настолько малы, чтобы эффективно тестировать саму библиотеку,[4] за исключением случаев, когда есть основания полагать, что библиотека содержит ошибки или недостаточно полнофункциональна, чтобы удовлетворить все потребности разрабатываемого программного обеспечения.

Стиль разработки

Существуют различные аспекты использования разработки, основанной на тестировании, например, принципы «будь простым, глупым» (ЦЕЛОВАТЬ ) и "Тебе это не понадобится "(YAGNI). Сосредоточившись на написании только кода, необходимого для прохождения тестов, дизайн часто может быть чище и яснее, чем это достигается другими методами.[2] В Разработка через тестирование на примереКент Бек также предлагает принцип "Подделайте это, пока не сделаете это ".

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

Сначала напишите тесты: тесты должны быть написаны до функциональности, которую нужно протестировать. Утверждалось, что это дает много преимуществ. Это помогает гарантировать, что приложение написано для возможности тестирования, поскольку разработчики должны продумать, как тестировать приложение с самого начала, а не добавлять его позже. Это также гарантирует, что будут написаны тесты для каждой функции. Кроме того, написание тестов в первую очередь приводит к более глубокому и раннему пониманию требований к продукту, обеспечивает эффективность тестового кода и позволяет постоянно сосредоточивать внимание на качество программного обеспечения.[8] При написании кода, ориентированного на функции, разработчики и организации имеют тенденцию подталкивать разработчика к следующей функции, даже полностью игнорируя тестирование. Первый тест TDD может даже сначала не скомпилироваться, потому что требуемые классы и методы могут еще не существовать. Тем не менее, этот первый тест работает как начало исполняемой спецификации.[9]

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

Держите устройство маленьким

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

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

Передовые практики разработки через тестирование могут привести к разработка через приемочные испытания (ATDD) и Уточнение на примере где критерии, указанные заказчиком, автоматизируются в приемочные испытания, которые затем управляют традиционным процессом разработки на основе модульных тестов (UTDD).[10] Этот процесс гарантирует, что у заказчика есть автоматизированный механизм, позволяющий решить, соответствует ли программное обеспечение его требованиям. Благодаря ATDD у команды разработчиков теперь есть конкретная цель, которую нужно выполнить - приемочные испытания, - которые позволяют им постоянно концентрироваться на том, чего действительно хочет заказчик от каждой пользовательской истории.

Лучшие практики

Структура теста

Эффективный макет тестового примера обеспечивает выполнение всех необходимых действий, улучшает читаемость тестового примера и сглаживает процесс выполнения. Последовательная структура помогает в создании самодокументируемого тестового примера. Обычно применяемая структура для тестовых случаев включает (1) настройку, (2) выполнение, (3) проверку и (4) очистку.

  • Настройка: переведите тестируемое устройство (UUT) или всю тестовую систему в состояние, необходимое для запуска теста.
  • Выполнение: запуск / управление проверяемым оборудованием для выполнения целевого поведения и захвата всех выходных данных, таких как возвращаемые значения и выходные параметры. Этот шаг обычно очень простой.
  • Проверка: убедитесь, что результаты теста верны. Эти результаты могут включать явные выходные данные, захваченные во время выполнения, или изменения состояния проверяемого оборудования.
  • Очистка: возврат проверяемого оборудования или всей тестовой системы в состояние до тестирования. Это восстановление позволяет выполнить еще один тест сразу после этого. В некоторых случаях, чтобы сохранить информацию для анализа возможных сбоев теста, очистка должна начинать тест непосредственно перед запуском настройки теста. [8]

Индивидуальные передовые практики

Некоторые передовые практики, которым может следовать отдельный человек, заключаются в разделении общей логики настройки и разрыва на службы поддержки тестирования, используемые соответствующими тестовыми примерами, чтобы сохранить каждый тестовый оракул сосредоточился только на результатах, необходимых для проверки его теста, и на разработке тестов, связанных со временем, чтобы допускать выполнение в операционных системах, не работающих в режиме реального времени. Распространенная практика допуска 5–10% маржи для позднего выполнения снижает потенциальное количество ложноотрицательных результатов при выполнении теста. Также предлагается рассматривать тестовый код с тем же уважением, что и производственный код. Тестовый код должен работать правильно как для положительных, так и для отрицательных случаев, длиться долгое время, быть читаемым и поддерживаемым. Команды могут собираться вместе и анализировать тесты и методы тестирования, чтобы поделиться эффективными методами и выявить вредные привычки.[11]

Практики, которых следует избегать, или "антипаттерны"

  • Наличие тестовых примеров зависит от состояния системы, которым управляют из ранее выполненных тестовых примеров (то есть вы всегда должны запускать модульный тест из известного и предварительно настроенного состояния).
  • Зависимости между тестовыми примерами. Набор тестов, в которых тестовые примеры зависят друг от друга, является хрупким и сложным. Не следует предполагать порядок исполнения. Базовый рефакторинг начальных тестовых примеров или структуры UUT вызывает спираль все более и более всепроникающих воздействий на связанные тесты.
  • Взаимозависимые тесты. Взаимозависимые тесты могут вызывать каскадные ложноотрицательные результаты. Сбой в раннем тестовом примере нарушает последующий тестовый пример, даже если в проверяемом оборудовании не существует фактической ошибки, что увеличивает анализ дефектов и усилия по отладке.
  • Тестирование точного времени выполнения или производительности.
  • Создание «всезнающих оракулов». Оракул, который проверяет больше, чем необходимо, со временем становится более дорогим и хрупким. Эта очень распространенная ошибка опасна, потому что вызывает незаметную, но повсеместную потерю времени в сложном проекте.[11]
  • Детали реализации тестирования.
  • Медленно работающие тесты.

Преимущества

Исследование 2005 года показало, что использование TDD означает написание большего количества тестов, и, в свою очередь, программисты, которые пишут больше тестов, как правило, более продуктивны.[12] Гипотезы, касающиеся качества кода и более прямой корреляции между TDD и производительностью, оказались неубедительными.[13]

Программисты, использующие чистый TDD на новом ("Greenfield ") проекты сообщили, что они редко испытывали потребность в отладчик. Используется вместе с система контроля версий, когда тесты неожиданно завершаются неудачно, возврат кода к последней версии, прошедшей все тесты, часто может быть более продуктивным, чем отладка.[14]

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

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

Хотя верно то, что с TDD требуется больше кода, чем без TDD из-за кода модульного тестирования, общее время реализации кода может быть меньше на основе модели Мюллера и Падберга.[16] Большое количество тестов помогает ограничить количество дефектов в коде. Раннее и частое тестирование помогает выявлять дефекты на ранних этапах цикла разработки, предотвращая их превращение в обычные и дорогостоящие проблемы. Устранение дефектов на ранних этапах процесса обычно позволяет избежать длительной и утомительной отладки на более поздних этапах проекта.

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

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

Мадейски[17] предоставил эмпирические доказательства (через серию лабораторных экспериментов с участием более 200 разработчиков) о превосходстве практики TDD над традиционным подходом Test-Last или подходом тестирования на правильность в отношении более низкой связи между объектами (CBO). Средняя величина эффекта представляет собой средний (но близкий к большому) эффект на основе метаанализа проведенных экспериментов, что является важным выводом. Он предлагает лучшую модульность (т.е. более модульную конструкцию), более легкое повторное использование и тестирование разработанных программных продуктов благодаря практике программирования TDD.[17] Мадейски также измерил влияние практики TDD на модульные тесты, используя покрытие ветвей (BC) и индикатор оценки мутаций (MSI),[18][19][20] которые являются индикаторами тщательности и эффективности выявления неисправностей модульных тестов, соответственно. Влияние TDD на покрытие филиалов было средним по размеру и поэтому считается существенным эффектом.[17]

Ограничения

Разработка через тестирование не выполняет достаточного тестирования в ситуациях, когда требуются полные функциональные тесты для определения успеха или неудачи из-за широкого использования модульных тестов.[21] Примеры таких пользовательские интерфейсы, программы, которые работают с базы данных, а некоторые зависят от конкретных сеть конфигурации. TDD рекомендует разработчикам помещать минимальное количество кода в такие модули и максимизировать логику тестируемого библиотечного кода, используя подделки и издевается представлять внешний мир.[22]

Поддержка менеджмента необходима. Если вся организация не верит в то, что разработка через тестирование улучшит продукт, руководство может почувствовать, что время, потраченное на написание тестов, потрачено зря.[23]

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

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

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

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

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

Тестовая работа

Разработка через тестирование была принята за пределами разработки программного обеспечения как в группах продуктов, так и в сервисных группах, как работа, управляемая тестированием.[25] Как и в случае с TDD, команды, не занимающиеся программным обеспечением, разрабатывают контроль качества (QC) проверки (обычно ручные, а не автоматические) каждого аспекта работы перед ее началом. Эти проверки QC затем используются для информирования дизайна и подтверждения связанных результатов. Шесть шагов последовательности TDD применяются с небольшими семантическими изменениями:

  1. «Добавить чек» заменяет «Добавить тест»
  2. "Выполнить все проверки" заменяет "Выполнить все проверки".
  3. «Сделай работу» заменяет «Напиши код»
  4. "Выполнить все проверки" заменяет "Выполнить тесты"
  5. «Очистить работу» заменяет «Код рефакторинга»
  6. "Повторение"

TDD и ATDD

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

TDD и BDD

BDD (поведенческая разработка ) сочетает в себе практики TDD и ATDD.[27]Он включает в себя сначала практику написания тестов, но фокусируется на тестах, которые описывают поведение, а не на тестах, которые проверяют единицу реализации. Такие инструменты как JBehave, Огурец, Mspec и Specflow предоставляют синтаксисы, которые позволяют владельцам продуктов, разработчикам и инженерам по тестированию совместно определять поведение, которое затем может быть преобразовано в автоматизированные тесты.

Видимость кода

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

В объектно-ориентированный дизайн это по-прежнему не обеспечивает доступа к частным данным и методам. Поэтому для модульных тестов может потребоваться дополнительная работа. В Ява и другие языки, разработчик может использовать отражение для доступа к закрытым полям и методам.[28] В качестве альтернативы внутренний класс могут использоваться для хранения модульных тестов, чтобы они имели видимость членов и атрибутов включающего класса. в .NET Framework и некоторые другие языки программирования, частичные классы может использоваться для предоставления доступа к закрытым методам и данным для тестов.

Важно, чтобы такие тестовые хаки не оставались в производственном коде. В C и другие языки, директивы компилятора Такие как #if DEBUG ... #endif могут быть размещены вокруг таких дополнительных классов и всего остального кода, связанного с тестированием, чтобы предотвратить их компиляцию в выпущенный код. Это означает, что выпущенный код не совсем такой, как тот, который был протестирован модулем. Регулярное выполнение меньшего количества, но более полных, сквозных интеграционных тестов в окончательной сборке может гарантировать (помимо прочего), что не существует производственного кода, тонко зависящего от аспектов тестовой оснастки.

Среди практикующих TDD ведутся споры, зафиксированные в их блогах и других статьях, относительно того, разумно ли в любом случае тестировать частные методы и данные. Некоторые утверждают, что частные члены - это всего лишь деталь реализации, которая может измениться, и это должно быть разрешено, не нарушая количества тестов. Таким образом, должно быть достаточно протестировать любой класс через его открытый интерфейс или через интерфейс подкласса, который в некоторых языках называется «защищенным» интерфейсом.[29] Другие говорят, что важные аспекты функциональности могут быть реализованы в частных методах, и их прямое тестирование дает преимущество меньших и более прямых модульных тестов.[30][31]

Программное обеспечение для TDD

Есть много фреймворков и инструментов для тестирования, которые можно использовать в TDD.

Фреймворки xUnit

Разработчики могут использовать компьютерные рамки тестирования, обычно совместно именуемые xUnit (которые являются производными от SUnit, созданного в 1998 году) для создания и автоматического запуска тестовых примеров. Инфраструктуры xUnit предоставляют возможности проверки тестов в стиле утверждений и составления отчетов о результатах. Эти возможности критически важны для автоматизации, поскольку они переносят бремя проверки выполнения с независимой операции постобработки на операцию, которая включается в выполнение теста. Среда выполнения, предоставляемая этими средами тестирования, позволяет автоматически выполнять все системные тестовые примеры или различные подмножества вместе с другими функциями.[32]

Результаты TAP

Фреймворки тестирования могут принимать выходные данные модульных тестов на независимом от языка языке. Протокол Test Anything Создан в 1987 году.

Фейки, моки и интеграционные тесты

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

Когда разрабатываемый код опирается на базу данных, веб-службу или любой другой внешний процесс или службу, принудительное разделение с возможностью тестирования модулей также является возможностью и движущей силой для разработки более модульного, более тестируемого и более многократно используемого кода.[33] Необходимы два шага:

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

Методы поддельных и имитирующих объектов, которые возвращают данные якобы из хранилища данных или пользователя, могут помочь процессу тестирования, всегда возвращая одни и те же реалистичные данные, на которые могут полагаться тесты. Они также могут быть настроены на предопределенные режимы сбоев, чтобы можно было разработать и надежно протестировать процедуры обработки ошибок. В режиме сбоя метод может возвращать недопустимый, неполный или ноль ответ, или может бросить исключение. Фальшивые сервисы, отличные от хранилищ данных, также могут быть полезны в TDD: фальшивые сервисы шифрования могут фактически не шифровать передаваемые данные; служба поддельных случайных чисел всегда может вернуть 1. Поддельные или ложные реализации являются примерами внедрение зависимости.

Test Double - это специфическая для тестирования возможность, которая заменяет возможность системы, обычно класс или функцию, от которых зависит UUT. Есть два момента, когда тестовые двойники могут быть введены в систему: ссылка и выполнение. Подстановка времени ссылки - это когда тестовый дубль компилируется в загрузочный модуль, который выполняется для проверки тестирования. Этот подход обычно используется при работе в среде, отличной от целевой, которая требует дублирования кода аппаратного уровня для компиляции. Альтернативой замене компоновщика является замена во время выполнения, при которой реальная функциональность заменяется во время выполнения тестового примера. Эта замена обычно выполняется путем переназначения известных указателей на функции или замены объекта.

Тестовые двойники бывают разных типов и разной сложности:

  • Дурачок - Манекен - самая простая форма тестового дублера. Это облегчает замену времени компоновщика, предоставляя возвращаемое значение по умолчанию там, где это необходимо.
  • Заглушка - Заглушка добавляет упрощенную логику к манекену, обеспечивая различные выходы.
  • Шпион - шпион захватывает и делает доступной информацию о параметрах и состоянии, публикуя средства доступа для тестирования кода частной информации, обеспечивая более продвинутую проверку состояния.
  • Насмехаться - Макет определяется отдельным тестовым примером для проверки поведения конкретного теста, проверки значений параметров и последовательности вызовов.
  • Симулятор. Симулятор - это комплексный компонент, обеспечивающий более точное приближение целевой способности (удваиваемой). Симулятор обычно требует значительных дополнительных усилий по разработке.[8]

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

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

  • В Срывать , который является неотъемлемой частью многих тестовых фреймворков.
  • попробуй ... поймай ... наконец Обработка исключений конструкции, если таковые имеются.
  • Транзакции базы данных где сделка атомарно включает, возможно, операцию записи, чтения и соответствующую операцию удаления.
  • Создание «снимка» базы данных перед запуском любых тестов и откат к снимку после каждого запуска теста. Это можно автоматизировать с помощью такой инфраструктуры, как Муравей или же NAnt или непрерывная интеграция система, такая как Круиз-контроль.
  • Инициализация базы данных в чистом состоянии перед тесты, а не очистка после их. Это может быть актуально, когда очистка может затруднить диагностику сбоев теста за счет удаления окончательного состояния базы данных перед выполнением подробной диагностики.

TDD для сложных систем

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

Проектирование для проверки

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

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

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

Управление тестами для больших команд

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

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

Смотрите также

Рекомендации

  1. ^ Кент Бек (11 мая 2012 г.). «Почему Кент Бек говорит о« повторном открытии »разработки через тестирование?». Получено 1 декабря, 2014.
  2. ^ а б c Бек, Кент (2002-11-08). Разработка через тестирование на примере. Васим: Эддисон Уэсли. ISBN  978-0-321-14653-3.
  3. ^ Ли Коупленд (декабрь 2001 г.). «Экстремальное программирование». Компьютерный мир. Архивировано из оригинал 27 августа 2011 г.. Получено 11 января, 2011.
  4. ^ а б Ньюкирк, JW, и Воронцов, AA. Разработка через тестирование в Microsoft .NET, Microsoft Press, 2004.
  5. ^ Фезерс, М. Эффективная работа с Legacy Code, Prentice Hall, 2004 г.
  6. ^ Бек, Кент (1999). Объяснение опыта, 1-е издание. Эддисон-Уэсли Профессионал. п.57. ISBN  0201616416.
  7. ^ Оттингер и Лангр, Тим и Джефф. «Простой дизайн». Получено 5 июля 2013.
  8. ^ а б c d е ж грамм "Effective TDD for Complex Embedded Systems Whitepaper" (PDF). Pathfinder Solutions.
  9. ^ "Agile Test Driven Development". Agile Sherpa. 2010-08-03. Архивировано из оригинал на 2012-07-23. Получено 2012-08-14.
  10. ^ Koskela, L. "Test Driven: TDD and Acceptance TDD for Java Developers", Manning Publications, 2007
  11. ^ а б Test-Driven Development (TDD) for Complex Systems Introduction на YouTube by Pathfinder Solutions
  12. ^ Erdogmus, Hakan; Morisio, Torchiano. "On the Effectiveness of Test-first Approach to Programming". Proceedings of the IEEE Transactions on Software Engineering, 31(1). January 2005. (NRC 47445). Архивировано из оригинал на 2014-12-22. Получено 2008-01-14. We found that test-first students on average wrote more tests and, in turn, students who wrote more tests tended to be more productive.
  13. ^ Proffitt, Jacob. "TDD Proven Effective! Or is it?". Архивировано из оригинал на 2008-02-06. Получено 2008-02-21. So TDD's relationship to quality is problematic at best. Its relationship to productivity is more interesting. I hope there's a follow-up study because the productivity numbers simply don't add up very well to me. There is an undeniable correlation between productivity and the number of tests, but that correlation is actually stronger in the non-TDD group (which had a single outlier compared to roughly half of the TDD group being outside the 95% band).
  14. ^ Llopis, Noel (20 February 2005). "Stepping Through the Looking Glass: Test-Driven Game Development (Part 1)". Games from Within. Получено 2007-11-01. Comparing [TDD] to the non-test-driven development approach, you're replacing all the mental checking and debugger stepping with code that verifies that your program does exactly what you intended it to do.
  15. ^ Mayr, Herwig (2005). Projekt Engineering Ingenieurmässige Softwareentwicklung in Projektgruppen (2., neu bearb. Aufl. ed.). München: Fachbuchverl. Leipzig im Carl-Hanser-Verl. п. 239. ISBN  978-3446400702.
  16. ^ Müller, Matthias M.; Padberg, Frank. "About the Return on Investment of Test-Driven Development" (PDF). Universität Karlsruhe, Germany: 6. S2CID  13905442. Получено 2012-06-14. Цитировать журнал требует | журнал = (помощь)
  17. ^ а б c Madeyski, L. "Test-Driven Development - An Empirical Evaluation of Agile Practice", Springer, 2010, ISBN  978-3-642-04287-4, pp. 1-245. DOI: 978-3-642-04288-1
  18. ^ The impact of Test-First programming on branch coverage and mutation score indicator of unit tests: An experiment. by L. Madeyski Information & Software Technology 52(2): 169-184 (2010)
  19. ^ On the Effects of Pair Programming on Thoroughness and Fault-Finding Effectiveness of Unit Tests by L. Madeyski PROFES 2007: 207-221
  20. ^ Impact of pair programming on thoroughness and fault detection effectiveness of unit test suites. by L. Madeyski Software Process: Improvement and Practice 13(3): 281-295 (2008)
  21. ^ "Problems with TDD". Dalkescientific.com. 2009-12-29. Получено 2014-03-25.
  22. ^ Hunter, Andrew (2012-10-19). "Are Unit Tests Overused?". Simple-talk.com. Получено 2014-03-25.
  23. ^ Loughran, Steve (November 6, 2006). "Testing" (PDF). HP Laboratories. Получено 2009-08-12.
  24. ^ "Fragile Tests".
  25. ^ Leybourn, E. (2013) Directing the Agile Organisation: A Lean Approach to Business Management. London: IT Governance Publishing: 176-179.
  26. ^ Lean-Agile Acceptance Test-Driven Development: Better Software Through Collaboration. Boston: Addison Wesley Professional. 2011 г. ISBN  978-0321714084.
  27. ^ "BDD". Получено 2015-04-28.
  28. ^ Burton, Ross (2003-11-12). "Subverting Java Access Protection for Unit Testing". O'Reilly Media, Inc. Получено 2009-08-12.
  29. ^ van Rossum, Guido; Warsaw, Barry (5 July 2001). "PEP 8 -- Style Guide for Python Code". Фонд программного обеспечения Python. Получено 6 мая 2012.
  30. ^ Newkirk, James (7 June 2004). "Testing Private Methods/Member Variables - Should you or shouldn't you". Корпорация Майкрософт. Получено 2009-08-12.
  31. ^ Stall, Tim (1 Mar 2005). "How to Test Private and Protected methods in .NET". CodeProject. Получено 2009-08-12.
  32. ^ "Effective TDD for Complex, Embedded Systems Whitepaper". Pathfinder Solutions. Архивировано из оригинал на 2013-08-20. Получено 2012-11-27.
  33. ^ Fowler, Martin (1999). Refactoring - Improving the design of existing code. Boston: Addison Wesley Longman, Inc. ISBN  0-201-48567-2.

внешняя ссылка