Некоторое время назад я написал статью для Devzone под названием «Введение в искусство модульного
тестирования в PHP», в которой объяснялось, почему тестирование, и особенно автоматизированное тестирование, является важным аспектом разработки на любом языке программирования. Я также предложил краткий пример применения методологии проектирования под названием Test-Driven Development, которая продемонстрировала, что написание тестов до написания кода привело к улучшению более простого дизайна без ошибок.
В этой статье я познакомлю вас с практикой TDD под названием «Поведение, управляемое поведением», которая привлекает внимание более года и привлекает новообращенных (как и я!).
Разработка через тестирование
В старые времена eXtreme Programming (XP) никто не рассматривал TDD. Это было все о тестировании всего, что могло бы пойти не так (идти в крайности в истинном стиле XP), чтобы предотвратить его неизбежную ошибку. Если вы только начинаете с модульного тестирования, вы, вероятно, заметили, как непроверенный код, похоже, собирает ошибки с невероятной скоростью.
Затем люди стали слышать об использовании тестов для разработки приложений и о том, как это чудесное открытие привело к созданию более надежного, более надежного исходного кода более высокого качества и с превосходным дизайном. Вскоре тест-ориентированная разработка стала движущей силой XP и Agile-движений, десятки книг и сотни статей превозносили ее достоинства. Каркасы, изначально разработанные для тестирования, были быстро адаптированы для использования в TDD. С тех пор в PHP выросли SimpleTest, PHPUnit и даже PHPAR-бегун PEAR.
Но путь к экспертизе TDD был вымощен выбоинами и обходными путями, плохими вывесками и тупиками. Сегодня, по крайней мере, в PHP, TDD остается нечеткой идеей, которую многие разработчики не приняли. Даже когда они это делают, существует множество плохих практик, которые относительно распространены. Проблема с TDD в том, что он исторически связан с тестированием. Но в то же время это не методология тестирования. Этот запутанный момент отправил в дебри более одного разработчика.
Преодоление разрыва между тестированием и управляемой тестированием разработкой является даже предсказуемым процессом. Это выглядит примерно так:
- Разработчик проявляет любопытство и начинает писать модульные тесты для своего кода.
- По мере того, как количество тестов увеличивается, она испытывает внезапный всплеск уверенности, зная, что эти тесты следят за ней за любыми ошибками, думающими о том, чтобы проникнуть в приложение.
- Воодушевленный ее уверенностью, она может решить после прочтения некоторых статей TDD провести пробный заезд и посмотреть, как все пойдет.
- В момент вдохновения она понимает, что каждый тест в TDD выглядит как проверенный вариант использования или пример, документирующий, как использовать написанный код. Тесты как документация становятся реальным соображением.
- Поскольку тесты документируют примеры / варианты использования, очевидно, что они документируют открытый API / интерфейс классов. Это откровение, наконец, начинает освещать, как на самом деле работает TDD. Проектирование и тестирование общедоступных интерфейсов становится ключевым моментом для написания тестов при практике TDD. Тестирование государственных (частных и защищенных ресурсов) становится все более редким. Дизайн API вдруг очень важен.
- Наконец она осознает, что разработка общедоступного API с помощью тестов приравнивается к проверке и определению поведения объектов. После этого момента Эврика, ее опыт неоспорим. Объяснения TDD, которые она дает сейчас, сделаны в терминах «поведения» — волшебного слова B.
- Поведение имеет два элемента — открытый интерфейс и объектные взаимодействия. На этом этапе интерес к Mock Objects может возрасти, если он поддерживается используемой структурой модульного тестирования.
Здесь огромная проблема. На шаге 4 слишком многие разработчики либо: а) считают бред TDD, либо б) не совсем понимают, что тесты — это не тесты, а примеры желаемого поведения. Это процесс, который повторяется бесчисленное количество раз. Часто, если им повезет, кто-то на шаге 4 встретит кого-то еще на шаге 5+ и получит здоровый импульс от парного программирования или какой-либо другой формы совместной разработки.
Поведенческое развитие
Что происходит на шаге 4, чтобы заставить разработчиков терпеть неудачу в TDD — или, по крайней мере, заставить их не продвигаться дальше, кроме как путем постепенного самопознания? Многие пальцы недавно указали на T в TDD. TDD был разработан как процесс проектирования, но благодаря своему происхождению он стал процессом проектирования, наряженным в терминологию процесса тестирования. Проще говоря, это чертовски смущало всех — даже меня; Я провел некоторое время, борясь с TDD! Я знаю многих людей, которые все еще настаивают на том, чтобы TDD занимался тестированием — не имеет значения, как многие эксперты TDD говорят об обратном, после того, как его укоренили, трудно изгнать.
Поведенческая разработка (BDD) была сформулирована Дэном Нортом (ThoughtWorks, дом Мартина Фаулера) для продвижения TDD на следующий уровень. Как процесс, освобожденный от неправильных представлений о тестировании, с фидерами, связанными с проектированием, управляемым доменом, и разработкой, основанной на приемочном тестировании (две другие важные методологии TDD существуют параллельно с XP). Самое смешное в DDD и ATDD заключается в том, что они больше ориентированы на не разработчиков. В конце концов, клиенту на самом деле наплевать на юнит-тесты, но он передает им текстовые спецификации, данные FIT, пользовательские истории или макеты пользовательского интерфейса, и они будут счастливы как грязь (надеюсь!). Клиенты очень предпочитают материалы, которые они могут редактировать сами.
Таким образом, BDD больше фокусируется на трех уровнях. Первое — оно фокусируется на уточнении поведения. Это то, что вы будете делать, когда достигнете шага 6 на лестнице TDD, так что, возможно, просто начните делать это сейчас и обманывайте, приняв язык, независимый от тестов! Во-вторых, он объявляет документацию важным фактором при общении (будь то другие разработчики или заинтересованные лица / клиенты) — так что тесты и прикольный синтаксис PHP не очень удобочитаемы, а также грамматически правильный, понятный, простой текст (или близкий факсимиле в исходный код мира — предметно-ориентированный язык (DSL)). В-третьих, избегая любой ссылки на Тестирование языка BDD, вы попадаете в лучшую систему координат. Эти пункты неизбежно приводят к улучшению дизайна — золотой цели TDD в конце дня — и гораздо более легкому переводу от требований клиента к исходному коду.
По сути, BDD имеет три основных направления: поведение, документация и дизайн.
Возвращение Фокуса Ценности Бизнеса
TDD обычно рассматривается с точки зрения кода и программирования. У вас есть класс, его методы, и они должны быть разработаны путем написания тестов. В некотором смысле это не является недействительным — на уровне класса происходит много безумных разработок. Но TDD не является изолированной практикой. Есть также Domain-Driven Design (DDD) и то, что я написал несколько месяцев назад под названием Acceptance Testing (ATDD).
Обе эти две дисциплины ориентированы на клиента. Клиенты могут не только обсуждать критерии приемлемости и бизнес-логику, но они могут буквально определять и писать пользовательские истории и тестировать себя (возможно, с небольшой помощью и при участии разработчиков). Рассмотрим популярность FIT. Если вы когда-нибудь найдете бизнес-аналитика с ошибкой FIT, это прекрасно. Конечно, большинство из этих «тестов» действительно являются спецификациями. Что система должна делать с учетом A, когда я делаю B, а затем C, то есть пользовательские истории, разбитые на последовательность повторяющихся событий и ограничений. Поскольку эти пользовательские истории определяют ваши конечные цели, они также напрямую влияют на то, что вы делаете в процессах TDD и DDD. Если клиент говорит об учетных записях и клиентах, нетрудно догадаться, что вы будете TDD.Несколько моделей в дальнейшем, и вам нужно быть посредником между требованиями клиента к бизнес-правилам и кодом PHP.
Проблема в том, что разработчики и клиенты должны говорить на одном языке. Клиенты, вероятно, не пишут PHP, и вы, вероятно, не пишете тесты на простом английском языке, поэтому должно появиться что-то, что связывает их вместе. DDD называет это вездесущим языком.
«Модель предметной области может быть ядром общего языка для программного проекта. Модель представляет собой набор концепций, выстроенных в головах людей проекта, с терминами и взаимосвязями, которые отражают понимание предметной области. Эти термины и взаимосвязи обеспечивают семантика языка, который адаптирован к предметной области и в то же время достаточно точен для технического развития. Это важный шнур, который вплетает модель в процесс разработки и связывает ее с кодом ».
— Эванс 2003
TDD не имеет этого (я всегда могу упомянуть FIT еще раз, я полагаю). BDD с другой стороны делает. Если вы когда-нибудь задумывались над тем, почему BDD так одержимы такими понятиями, как предметно-ориентированные языки и беглые интерфейсы, основанные на грамматически правильном английском языке, то вам есть объяснение. Цель состоит в том, чтобы использовать форму спецификации, которая является: а) читаемой клиентом, б) редактируемой клиентом и в) способной к автоматическому выполнению.
TDD падает на этом препятствии. Модульные тесты написаны на родном языке программирования, не заботясь о грамматике английского языка
С точки зрения PHP это очень трудно продемонстрировать (пока нет доступной полнофункциональной среды BDD — хотя она еще и находится в стадии разработки), но в Java или Ruby она описана на двух уровнях. Во-первых, уровень низколежащего исходного кода, где TDD обычно наиболее заметен (мы посетим его позже), а во-вторых, на уровне DDD / ATDT, где пользовательские пользовательские истории переводятся в приемочные тесты, написанные как можно ближе к простому английскому языку. пока еще исполняемый.
Рассмотрим пример бизнес-логики:
Story: I have a savings and current account and I want
to transfer money from savings to current.
Scenario: savings account has enough money
Given my savings account balance is 100
And my current account balance is 50
When I transfer 20 from savings to current
Then my savings account balance should be 80
And my current account balance should be 70
Это на простом английском языке, и, как ни странно это выглядит, он может быть легко выполнимым (небольшой разбор и подходящий бегун). Если вы когда-либо использовали YAML, то, скорее всего, о чем-то говорите прямо сейчас. Дело в том, что этот формат довольно прост. У вас есть пользовательская история и набор сценариев, чтобы продемонстрировать, как пользовательская история может быть задана с точки зрения ценного поведения. Формат — простой текст (нулевой код), поэтому его легко редактировать не разработчиком. В то же время, он очень структурирован, поэтому его можно анализировать в коде и выполнять как приемочный тест.
Переход от тестирования к определению поведения
Однажды мы оставим сценарии для другой статьи, а сейчас вернемся к уровню исходного кода и поговорим о поведении. Как я уже отмечал ранее, если вы перейдете к шагу 6 по лестнице TDD, внезапно обнаружится, что TDD всегда лучше всего применять, описывая поведение объекта или группы объектов. Целью TDD является не создание обширного набора регрессионных тестов, а написание тестов в таком стиле, который описывает поведение, решает текущую проблему по частям и приводит к более простому, более очевидному решению с лучшим дизайном.
Это не значит, что тестирование неважно! Но это полезный результат применения подходящего процесса TDD / BDD, при этом он не является самоцелью.
Теперь поведение можно описать в форме: учитывая некоторый контекст, когда я делаю это, тогда он должен делать что-то интересное.
Возможно, более ранний пример сценария знаком здесь; Мы снова используем слова «Дано», «Когда», «Тогда» и «Должен». Эти слова на самом деле очень убедительны при анализе и описании поведения. Они предлагают очень структурированный способ мышления, который одинаково хорошо сочетается с TDD или BDD. Фактически, этот способ мышления чрезвычайно важен, поскольку он избегает многих разработчиков, пытающихся изучить TDD.
Проблема, с которой столкнулся до достижения шага 6 в нашей лестнице TDD, заключается в том, что TDD основан на не-TDD структуре — модульное тестирование не имеет структуры для формирования поведения и оставляет любую структурированную передовую практику для самостоятельного обнаружения (если повезет) , Именно для решения этой проблемы были разработаны специализированные среды разработки, управляемые поведением, для всего, от Java до Smalltalk. Такие структуры предлагают специальную структуру и средства для определения поведения с самого начала.
К сожалению, это часто рассматривается как дублирующая среда модульного тестирования, потому что результирующие спецификации не кажутся такими уж впечатляющими, пока вы не уловите ошибку поведения и не обнаружите признаки простого хорошо структурированного примера. Больше чем один человек рассматривал BDD и признал, что не использует названия объяснительных методов для объяснения теста, и это только одна привычка, которую BDD выталкивает с самого начала.
Важно то, что то, что является необязательным в структуре UT, применяется в структуре BDD. Если вы сами не достигнете шага 6, вы будете поражены его первой спецификацией. Это значительно уменьшает кривую обучения, присутствующую в TDD, почти сразу заставляет пользователей мыслить правильно и делает все это без использования слова «тест».
Давайте рассмотрим небольшой пример на уровне кода.
Мы получили запрос от какого-то парня, одетого как клингон, чтобы написать контрольное приложение для хищной птицы клингон. Он был немного расплывчат в деталях, но он записал некоторые основные идеи, в том числе на английском:
«Когда мой корабль выстрелит своими разрушителями, это уменьшит доступную мощность».
Это немного расплывчато, поэтому его подтолкнули немного дальше, чтобы определить более конкретный сценарий, который мы можем на самом деле указать.
«Учитывая мощность корабля в 100 мегаджоулей, когда мой корабль выстрелит своими разрушителями в ромуланскую собаку, тогда мощность составит 99 мегаджоулей».
Исходя из вышеизложенного, мы можем извлечь BDD-фреймворк (используя здесь PHPSpec, который я написал; disclaimer!) И установить кодирование этой скудной спецификации в исполняемый пример (spec), показывающий, как заставить объект демонстрировать такое поведение.
class DescribeKlingonBirdOfPrey extends PHPSpec_Context {
public function itShouldHaveReducedPowerAfterItsDisruptorsAreFired() {
$ship = new Ship_Klingon_BirdOfPrey;
$ship->power = 100;
$ship->fire();
$this->spec($ship->power)->should->be(99);
}
}
Класс может быть сохранен в файл с именем «KlingonBirdOfPreySpec.php» или файл с именем в качестве класса. Либо допустимы. Контекст здесь не совсем определим, поэтому сейчас мы просто воспользуемся именем класса. Позже вы можете исследовать, меняется ли поведение, если Корабль вновь введен в эксплуатацию, находится в аварийном состоянии или движется к куче отходов. Контекст может быть странным образом важен, если он каким-то образом влияет на все виды поведения в глобальном масштабе.
Несколько дней спустя странный парень (очень искренний) вернулся в дурном настроении и бьет себя в грудь перед ревом:
«Учитывая мощность корабля более 1 мегаджоуля, когда мощность достигает 1 мегаджоуля, мой корабль самоуничтожится автоматически (так что мой командир может умереть с честью! Капла!»).
Он спешит, но все равно …
Добавьте еще одну спецификацию для этого нового поведения. Если вы изобретательны, вы можете искать какое-либо дополнительное или даже противоположное поведение, например, можем ли мы предположить, что наш друг-клингон не намеревался самоуничтожиться, если мощность уже составляла 1 мегаджоуль? Как мы определяем минимальные уровни в любом случае? Некоторые вещи, чтобы обсудить, если он когда-нибудь придет стучит завтра …
class DescribeKlingonBirdOfPrey extends PHPSpec_Context {
public function itShouldHaveReducedPowerAfterItsDisruptorsAreFired() {
$ship = new Ship_Klingon_BirdOfPrey;
$ship->power = 100;
$ship->fire();
$this->spec($ship->power)->should->be(99);
}
public function itShouldSelfDestructWhenPowerHitsMinimumLevels() {
$ship = new Ship_Klingon_BirdOfPrey;
$ship->power = 2;
$ship->fire();
$this->spec($ship)->should->haveSelfDestructed(); // predicates to hasSelfDestructed()
}
public function itShouldNotSelfDestructIfPowerOriginallyStartsAtMinimumLevels() {
$ship = new Ship_Klingon_BirdOfPrey;
$ship->power = 1;
$ship->fire();
$this->spec($ship)->shouldNot->haveSelfDestructed(); // predicates to hasSelfDestructed()
}
}
Реализация:
class Ship_Klingon_BirdOfPrey {
public $power = 0;
private $_selfDestructed = false;
public function fire() {
$this->power--;
if ($this->power == 1) {
$this->_selfDestruct();
}
}
public function hasSelfDestructed() {
return $this->_selfDestructed;
}
private function _selfDestruct() {
$this->_selfDestructed = true;
}
}
Вы, вероятно, уже поняли смысл, всякий раз, когда обнаруживается ценная часть поведения, вы пишете пример этого. В PHPSpec, как и в любой среде BDD, ограничения класса сильно структурированы. Имена классов должны начинаться с «Describe», а имена методов спецификаций должны начинаться с «itShould». Ожидания объявляются с использованием специфичного для домена языка PHPSpec. Чтобы объяснить все это, вы можете запустить спецификации, используя программу запуска командной строки PHPSpec, используя опцию «—specdoc» или более короткую «-s»:
$ phpspec DescribeKlingonBirdOfPrey --specdoc
...
Finished in 0.004152936935 seconds
klingon bird of prey
-should have reduced power after its disruptors are fired
-should self destruct when power hits minimum levels
-should not self destruct if power originally starts at minimum levels
3 examples, 0 failures
Выходные данные specdoc — это просто список всех имен методов, разбитых на жизнеспособные предложения для каждого контекста. Давайте рассмотрим все это с точки зрения основного внимания BDD к поведению, документации и дизайну.
Поведение; Написанные спецификации являются в основном примерами поведения, которое мы считаем ценным и которое мы хотим, чтобы наш объект (корабль) демонстрировал. Во многих отношениях они очень похожи на модульные тесты, в которых рекомендуется использовать несколько ключевых передовых методов, главным из которых является одно ожидание на пример. Проблема многих ожиданий в любом из примеров заключается в том, что это может означать, что ваше поведение недостаточно детально. Это может быть проблематично, если агрегированные поведения являются взаимоисключающими, и, не идентифицируя их отдельно, вы не можете идентифицировать проблемы, характерные для одной, а не для обеих.
Документация; Спецификации являются примерами, они озаглавлены именем метода, который описывает поведение, которое демонстрирует пример, все спецификации могут быть выведены в виде простого текста для чтения / просмотра любым лицом вне группы разработчиков. Таким образом, имена методов можно редактировать время от времени, чтобы они оставались конкретными и сфокусированными. Примеры очень сфокусированы на одном конкретном поведении, только одна спецификация определена для каждого примера. Ожидание использует специфичный для предметной области язык, который тщательно отслеживает грамматически правильный английский.
Дизайн; Теперь, когда мы больше не тестируем, мы можем реализовать поведение. Это не очень отличается от TDD, на самом деле все иначе в том, как мы попали сюда, указав ценное поведение, задокументировав его, и только потом рассматривая его реализацию. Как и в случае с TDD, каждый дополнительный шаг упрощает проблему, которую мы стремимся решить, поскольку мы сталкиваемся с одной ее частью за раз. Мы должны постоянно проводить рефакторинг, чтобы сохранить код в чистоте от обычных запахов кода. Сосредоточение внимания на поведении упрощает вычет дополнительных необходимых видов поведения. Проектирование должно привести к созданию более простого и элегантного надежного кода, который легче поддерживать и, как правило, приводит к меньшему количеству ошибок.
BDD с PHPUnit / SimpleTest?
Важный аспект того, можете ли вы позаимствовать поощряемую BDD структуру и методы и применять их в рамках модульного тестирования, зависит от нескольких факторов.
Главное, насколько хорошо фреймворк поддерживает Mock Objects. BDD — это процесс «вовне», поскольку поведение проявляется на внешних слоях любой системы, а реализация начинается с внешней поверхности (например, хищная птица клингон) и продвигается все глубже (например, управление огнем с помощью системы оружия, разрушителя). Модель и технические характеристики).
Mock Objects идеально подходят для этого подхода — после определения внешнего поведения и реализации реализации, Mock Objects может макетировать более глубокие объекты, которые вы хотите добавить, но которые еще не существуют. Поскольку Mock Object описывает взаимодействия объектов — они в конечном итоге создают открытый API следующего уровня и ожидаемые возвращаемые значения, открывая дверь для нового набора спецификаций на этой основе для их реализации (или нет — некоторые новые объекты могут фактически подкрасться к поверхность, например, на поверхности раздела). Здесь важно отметить, что заглушки не способны на одно и то же умение — заглушки не способны содержать ожидания и не знают о взаимодействии объектов (т. Е. Как один объект вызывает и использует другой, порядок вызовов, количество вызовов, действительный аргумент). списки и т. д.).
PHPUnit в настоящее время имеет довольно слабую реализацию Mock Object. Легко критиковать то, что, в конце концов, является достаточно новой функцией, и есть толчок к созданию независимой платформы Mock Object в PHP, чтобы сократить разрыв. SimpleTest, с другой стороны, уже много лет отлично поддерживает Mock Object, в то время как PHPT не поддерживает Mock Object с полной остановкой (еще одно оправдание для независимой платформы Mock Object!).
Вторым фактором является включение структуры спецификации в стиле BDD. Ни рамки модульного тестирования в настоящее время способны на простом английском стиль домен языка специфического для спецификации , так как они следуют за BDD оспаривал условность испытаний «утверждения» , написанные исключительно для синтаксиса PHP, хотя недавнее сообщение в блоге Рафаэля Столта показывают некоторое сходство возможно. PHPUnit также позволяет абстрагировать имена методов от включения слова «тест», хотя все еще требует, чтобы такие методы были помечены аннотацией «@test». Тем не менее, если вы хотите применить BDD к существующему проекту PHPUnit, эта возможность требует более тщательного изучения.
Третий фактор — это реализация поддержки пользовательских историй и сценариев, которую не имеет и не планирует ни один из модульных тестов. Примечательно, что ни PHPSpec сейчас тоже! Однажды скоро … Это довольно важный аспект целей BDD по преодолению разрыва между разработчиками и клиентами, который TDD явно не интегрирует. Структура спецификации уровня кода — это только одна сторона уравнения BDD. Наш пример «Хищная птица», поскольку он углубляется в своего рода бизнес-логику, может лучше подходить к текстовому формату простого текста, который клингон мог бы редактировать самостоятельно.
Вывод
Надеемся, что это введение в развитие, управляемое поведением, было и проницательным, и полезным. Если у вас есть какие-либо вопросы, пожалуйста, оставьте комментарий ниже, и я даже ограничу свой ответ, поэтому он немного короче, чем эта статья!
Copyright (c) 2007 Падрайк Брейди
Этот учебник впервые появился на Zend’s DevZone и используется с разрешения.