BDD является логическим следующим шагом по сравнению с тест-ориентированной разработкой (TDD).
Поведенческое развитие
По сути, BDD — это способ удовлетворения требований. Но не только какие-то требования, исполняемые! С помощью BDD вы пишете сценарии в формате, который можно запустить с программным обеспечением, чтобы убедиться, что программное обеспечение ведет себя так, как нужно.
Сценарии
Сценарии написаны в формате « Дано, Когда, Тогда» , также известном как Огурец :
1
2
3
4
5
|
Given the ATM has $250 And my balance is $200 When I withdraw $150 Then the ATM has $100 And my balance is $50 |
Данные указывают на начальный контекст, когда указывает на возникновение интересного события, а затем утверждает ожидаемый результат. И может использоваться вместо повторяющегося ключевого слова, чтобы сделать сценарий более читабельным.
Given / When / Then — очень мощная идиома , которая позволяет описать практически любое требование. Сценарии в этом формате также легко анализируются, поэтому мы можем автоматически запускать их.
Сценарии BDD отлично подходят для разработчиков, так как они обеспечивают быструю и однозначную обратную связь о том, закончена ли история . Могут быть предоставлены не только основной сценарий успеха, но также альтернативные сценарии и сценарии исключений , а также случаи злоупотреблений . Последнее требует, чтобы Владелец продукта сотрудничал не только с тестировщиками и разработчиками, но и со специалистами по безопасности. Преимущество состоит в том, что становится легче управлять требованиями безопасности .
Хотя BDD на самом деле посвящен процессу совместной работы, а не инструментам, я сосредоточусь на инструментах до конца этой статьи. Помните, что инструменты никогда не спасут вас, в то время как общение и сотрудничество могут . С учетом этого предостережения давайте начнем реализацию BDD с помощью некоторых инструментов с открытым исходным кодом.
JBehave
JBehave — это инструмент BDD для Java. Он анализирует сценарии из файлов истории, сопоставляет их с кодом Java, запускает их с помощью тестов JUnit и генерирует отчеты.
JUnit
Вот как мы запускаем наши истории, используя JUnit:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
@RunWith (AnnotatedEmbedderRunner. class ) @UsingEmbedder (embedder = Embedder. class , generateViewAfterStories = true , ignoreFailureInStories = true , ignoreFailureInView = false , verboseFailures = true ) @UsingSteps (instances = { NgisRestSteps. class }) public class StoriesTest extends JUnitStories { @Override protected List<String> storyPaths() { return new StoryFinder().findPaths( CodeLocations.codeLocationFromClass(getClass()).getFile(), Arrays.asList(getStoryFilter(storyPaths)), null ); } private String getStoryFilter(String storyPaths) { if (storyPaths == null ) { return '*.story' ; } if (storyPaths.endsWith( '.story' )) { return storyPaths; } return storyPaths + '.story' ; } private List<String> specifiedStoryPaths(String storyPaths) { List<String> result = new ArrayList<String>(); URI cwd = new File( 'src/test/resources' ).toURI(); for (String storyPath : storyPaths.split(File.pathSeparator)) { File storyFile = new File(storyPath); if (!storyFile.exists()) { throw new IllegalArgumentException( 'Story file not found: ' + storyPath); } result.add(cwd.relativize(storyFile.toURI()).toString()); } return result; } @Override public Configuration configuration() { return super .configuration() .useStoryReporterBuilder( new StoryReporterBuilder() .withFormats(Format.XML, Format.STATS, Format.CONSOLE) .withRelativeDirectory( '../build/jbehave' ) ) .usePendingStepStrategy( new FailingUponPendingStep()) .useFailureStrategy( new SilentlyAbsorbingFailure()); } } |
Это использует аннотацию @RunWith
JUnit 4 для указания класса, который будет выполнять тест. AnnotatedEmbedderRunner
— это JUnit Runner
который предоставляет JBehave. Он ищет аннотацию @UsingEmbedder
чтобы определить, как запускать истории:
-
generateViewAfterStories
инструктирует JBehave создать тестовый отчет после запуска историй -
ignoreFailureInStories
позволяет JBehave генерировать исключение, когда история терпит неудачу. Это важно для интеграции с Jenkins, как мы увидим ниже
Аннотация @UsingSteps
связывает шаги сценариев с кодом Java. Подробнее об этом ниже. Вы можете перечислить более одного класса.
Наш тестовый класс повторно использует класс JUnitStories от JBehave, который позволяет легко запускать несколько историй. Нам нужно только реализовать два метода: storyPaths()
и configuration()
.
Метод storyPaths()
сообщает JBehave, где искать истории для запуска. Наша версия немного сложна, потому что мы хотим иметь возможность запускать тесты как из нашей IDE, так и из командной строки, а также потому, что мы хотим иметь возможность запускать либо все истории, либо определенный поднабор.
Мы используем системное свойство bdd.stories
чтобы указать, какие истории следует запускать. Это включает в себя поддержку подстановочных знаков. Наше соглашение об именах требует, чтобы имена файлов историй начинались с персоны , поэтому мы можем легко запустить все истории для одной персоны, используя что-то вроде -Dbdd.stories=wanda_*
.
Метод configuration()
сообщает JBehave, как запускать истории и составлять отчеты о них . Нам нужен вывод в XML для дальнейшей обработки в Jenkins, как мы увидим ниже.
Интересной особенностью является расположение отчетов. JBehave поддерживает Maven, что нормально, но они предполагают, что все следуют соглашениям Maven , а это на самом деле не так. По умолчанию выходные данные отправляются в каталог с именем target
, но мы можем переопределить его, указав путь относительно target
каталога. Мы используем Gradle вместо Maven, и временные файлы Gradle попадают в каталог build
, а не в target
. Подробнее о Gradle ниже.
меры
Теперь мы можем запустить наши истории, но они потерпят неудачу. Мы должны сказать JBehave, как сопоставить шаги Given / When / Then в сценариях с кодом Java. Классы Steps определяют, какой словарь можно использовать в сценариях. Таким образом, они определяют домен-специфический язык (DSL) для приемочного тестирования нашего приложения.
Наше приложение имеет интерфейс RESTful , поэтому мы написали универсальный REST DSL. Однако из-за ограничения HATEOAS в REST клиенту требуется много вызовов для обнаружения URI, которые он должен использовать. Таким образом, написание сценариев становится довольно скучным и повторяющимся, поэтому мы добавили DSL для конкретного приложения поверх REST DSL. Это позволяет нам писать сценарии в терминах, понятных Владельцу продукта .
Распределение шагов для приложения поверх общих шагов REST имеет ряд преимуществ:
- Легко реализовать новый DSL для конкретного приложения, поскольку им нужно только вызвать DSL для REST
- Специфичный для REST DSL может использоваться совместно с другими проектами
Gradle
С шагами на месте, мы можем запустить наши истории из нашей любимой IDE . Это прекрасно работает для разработчиков, но не может быть использовано для непрерывной интеграции (CI).
Наш CI-сервер выполняет автономную сборку, поэтому мы должны иметь возможность запускать сценарии BDD из командной строки. Мы автоматизируем нашу сборку с помощью Gradle, и Gradle уже может запускать тесты JUnit. Тем не менее, наша сборка является сборкой нескольких проектов . Мы не хотим запускать наши сценарии BDD, пока не будут собраны все проекты, не создан дистрибутив и не запущено приложение.
Итак, во-первых, мы отключаем запуск тестов в проекте, который содержит истории BDD:
1
2
3
|
test { onlyIf { false } // We need a running server } |
Затем мы создаем еще одну задачу, которую можно запустить после запуска нашего приложения:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
task acceptStories(type: Test) { ignoreFailures = true doFirst { // Need 'target' directory on *nix systems to get any output file( 'target' ).mkdirs() def filter = System.getProperty( 'bdd.stories' ) if (filter == null ) { filter = '*' } def stories = sourceSets.test.resources.matching { it.include filter }.asPath systemProperty( 'bdd.stories' , stories) } } |
Здесь мы видим силу Gradle. Мы определили новую задачу типа Test
, чтобы она уже могла запускать тесты JUnit. Далее мы настраиваем эту задачу с помощью небольшого скрипта Groovy .
Во-первых, мы должны убедиться, что target
каталог существует. Нам это не нужно или даже не нужно, но без него JBehave не работает должным образом в * nix системах. Я думаю, что это маленький Maven-изм
Затем мы добавляем поддержку для запуска подмножества историй, снова используя системное свойство bdd.stories
. Наши файлы истории находятся в src/test/resources
, поэтому мы можем легко получить к ним доступ, используя стандартный набор исходных текстов Gradle. Затем мы устанавливаем системное свойство bdd.stories
для JVM, которая выполняет тесты.
Дженкинс
Так что теперь мы можем запускать наши сценарии BDD как из нашей IDE, так и из командной строки. Следующим шагом является их интеграция в нашу сборку CI.
Мы могли бы просто заархивировать отчеты JBehave как артефакты, но, если честно, отчеты, которые генерирует JBehave, не так уж велики. К счастью, команда JBehave также поддерживает плагин для CI-сервера Jenkins . Этот подключаемый модуль требует предварительной установки подключаемого модуля xUnit .
После установки подключаемых модулей xUnit и JBehave в jenkins мы можем настроить нашу работу Jenkins для использования подключаемого модуля JBehave. Сначала добавьте действие xUnit после сборки. Затем выберите отчет о тестировании JBehave.
При такой конфигурации выходные данные запуска JBehave в наших историях о BDD выглядят так же, как и для обычных модульных тестов:
Обратите внимание, что желтая часть на графике указывает на ожидающие шаги. Они используются в сценариях BDD, но не имеют аналогов в классах Java Steps. Ожидающие шаги показаны в столбце Skip
в результатах теста:
Обратите внимание, как плагин JBehave Jenkins переводит истории в тесты и сценарии для методов тестирования. Это позволяет легко определить, какие сценарии требуют больше работы.
Хотя плагин JBehave работает довольно хорошо, есть две вещи, которые можно улучшить:
- Вывод результатов испытаний не показан. Это затрудняет выяснение причин неудачного сценария. Поэтому мы также архивируем протокол испытаний JUnit
- Если вы настраиваете
ignoreFailureInStories
какfalse
, JBehave генерирует исключение при сбое, который усекает вывод XML. Плагин JBehave Jenkins больше не может анализировать XML (так как он плохо сформирован) и полностью завершается сбоем, оставляя вас без результатов теста.
В целом это незначительные неудобства, и мы очень довольны нашими автоматизированными сценариями BDD.
Приятного кодирования и не забудьте поделиться!
Ссылка: разработка на основе поведения (BDD) с JBehave, Gradle и Jenkins от нашего партнера по JCG Ремона Синнема в блоге по безопасной разработке программного обеспечения .