Я читал про BDD, и понял одну вещь: BDD это блаблабла блабла бла. Нету у него нормального определения. Вот, например, написано:
BDD совмещает в себе основные техники и практики TDD и идеи DDD для того чтобы предоставить программистам, тестерам, аналитикам и менеджерам общий процесс взаимодействия по ходу разработки ПО.
Все понятно? Мне — ничего. Поэтому я расскажу, что мы делаем и зачем, из того, что может иметь отношение к BDD.
Приступая к планированию фичи, мы описываем ее в терминах «поведения системы», например:
Given Jira Issue with several bugs
When all that bugs are fixed
Then Issue is closed
Мы это пишем прямо на доске, по ходу планинга, вместе с Product Owner, в таком объеме, чтобы было понятно заказчику (product owner) и тестировщикам, и разработчикам: что же нам надо сделать, и как это тестировать. По сути, это требования/тесты, которые записываются в таком «кратком» (и заметьте, не четком) виде, всей командой, не тратя на это много времени. И больше никакой документации, только слова. Понимаю, это довольно «дикий» случай, и не всегда так все просто, но в любом случае, мы стараемся выделить бизнес проблему, и записать наиболее краткое решение, не вдаваясь в детали. Детали идут потом, они могут быть оговорены на планинге или выясняться уже по ходу разработки с PO (да, у нас заказчик быстро отвечает на вопросы), но они не документируются и не тестируются и никто не будет их читать.
Только не путайте описание поведения и системную документацию. Поведение может (и будет) меняться. Сегодня эта копка делает одно, завтра уже другое. Приведенное описание поведения устаревает с каждым спринтом/итерацией, на него нельзя полагаться и выяснить то, как же устроена фича спустя некоторое время. Поэтому существует системная документация, где описывается весь актуальный функционал. Нет, у нас такой документации нет, у нас все в голове и коде. И пока, лично я, не испытывал неудобств. А что будет если голова попадет под автобус? Я тоже задавал такой вопрос. Мне сказали: будет плохо, но мы не верим что документация спасет, вряд ли ее будут всю читать, и вряд ли она будет столь актуальна и сможет дать ответы так же, как голова.
Теперь про тесты. Есть замечательная штука — Cucumber (и еще похожее: concordion). Он позволяет сопоставить каждой проcтой строчке текста (такой как: «all that bugs are fixed») свой метод на .Net,Java,Ruby и других языках программирования, и даже позволяет выделить переменные с помощью регулярных выражений, чтобы можно было повторно использовать методы.
И благодаря cucumber, все тесты имеют отличный читаемый вид, они отражают текущее состояние системы и могут являться своебразной системной документацией, а к тому же они уже написаны на планинге!
Все вроде замечательно, но мы не используем Cucumber, потому что:
- Наши тестовые методы, (вренее тестовое API, а не те методы, что с аннотацией @
Test
), принимают на вход сложные значения/объекты (Map), которые трудно записать в простой текстовой форме и распарсить с помощью регулярных выражений. - Тестовые методы возвращают и используют переменные, например, возвращают Primary Key созданных сущностей, потом этот PK используется в других методах. И хотя поддержка «переменных» возможна в протом текстовом языке, это все равно неудобно и является большим минусом.
- Мы используем выражения в качестве перменных. Например, разные builders, чтобы задать дату «прошлого воскресенья» или «следующего понедельника». Кстати, мы генерируем произвольные/рандомные тестовые данные везде, где это только возможно, и все проверки делаем исходя из них, поэтому мы часто используем выражения и переменные.
- Контекст. У нас большинство действий привязано к текущей сессии пользователя. Когда понадобилось создать две параллельные сессии, это не вызвало никаких проблем. В случае cucumber, пришлось бы выделять абсолютно новые фразы/методы, которые работали бы, как с указанием сессии, так и без.
- Проверки данных. Для этого мы используем hamcrest, и с помощью всего нескольких матчеров, мы можем записывать кучу разнообразных и сложных условий. С cucumber, для этого приходится выделять отдельные методы с одной строчкой кода на каждое сочетание матчеров. А в худшем случае, по одному методу на каждое сочетние матчеров и проверяемого значения.
- Рефакторинг. Я рефакторю тесты значительно больше чем код, (ну так получается ) и IDE мне в этом очень сильно помогает, автоматизируя довольно сложные действия. А как рефакторить простой текстовый язык? А что будет, если раньше был один метод, а потом код поменялся и нужно его разделить на два метода, с разными переменными или разными названиями. Можно ли это сделать автоматически, по всей базе простотектового кода?
Вместо того, чтобы тратить усилия на разработку простотекстовых сценариев с помощью Cucumber, мы пишем их на Java (как и остальной проект) и тратим усилия на то, чтобы код был «чистым» и читаемым.
Вышесказанное не является критикой Cucumber, это отличная и нужная штука, просто в нашем случае она создала бы больше проблем чем решений (по-моему субъективному мнению).
Рассмотрим пример кода, соответcтвующий, приведенному выше, описанию поведения:
String issueId = issueHelper().createIssue();
List bugIds = new ArrayList();
int numberOfBugs = Random.getNumberBetween(1, 5);
while(numberOfBugs>0){
bugIds.add(issueHelper().createBug(issueId));
numberOfBugs--;
}
for(String bugId : bugIds){
navigator().goto(bugId);
workflowHelper().doAction("fixed");
}
navigator().goto(issueId);
assertThat(getField("status"),is("Closed"));
При выполнении теста, этот код выводит следующее:
Создать Issue с произвольными параметрами (Ключ нового issue HR-17)
Создать Bug на issue HR-17 (Ключ нового бага BG-26)
Создать Bug на issue HR-17 (Ключ нового бага BG-27)
Перейти на страницу по ключу BG-26
Выполнить действие fixed
Перейти на страницу по ключу BG-27
Выполнить действие fixed
Перейти на страницу по ключу HR-17
Проверить что Значение поля status is «Closed»
Такой тестовый вывод очень похож на cucumber сценарий в моем предыдущем проекте, и может читаться аналитиками/менеджерами (я проверял). Они могут даже проводить своеобразное «code review» того, на сколько соответствует лог первоначальному BDD описанию.
Потому у нас и получается «BDD наоборот».
Иногда аналитикам или разработчикам по ходу code review может что-то не понравится, тогда мы можем всего за пару кликов отрефакторить код, например объединить методы «Перейти на страницу по ключу» и «Выполнить действие» в один метод.
Вы могли заметить, что в коде теста нет никаких комментариев или специальных вызовов, которые могли бы выводить такой лог, и это не случайно.
Весь вывод лога сделан на основе технологии AspectJ, которая позволяет перехватывать любые вызовы методов и оборачивать их своим кодам. В нашем случае, мы выводим в лог описание метода из JavaDoc, подставляя в него значения параметров, с которыми произошел вызов и, если возможно, то возвращаемое методом значение.
Мы делаем то же самое, что в технологии Cucumber, с точностью до наоборот. В Сucumber, мы каждому методу сопоставляем регулярное выражение (или шаблон), согласно которому мы будем выделять переменные из текстовой строчки, у нас же, мы сопоставляем шаблон, в который мы будем подставлять переменные в выводимую строчку лога.
Какой в этом всем смысл?
С точки зрения BDD, документации и совместной работы всех вместе, в обнимку — не знаю.
Но я знаю точно, в красивом логе тестов, смысл есть:
- Это сильно способствует улучшению качества кода тестов. Правда, очень.
Но только в том случае, если Вы логируете описания вызовов методов, а не принудительно выводите:log("I did something")
. Тогда Вам приходится выделять методы, соответствующие бизнес понятиям, а потом, глядишь, и кода меньше дублируется, и структурирован он лучше. И следить за этим может аналитик, без опыта программирования, который скажет — ваш код оперирует бизнес понятиями, или какие-то непонятные кнопки нажимает, т.е. скатился куда-то, на нижний уровень интерфейсных галочек. Соответственно тесты короче и понятнее. - Это позволяет понять, почему тест «упал».
Причем мы можем логгировать как вызовы методов первого уровня, т.е. те, что написаны в тестовом сценарии, так и вызовы внутренних методов. И не просто вызовы, а со всеми параметрами и возвращенными значениями. - Иногда проще понять лог, чем код, чтоб разобраться, что же тест делает.
Я сам в шоке от этого, вроде стараешься, пишешь красиво, рефакторишь, а все равно, через какое-то время код не понятный, а «русский» лог помогает. Ну, значит, есть еще куда стараться и улучшать код. Иногда, кстати, помогает посмотреть на BDD описание, которое мы с доски переписываем в аннотацию к каждому тесту. Эти описания идут в тестовый отчет, потому что они все-таки компактнее и понятнее. Это еще раз меня убеждает, что было правильно разделять такие описания и тестовый сценарий. - Это заставляет писать JavaDoc к тестовому API, хоть какой-то. А это, в свою очередь, упрощает написание тестов, особенно если их пишет не один человек. Интересно, а при использовании Сucumber, можно по Ctrl+пробелу получить подсказки доступных методов, с описанием параметров? Пошел на<Ctrl+Space>
- Можно отвлечься и поизучать Aspect Oriented Programming. В качестве идеи на будущее: хочется сделать так, чтобы при вызове
doSomething(getSomething())
в логdoSomething()
подставлялся не просто результат методаgetSomething()
(например просто 5), а с описанием из JavaDoc методаgetSomething()
, чтобы было понятно, что означает этот результат (5) и откуда он взялся. Я этим обязательно займусь, когда меня в очередной раз «все задолбает».
Если кому-то интересны технические детали: как парсить JavaDoc, как работать c AspectJ, напишите коммент, и я подготовлю об этом отдельный пост, в котором также расскажу, как можно в JavaDoc запихнуть таблицу с тестовыми данными в простой текстовой форме (скопипастив ее из Excel), и сделать так, чтобы тестовый метод вызывался для каждой строчки этой таблицы — это, как раз то, как работает Cucumber, аналитикам это нравится, я же не вижу в этом прелести. А вы что думаете?
Мораль сей басни такова: экспериментируйте, пишите тесты, и лог вам в помощь.
Автор: susliks