Specification By Example – BDD для прагматиков
На Хабре довольно много упоминаний о BDD. К сожалению, статьи, которые я читал, так и не дали мне ответа на вопрос «а зачем мне все это нужно?» Ответ пришел с неожиданной стороны. Когда я всерьез занялся вопросом автоматизации приемочного тестирования, мне под руку попалась книга Gojko Adzic (не уверен в транскрипции, поэтому не стал переводить имя автора) Specification By Example.
Читая ее, я не уставал удивляться: каждая новая глава описывала шишки, которые я набивал на своем личном опыте, и предлагала решения аналогичные или лучшие, чем те, к которым я приходил сам методом проб и ошибок.
Эта статья – первая в цикле «BDD для прагматиков». В ней описаны ключевые элементы наиболее эффективного, на мой взгляд, процесса разработки коммерческого ПО в современных условиях. Два продолжения будут посвящены работе со SpecFlow и автоматизации приемочного тестирования.
Чтобы продукт «взлетел» в условиях современного IT-рынка, нам, разработчикам, нужно достичь две цели. Сделать правильный продукт (именно то, что рынок захочет купить) и сделать продукт правильно (без спагетти-кода, костылей и прочего технического долга).
Правильный продукт и продукт, сделанный правильно, – не одно и то же!
Невыполнение первого условия приведет к финансовому краху, а значит с высокой вероятностью к закрытию проекта. Невыполнение второго – к тому, что в один прекрасный день мы обнаружим себя по пояс в г… коде. Наверное, не нужно объяснять все прелести поддержки legacy-систем без тестов и спецификации.
Specification By Example – это процесс, позволяющий регулярно добиваться выполнения обоих пунктов. В основе процесса лежат agile, tdd, bdd, continuous integration и автоматизация тестирования.
Ключевые элементы Specification By Example:
- Выделяйте главное (deriving scope from goals)
- Составляйте спецификацию совместно (specifying collaboratively)
- Приводите примеры (illustrating using examples)
- Очищайте спецификацию (refining the specification)
- Внедряйте автоматизацию тестирования без изменения спецификации (automating validation without changing specification)
- Встраивайте выполнение тестов в процесс сборки и развивайте документацию (validating frequently, evolving a documentation system)
Выделяйте главное (deriving scope from goals)
Подавляющее меньшинство людей способно сформулировать, что им действительно нужно. В большинстве случаев вы столкнетесь с тем, что клиент сформулирует требования не в виде «у меня есть цель, как мы можем ее достичь?», а: «сделайте мне вундервафлю, как у соседа, но с перламутровыми пуговицами». Клиент будет рассказывать вам, как он видит решение проблемы, и погружаться все глубже в детали реализации.
Здесь кроется первая и самая главная ошибка. Обычно одну и ту же проблему можно решить несколькими способами, а в виде требований нам приходит уже запрос на реализацию одного из возможных решений. Но не все йогурты решения одинаково полезны эффективны.
Наша задача, как инженеров, предложить решение лучше и дешевле, чем просит клиент. Этот принцип сформулировал еще Генри Форд:
«Если бы я спросил людей, чего они хотят, они бы попросили более быструю лошадь».
Истребитель F-16 – один из самых успешных истребителей за всю историю армии США. Первоначальные требования к разработке были достичь скорости Mach 2-2.5, что в комплексе с другими требованиями делало разработку и производство такого самолета очень дорогими. Harry Hillaker – ведущий конструктор F-16 – уточнил, почему так важно это требование скорости, и получил ответ: «истребитель должен смотаться, если станет действительно жарко». Hillaker предложил альтернативное решение и спроектировал истребитель, превосходящий на тот момент другие по маневренности.
Прошло более 30 лет, а эти истребители все еще производят. 4400 самолетов продано в 25 стран мира. Именно на F-16 летают в большинстве голливудских фильмов: от «Дня независимости» до «Трансформеров»… F-16 до сих пор не может развивать скорость выше, чем Mach 2.
F-16 оказался таким успешным, потому что инженер предложил решение лучше и дешевле, чем просил клиент.
Обращайте внимание на цели, а не переходите сразу к решению. Достичь цели можно разными способами.
Как выделить главное
Альберт Эйнштейн однажды сказал:
«Формулирование проблемы зачастую важнее ее решения».
Прежде чем кодировать, составьте спецификацию. Да, мы – разработчики – не любим бумажки, формализм и многостраничные талмуды, которые никто в итоге не читает. Но задумайтесь, как часто вы проклинали того, кто составлял требования и приемочные критерии. Возьмите на себя ответственность и составьте спецификацию сами в том виде, в котором вам будет удобно работать.
Вот несколько примеров неудачных пунктов, попавших в спецификацию:
- Все страницы должны отображаться за 0.1 секунды. Слово «все» следует избегать в спецификации в принципе. Да, главная страница должна открываться настолько быстро, насколько возможно. Но вы потратите недели или месяцы, пытаясь загнать в эти рамки сводный отчет, который запускают раз в год. Такие редкие и ресурсоемкие операции могут выполняться долго. Ничего страшного в этом нет.
- User-interface должен выглядеть в стиле OSX. Мода породила тысячи сайтов с заменой родных контролов, а вместе с ними боль тысяч верстальщиков и web-разработчиков. Пусть интерфейс выглядит в стиле OSX на маке и в браузере Сафари. В других системах оставьте родные элементы управления.
- На главной странице веб-сайта должен быть флеш-баннер. Возможно, для этого баннера вообще не нужен флеш, и все можно сделать средствами html/css/javascript, а аналогичный модуль у вас есть уже «из коробки». Избегайте чрезмерного уточнения технологических аспектов в спецификации.
Используйте user-stories
User-stories – один из лучших способов понять, что на самом деле нужно. User-stories могут оформляться немного по-разному, но обязательно должны содержать 3 пункта:
In order to — зачем?
As a — кто?
I want — что?
Проработав каждую такую историю, вы сможете понять, действительно ли нужно то, о чем вас просят, или на самом деле есть лучшее решение проблемы, про которое никто просто не подумал. Если I want уже сформулирован, а As a и In order to – нет, – это повод задуматься. Возможно, вы собираетесь разработать никому не нужный функционал.
Вот так могут выглядеть user-stories рассылки спама программы повышения лояльности интернет-магазина (прошу прощения у читателей, плохо знающих английский язык, но я убежден, что документация должна быть на английском):
- In order to be able to do direct marketing of products to existing customers,
As a marketing manager
I want customers to register personal details by joining a VIP program. - In order to entice existing customers to register for the VIP program,
As a marketing manager
I want the system to offer free delivery on certain items to VIP customers. - In order to save money,
As an existing customer
I want to receive information on available special offers.
Составляйте спецификацию совместно (specifying collaboratively)
Спецификация, составленная единолично, без команды – вторая большая ошибка и потенциальное пространство для возникновения непонимания и последующих правок. Вместо того чтобы полагаться только на одного специалиста, привлеките к составлению спецификации всю команду. Если вы работаете по скраму, пленинг-митинг – отличное время, чтобы сделать это.
Разработчики лучше понимают инфраструктуру и знают технологии, которые можно применить для решения проблемы. QA-специалисты укажут, в каких местах могут возникнуть ошибки. Product-owner – эксперт в предметной области. Вся эта информация полезна для составления спецификации. Совместная работа позволяет:
- Лучше разобраться с проблемой и найти оптимальное решение
- Сделать так, чтобы вся команда понимала новые требования одинаково
- Вовлечь всех участников в процесс
На этапе планирования лучше подключить всю команду. После того, как основной фронт работ понятен, более эффективна парная работа или небольшие совещания на 3-4 человека, чтобы прояснить сложные моменты.
Приводите примеры (illustrating using examples)
Натуральные языки играют с нами злую шутку. Они оставляют простор для толкований, непонимания, а иногда требуют знания предметной области и/или специфического жаргона, чтобы понять, о чем идет речь. Небольшое недопонимание может привести к срывам сроков и большому количеству правок или даже переписыванию целых модулей. Вместо того чтобы долго и мучительно формулировать требования, опишите их с помощью примеров. Вместе с клиентом вы сможете найти ключевые примеры. Помощь разработчиков и тестировщиков просто неоценима в данном случае, потому что они смогут заранее указать на потенциальные проблемные области и технические ограничения.
Вот как может выглядеть процесс совместной работы над спецификацией. Для примера возьмем интернет-магазин «Рога и Копыта».
- Варвара – представитель клиента
- Вася – разработчик
- Оля – QA
Варвара: Итак, мы заключили договор с издательством «ABC Press» о том, что мы будем бесплатно доставлять книги издательства, купленные у нас.
Вася: По всей России?
Варвара: Нет, что вы. Только по Москве.
Оля: А у нас в админке везде заполнено правильное издательство? Варя, ты можешь дать нам полный список книг, боюсь, можем напутать, в БД поле «издательство» — это строка.
Варвара: Да, конечно, там не так много книг.
Вася: А сколько книг мы можем доставить бесплатно? У нас есть какие-то ограничения: 5-10? Что произойдет, если в корзине будут книги других издательств?
Варвара: Это не важно, пока в корзине есть хотя бы одна книга, доставка бесплатно.
Вася: Хорошо, а что произойдет, если клиент закажет книгу и холодильник? Такая доставка будет стоить дорого…
Варвара: Да, об этом мы как-то не подумали. Давайте, я запишу и уточню, и мы обсудим это через неделю, информации уже достаточно, чтобы начать работу?
Вася: Конечно, начну набрасывать архитектуру.
Оля: А я проверю, что там с издательствами.
Через 3 дня.
Варвара (по телефону): Мы пообщались и решили, что бесплатная доставка будет доступна только для книг. Если в корзине есть что-то еще – только обычная доставка. И еще мы решили ограничить сверху количество книг – не более 10.
Вася: Ок.
В результате, мы получим ключевые примеры:
Customer type | Cart contents | Delivery |
---|---|---|
VIP | 1 book | Free |
VIP | 10 books | Free |
VIP | 11 books | Standard |
Regular | 10 books | Standard |
VIP | 5 washing machines | Standard |
VIP | 1 washing machine | 5 books Standard |
Очищайте спецификацию (refining the specification)
В ходе совместного обсуждения вы сможете достичь общего видения цели. Можно сравнить предыдущий этап с тем, что вы добыли алмаз. Алмаз сам по себе довольно ценен, но после обработки его ценность возрастет многократно.
В ходе обсуждения каждый член команды будет тянуть одеяло на себя.:
- Представители бизнеса склонны уделять слишком много внимания UI (потому что они не специалисты в технической области, UI – это единственное, что они могут «потрогать руками»).
- Разработчики склонны углубляться в детали реализации: какой фреймворк выбрать и какую технологию применить.
- Тестировщики будут параноидально искать ошибки и уязвимости.
Таким образом, первоначальный вариант наверняка получится противоречивым и избыточным.
Цель этого этапа – отделить зерна от плевел и предоставить нужное количество деталей. Спецификация должна стать единым документом для:
- Приемочных критериев
- Приемочных тестов
- Будущих регрессионных тестов
Для того чтобы максимально снизить шансы недопонимания, мы запишем сценарии в терминах Given When Then. Если вы еще не знакомы с этой формой записи, вспомните уроки математики в школе: Дано, Найти, Решение. Тут примерно то же самое.
Given — первоначальный контекст (предусловие)
When — событие (что является триггером сценария)
Then — результат, который мы хотим получить
Для нашего примера с книгами это будет выглядеть так:
Feature: Free delivery
In order to save money
As a VIP customer
I want the system to offer free delivery on certain items to me
Scenario: Free delivery
Given I am a VIP customer
And I am on product detail page
And There are only books in my shopping cart
And There are <= 10 books in my shopping cart
And I have added 'ABC Press' book to my shopping cart
When I press 'Go to checkout' button
And I have chosen 'Moscow' in 'Ship To' dropdown
Then I can choose free delivery
Подобная форма записи с одной стороны остается читаемой для простых смертных не технических специалистов (менеджеров, представителей клиента, бизнесс-аналитиков), а с другой – достаточно строга, для того чтобы избежать неоднозначности.
Мы можем немного изменить сценарий, чтобы обрадовать QA.
Scenario Outline: Free delivery
Given I am a VIP customer
And I am on product detail page
And There are only books in my shopping cart
And There are <bookQuantity> books in my shopping cart
And I have added 'ABC Press' book to my shopping cart
When I press 'Go to checkout' button
And I have chosen 'Moscow' in 'Ship To' dropdown
Then <deliveryType> is available
Examples:
|bookQuantity|deliveryType|
|5 |Free |
|10|Free |
|11|Standard |
В этом случае сценарий можно использовать как список тест-кейсов.
Для написания этого сценария я воспользовался SpecFlow – решением для .NET-платформы. Аналогичные инструменты есть для Java, Ruby, PHP.
Я не зря акцентирую внимание на инструменте. Как именно писать тесты с помощью Given When Then – слишком обширная тема, которую мы рассмотрим в следующей статье, а пока ограничимся базовой информацией. Тесты создаются в декларативном стиле, с помощью «шагов». Обратите внимание, что параметры из таблицы Examples будут переданы в аргументы методов.
namespace ProjectName.Specification
{
public class FreeDeliverySteps
{
[Given("I am a VIP customer")]
public void GivenIAmVipCustomer()
{
throw new NotImplementedException();
}
[Given("I am on product detail page")]
public void GivenIAmOnProductDetailPage()
{
throw new NotImplementedException();
}
[Given("I have added 'ABC Press' book to my shopping cart")]
public void GivenIHaveAddedAbcPressBookToMyShoppingCart()
{
throw new NotImplementedException();
}
[Given("There are only books in my shopping cart")]
public void GivenThereAreOnlyBooksInMyShoppingCart()
{
throw new NotImplementedException();
}
[Given("There are (.*) books in my shopping cart")]
public void GivenThereAreBookQuantityBooksInMyShoppingCart(int bookQuantity)
{
throw new NotImplementedException();
}
[When("I press 'Go to checkout' button")]
public void WhenIPressGoToCheckoutButton()
{
throw new NotImplementedException();
}
[When("And I have chosen 'Moscow' in 'Ship To' dropdown")]
public void WhenIHaveChosenMoscowInShipToDropdown ()
{
throw new NotImplementedException();
}
[Then("Then (.*) is available")]
public void ThenDeliveryTypeIsAvailable(string deliveryType)
{
throw new NotImplementedException();
}
}
}
Более подробно про BDD можно почитать на сайте родоначальника этой парадигмы.
Внедряйте автоматизацию тестирования без изменения спецификации (automating validation without changing specification)
Законченная спецификация послужит вам одновременно техническим заданием на разработку функционала и тестовыми сценариями для проверки правильности выполнения работы. Не откладывайте автоматизацию тестирования «на потом». Тесты позволят обнаружить ошибки на раннем этапе разработки, а значит вам не придется переделывать функционал снова и снова.
Если Когда в будущем требования изменятся, вам не нужно будет разбираться, какие тесты больше не нужны. Раз требования изменились, значит спецификацию тоже надо изменить.
Тесты, покрывавшие часть кода функционала, который «попал под нож», либо станут красными, либо вообще перестанут выполняться (перейдут в статус pending), и процесс надо будет повторить сначала: написать тесты, переписать код, проверить, что все тесты проходят.
При такой организации работы Вы будете изменять спецификацию и тесты в одном месте. То есть сама спецификация станет исполняемой (executable).
Встраивайте выполнение тестов в процесс сборки и развивайте документацию (validating frequently, evolving a documentation system)
О пользе Continuous Integration на Хабре уже написано немало. Мы рассмотрим эту практику со своей колокольни. Итак, у нас есть исполняемая спецификация (спецификация с примерами и связанными с ней автоматизированными тестами). К сожалению, большая часть команды до сих пор не может «пощупать» результат проделанной работы. Не будут же менеджеры устанавливать IDE, обвешанную плагинами, и разворачивать всю систему на своей машине.
Нужно встроить выполнение тестов в вашу систему сборки (если билд-сервера в организации нет, может быть это повод, для того чтобы он появился?)
Как только тесты будут выполняться регулярно, вы заметите, что автоматизированные тесты – это самый надежный источник информации о состоянии дел в проекте на данный момент. На графике ниже пример успешной итерации разработки. В первый день написана малая часть приемочных тестов и все они красные (что понятно, ведь никакого функционала у нас еще нет). Постепенно количество тестов возрастает, чтобы покрыть весь скоуп разработки. Параллельно количество зеленых тестов растет – функционал начали разрабатывать.
Количество зеленых тестов – единственный надежный критерий оценки того, что уже сделано и что предстоит.
К сожалению, практика показывает, что разработчики, пусть и из благих побуждений, могут рапортовать «да, да, завтра все заработает» месяцами. График не будет врать: фича готова, когда все ее тесты зеленые.
Слишком много красных тестов, а релиз скоро? Скорее всего, нужно принять меры, иначе функционал не будет готов в срок с должным уровнем качества.
С развитием продукта спецификация будет расти, а требования – меняться вслед за требованиями рынка. Даже такую спецификацию придется поддерживать и структурировать, чтобы вся информация была быстро доступна. Я готов заплатить эту цену за уверенность в том, что каждый следующий релиз пройдет без срывов сроков, сверхурочной работы, и продукт будет работать как часы.
К чему это я
Мир вокруг нас меняется очень быстро. И сейчас мир бизнеса и разработки повернулись друг к другу если не лицом, то хотя-бы в пол-оборота. Но пропасть коммуникации до сих пор не преодолена окончательно.
Я начал внедрять практики Specification By Example значительно раньше, чем прочитал книгу. Многие из описанных рекомендаций приходят с опытом, многое можно почерпнуть из других источников. Действительно ценно то, что Gojko Adzic смог систематизировать эту информацию и предложить процесс.
Мой опыт полностью совпадает с опытом автора: проекты, на которых внедряются принципы Specification By Example, разрабатываются быстрее, а количество багов и правок уменьшается.
Я надеюсь, что статья будет полезна молодым командам, которые находятся в поиске подходящего процесса.
Ссылки
- Сайт книги: specificationbyexample.com
- Доклад Николая Алименкова, через который я познакомился с книгой (Thucydides — приемочные тесты нового поколения): video.yandex.ru/users/ya-events/view/809/?cauthor=ya-events&cid=75
Автор: marshinov