Разработка на основе поведенческих сценариев
Разработка на основе поведенческих сценариев (BDD) — популярная методология разработки программного обеспечения. BDD считается расширением TDD и в значительной степени вдохновлена практиками Agile. Основная причина выбора BDD в качестве процесса разработки заключается в устранении барьеров в общении между бизнес- и техническими командами. BDD способствует использованию автоматизированного тестирования для проверки всех документированных функций проекта с самого начала. Именно поэтому часто говорят о BDD в контексте фреймворков для тестирования (таких как Codeception). Однако подход BDD — это гораздо больше, чем просто тестирование — это общий язык для всех членов команды, используемый во время процесса разработки.
Что такое разработка на основе поведенческих сценариев
BDD был представлен Даном Нортом. Он описал его как:
методология, ориентированная на внешние факторы, основанная на pull, включающая множество заинтересованных сторон, множественных масштабов, с высокой автоматизацией, Agile. Она описывает цикл взаимодействий с чётко определёнными результатами, приводящий к поставке работающего, протестированного программного обеспечения, имеющего значение.
BDD эволюционировал с момента своего создания, начав с замены слова «тест» на «должен» в модульных тестах и перейдя к мощным инструментам, таким как Cucumber и Behat, которые позволили использовать пользовательские истории (человекочитаемый текст) в качестве тестовых кейсов принятия.
Идею BDD можно свести к следующему:
- описание функций в сценарии с помощью формального текста
- использование примеров для конкретизации абстрактных вещей
- реализация каждого шага сценария для тестирования
- написание фактического кода, реализующего функцию
Написание каждой функции в формате пользовательской истории, которая автоматически выполняется как тест, гарантирует, что бизнес, разработчики, QA и менеджеры находятся в одной точке.
BDD поощряет обсуждения и исследования с целью формализации требований и функций, которые необходимо реализовать, требуя написания пользовательских историй таким образом, чтобы их мог понять каждый.
Благодаря тому, что тесты являются частью пользовательской истории, BDD позволяет нетехническому персоналу писать (или редактировать) тесты принятия.
С помощью этой процедуры мы также гарантируем, что каждый член команды знает, что было разработано, чего не было, что было протестировано, а чего нет.
Универсальный язык
Универсальный язык всегда рассматривается как общий язык. Это его основное преимущество. Это не набор терминов из наших бизнес-спецификаций и не набор технических терминов разработчиков. Это общие слова и термины, которые могут быть понятны людям, для которых мы разрабатываем программное обеспечение, и которые должны быть понятны разработчикам. Установление правильного общения между этими двумя группами людей имеет жизненно важное значение для создания успешного проекта, который будет соответствовать предметной области и удовлетворять всем бизнес-требованиям.
Каждая функция продукта должна рождаться из разговора между
- бизнес-аналитиками (аналитики, владелец продукта)
- разработчиками
- QA
которые в BDD известны как «три друга».
Такие разговоры должны приводить к написанию историй. Должен быть участник, который что-то делает, функция, которая должна быть выполнена в рамках истории, и достигнутый результат.
Давайте попробуем написать простую историю:
As a customer I want to buy several products I put first product with $600 price to my cart And then another one with $1000 price When I go to checkout process I should see that total number of products I want to buy is 2 And my order amount is $1600
Как видим, эта простая история выделяет ключевые понятия, которые называются контрактами. Мы должны выполнить эти контракты, чтобы правильно смоделировать программное обеспечение. Но как мы можем проверить, что эти контракты выполняются? Cucumber представил специальный язык для таких историй под названием Gherkin. Та же история, преобразованная в Gherkin, будет выглядеть так:
Feature: checkout process
In order to buy products
As a customer
I want to be able to buy several products
Scenario:
Given I have product with $600 price in my cart
And I have product with $1000 price
When I go to checkout process
Then I should see that total number of products is 2
And my order amount is $1600 Cucumber, Behat и, конечно же, Codeception могут выполнить этот сценарий шаг за шагом как автоматизированный тест. Каждый шаг в этом сценарии требует кода, который его определяет.
Gherkin
Давайте подробнее изучим формат Gherkin, а затем посмотрим, как его выполнить с помощью Codeception:
Функции
Всякий раз, когда вы начинаете писать историю, вы описываете определённую функцию приложения с набором сценариев и примеров, описывающих эту функцию.
Файл функции написан в формате Gherkin. Codeception может сгенерировать файл функции для вас. Мы предположим, что мы будем использовать сценарии в файлах функций для тестов принятия, поэтому файлы функций должны быть размещены в acceptance каталоге пакета:
php vendor/bin/codecept g:feature acceptance checkout
Сгенерированная шаблон будет выглядеть следующим образом:
Feature: checkout In order to ... As a ... I need to ... Scenario: try checkout
Этот шаблон можно заполнить, задав исполнителя и цели:
Feature: checkout In order to buy product As a customer I need to be able to checkout the selected products
Далее мы опишем эту функцию, написав примеры для неё
Сценарии
Сценарии — это реальные примеры использования функции. Внутри файла функции они должны быть написаны внутри блока Feature. Каждый сценарий должен содержать своё название:
Feature: checkout In order to buy product As a customer I need to be able to checkout the selected products Scenario: order several products
Сценарии пишутся пошагово с использованием подхода Given-When-Then. Вначале сценарий должен описать свой контекст с ключевым словом Given:
Given I have product with $600 price in my cart And I have product with $1000 price in my cart
Здесь мы также используем слово And, чтобы расширить Given и не повторять его в каждой строке.
Вот как мы описали начальные условия. Далее мы выполняем какое-то действие. Для этого мы используем ключевое слово When:
When I go to checkout process
И в конце мы проверяем наше ожидание, используя ключевое слово Then. Действие изменило начальное состояние Given и произвело некоторые результаты. Давайте проверим, соответствуют ли эти результаты нашим ожиданиям.
Then I should see that total number of products is 2 And my order amount is $1600
Мы можем протестировать этот сценарий, выполнив его в режиме предварительного выполнения. В этом режиме тест не будет выполняться (на самом деле, мы не определили ни одного шага для него, поэтому он не будет выполняться в любом случае).
$ codecept dry-run acceptance checkout.feature
checkout: order several products Signature: checkout:order several products Test: tests/acceptance/checkout.feature:order several products Scenario -- In order to buy product As a customer I need to be able to checkout the selected products Given i have product with $600 price in my cart And i have product with $1000 price in my cart When i go to checkout process Then i should see that total number of products is 2 And my order amount is $1600 INCOMPLETE Step definition for `I have product with $600 price in my cart` not found in contexts Step definition for `I have product with $1000 price` not found in contexts Step definition for `I go to checkout process` not found in contexts Step definition for `I should see that total number of products is 2` not found in contexts Step definition for `my order amount is $1600` not found in contexts Run gherkin:snippets to define missing steps
Помимо перечисленных шагов сценария, мы получили уведомление о том, что наши шаги ещё не определены. Мы можем легко определить их, выполнив команду gherkin:snippets для данного пакета:
codecept gherkin:snippets acceptance
Это сгенерирует шаблоны кода для всех неопределённых шагов во всех файлах функций этого пакета. Следующим шагом будет определение этих шагов и преобразование файла функции в действительный тест.
Определения шагов
Для соответствия шагам из файла функции коду PHP мы используем аннотации, которые добавляются к методам класса. По умолчанию Codeception ожидает, что все методы, помеченные аннотацией @Given, @When, @Then, будут содержать строку шага.
/** @Given I am logged as admin */
Шаги также могут соответствовать выражениям регулярных выражений. Таким образом, мы можем сделать шаги более гибкими.
/** @Given /I am (logged|authorized) as admin/ */
Обратите внимание, что регулярные выражения должны начинаться и заканчиваться символом /. Регулярные выражения также используются для сопоставления параметров и передачи их как аргументов в методы.
<?php
/**
* @Given /I am (?:logged|authorized) as "(\w+)"/
*/
function amAuthorized($role)
{
// logged or authorized does not matter to us
// so we added ?: for this capture group
} Параметры также могут передаваться в строках без регулярных выражений, используя заполнитель «:params».
/** @Given I am logged in as :role */
Это соответствует любому слову (переданному в двойных кавычках) или числу:
Given I am logged in as "admin" Given I am logged in as 1
Шаги определяются в файлах контекста. По умолчанию контекст — это класс исполнителя, т. е. для пакета тестов принятия по умолчанию контекстом является класс AcceptanceTester. Однако вы можете определять шаги в любых классах и включать их в качестве контекстов. Это полезно для определения шагов в классах StepObject и PageObject.
Чтобы перечислить все определённые шаги, выполните команду gherkin:steps:
codecept gherkin:steps
Тестирование поведения
Как уже упоминалось, файлы функций — это не просто пользовательские истории. Написав функции на формальном языке Gherkin, мы можем выполнять эти сценарии как автоматизированные тесты. Нет ограничений в способе тестирования этих сценариев. Тесты могут выполняться на функциональном, уровне принятия или предметной области. Однако в данном руководстве мы сосредоточимся на тестах принятия или пользовательского интерфейса.
Тестирование принятия
Поскольку мы сгенерировали фрагменты для недостающих шагов с помощью команды gherkin:snippets, мы определим их в файле AcceptanceTester.
<?php
class AcceptanceTester extends \Codeception\Actor
{
use _generated\AcceptanceTesterActions;
/**
* @Given I have product with :num1 price in my cart
*/
public function iHaveProductWithPriceInMyCart($num1)
{
throw new \PHPUnit\Framework\IncompleteTestError("Step `I have product with :num1 price in my cart` is not defined");
}
/**
* @When I go to checkout process
*/
public function iGoToCheckoutProcess()
{
throw new \PHPUnit\Framework\IncompleteTestError("Step `I go to checkout process` is not defined");
}
/**
* @Then I should see that total number of products is :num1
*/
public function iShouldSeeThatTotalNumberOfProductsIs($num1)
{
throw new \PHPUnit\Framework\IncompleteTestError("Step `I should see that total number of products is :num1` is not defined");
}
/**
* @Then my order amount is :num1
*/
public function myOrderAmountIs($num1)
{
throw new \PHPUnit\Framework\IncompleteTestError("Step `my order amount is :num1` is not defined");
}
} Обратите внимание, что :num1 может использоваться для строк и чисел (может содержать знак валюты). В данном случае :num1 соответствует $600, а $num1 присвоено значение 600. Если вам нужно получить точную строку, оберните значение в кавычки: "600$"
По умолчанию они генерируют исключения Incomplete, чтобы убедиться, что тесты с недостающими шагами случайно не будут помечены как успешные. Нам нужно будет реализовать эти шаги. Поскольку мы находимся в пакете принятия, мы, вероятно, используем модули PHPBrowser или WebDriver. Это означает, что мы можем использовать их методы внутри файла Tester, как мы делаем при написании тестов с помощью $I->. Вы можете использовать методы amOnPage, click, see внутри определений шагов, чтобы каждый шаг Gherkin-сценария расширялся базовыми шагами Codeception. Давайте покажем, как это можно реализовать в нашем случае:
<?php
class AcceptanceTester extends \Codeception\Actor
{
use _generated\AcceptanceTesterActions;
/**
* @Given I have product with :num1 price in my cart
*/
public function iHaveProductWithPriceInMyCart($num1)
{
// haveRecord method is available in Laravel, Phalcon, Yii modules
$productId = $this->haveRecord('Product', ['name' => 'randomProduct'.uniqid(), 'price' => $num1]);
$this->amOnPage("/item/$productId");
$this->click('Order');
}
/**
* @When I go to checkout process
*/
public function iGoToCheckoutProcess()
{
$this->amOnPage('/checkout');
}
/**
* @Then I should see that total number of products is :num1
*/
public function iShouldSeeThatTotalNumberOfProductsIs($num1)
{
$this->see($num1, '.products-count');
}
/**
* @Then my order amount is :num1
*/
public function myOrderAmountIs($num1)
{
$this->see($num1, '.total');
}
} Для повышения эффективности тестирования мы предположили, что мы используем один из фреймворков ActiveRecord, таких как Laravel, Yii или Phalcon, поэтому мы можем динамически создавать записи в базе данных с помощью метода haveRecord. После этого мы открываем браузер и тестируем наши веб-страницы, чтобы увидеть, что после выбора этих продуктов цена действительно рассчитывается правильно.
Мы можем выполнить предварительный прогон (или запустить) наш файл функции, чтобы увидеть, как Given/When/Then расширяются до подшагов:
Given i have product with $600 price in my cart
I have record 'Product',{"name":"randomProduct571fad4f88a04","price":"600"}
I am on page "/item/1"
I click "Order"
And i have product with $1000 price in my cart
I have record 'Product',{"name":"randomProduct571fad4f88b14","price":"1000"}
I am on page "/item/2"
I click "Order"
When i go to checkout process
I am on page "/checkout"
Then i should see that total number of products is 2
I see "2",".products-count"
And my order amount is $1600
I see "1600",".total" Таким образом, файл функции выполняется точно так же, как и любой другой тест Codeception. Подшаги предоставляют подробную информацию о том, как выполняется сценарий.
Одной из критик тестирования с помощью Gherkin было то, что только техническая команда знала, как выполняется сценарий теста. Это могло привести к ложноположительным тестам. Разработчики могли использовать пустые шаги для сценариев (или не относящиеся к делу) и создавать некорректные тесты для корректных сценариев. Codeception повышает уровень коммуникации; каждый член команды может понять, что происходит на более низком (техническом) уровне. Расширение сценария до подшагов отображает фактический процесс выполнения теста. Любой член команды может прочитать вывод и вложить усилия в улучшение набора тестов.
Расширенный Gherkin
Давайте улучшим наш пакет BDD, используя расширенные возможности языка Gherkin.
Фон
Если у группы сценариев одинаковые начальные шаги, например, для панели управления нам всегда нужно войти как администратору, мы можем использовать раздел Фон, чтобы выполнить необходимые подготовительные действия и не повторять одни и те же шаги во всех сценариях.
Feature: Dashboard
In order to view current state of business
As an owner
I need to be able to see reports on dashboard
Background:
Given I am logged in as administrator
And I open dashboard page Шаги в фоне определяются так же, как и в сценариях.
Таблицы
Сценарии могут стать более описательными, если вы представляете повторяющиеся данные в виде таблиц. Вместо написания нескольких шагов «У меня в корзине товар с ценой :num1 $» мы можем иметь один шаг с несколькими значениями в нём.
Given i have products in my cart
| name | category | price |
| Harry Potter | Books | 5 |
| iPhone 5 | Smartphones | 1200 |
| Nuclear Bomb | Weapons | 100000 | Таблицы — это рекомендуемый способ передачи массивов в сценарии тестов. Внутри определения шага данные хранятся в аргументе, переданном как экземпляр \Behat\Gherkin\Node\TableNode.
<?php
/**
* @Given i have products in my cart
*/
public function iHaveProductsInCart(\Behat\Gherkin\Node\TableNode $products)
{
// iterate over all rows
foreach ($node->getRows() as $index => $row) {
if ($index === 0) { // first row to define fields
$keys = $row;
continue;
}
$this->haveRecord('Product', array_combine($keys, $row));
}
} Примеры
В случае, когда сценарии представляют одну и ту же логику, но различаются данными, мы можем использовать Контур сценария, чтобы предоставить разные примеры для одного и того же поведения. Контур сценария похож на базовый сценарий, с некоторыми значениями, заменёнными заглушками, которые заполняются из таблицы. Каждый набор значений выполняется как отдельный тест.
Scenario Outline: order discount
Given I have product with price <price>$ in my cart
And discount for orders greater than $20 is 10 %
When I go to checkout
Then I should see overall price is "<total>" $
Examples:
| price | total |
| 10 | 10 |
| 20 | 20 |
| 21 | 18.9 |
| 30 | 27 |
| 50 | 45 | Длинные строки
Значения текста внутри сценариев могут быть установлены внутри блока """:
Then i see in file "codeception.yml"
"""
paths:
tests: tests
log: tests/_output
data: tests/_data
helpers: tests/_support
envs: tests/_envs
""" Эта строка передаётся как стандартный параметр строки PHP
<?php
/**
* @Then i see in file :filename
*/
public function seeInFile($fileName, $fileContents)
{
// note: module "Asserts" is enabled in this suite
if (!file_exists($fileName)) {
$this->fail("File $fileName not found");
}
$this->assertEquals(file_get_contents($fileName), $fileContents);
} Теги
Сценарии и функции Gherkin могут содержать теги, помеченные @. Теги эквивалентны группам в Codeception. Таким образом, если вы определите функцию с тегом @important, вы можете выполнить её внутри группы important, выполнив:
codecept run -g important
Тег должен быть размещён перед словом Scenario: или перед словом Feature:. В последнем случае все сценарии этой функции будут добавлены в соответствующую группу.
Настройка
Как мы упомянули ранее, шаги должны быть определены внутри классов контекста. По умолчанию все шаги определяются внутри класса актора, например, AcceptanceTester. Однако вы можете включить больше контекстов. Это можно настроить в глобальном codeception.yml или файле конфигурации набора:
gherkin:
contexts:
default:
- AcceptanceTester
- AdditionalSteps Файл AdditionalSteps должен быть доступен автозагрузчиком и может быть создан с помощью Codeception\Lib\Di. Это означает, что практически любой класс может быть контекстом. Если класс получает класс актора в конструкторе или в методе _inject, DI может инжектировать его в него.
<?php
class AdditionalSteps
{
protected $I;
function __construct(AcceptanceTester $I)
{
$this->I = $I;
}
/**
* @When I do something
*/
function additionalActions()
{
}
} Таким образом, PageObjects, Helpers и StepObjects также могут стать контекстами. Но предпочтительнее включать классы контекста по их тегам или ролям.
Если у вас есть класс Step\Admin, который определяет только шаги администратора, разумно использовать его в качестве контекста для всех функций, содержащих «Как администратор». В этом случае «администратор» — это роль, и мы можем настроить её на использование дополнительного контекста.
gherkin:
contexts:
role:
admin:
- "Step\Admin" Контексты также могут быть прикреплены к тегам. Это может быть полезно, если вы хотите переопределить шаги для некоторых сценариев. Допустим, мы хотим обойти шаги входа для некоторых сценариев, загружая уже определённую сессию. В этом случае мы можем создать класс Step\FastLogin с переопределённым шагом «Я вошёл как».
gherkin:
contexts:
tag:
fastlogin:
- "Step\FastLogin" Контексты также могут быть загружены автоматически:
gherkin:
contexts:
path: tests/_support/Steps
namespace_prefix: Steps
default:
- AcceptanceTester Это загрузит все контексты из указанного пути и добавит к нему заданный префикс пространства имён.
Миграция с Behat
Хотя Behat — отличный инструмент для разработки подхода «поведение-управление-разработкой», вы всё ещё можете предпочесть Codeception в качестве основного фреймворка для тестирования. Если вы хотите объединить все свои тесты (unit/functional/acceptance) и запустить их с одним исполнителем, Codeception — хороший выбор. Кроме того, Codeception предоставляет богатый набор хорошо поддерживаемых модулей для различных бэкендов тестирования, таких как Selenium Webdriver, Symfony, Laravel и т. д.
Если вы решили запустить свои функции с помощью Codeception, мы рекомендуем начать с символической ссылки на вашу директорию features в один из наборов тестов:
ln -s $PWD/features tests/acceptance
Затем вам нужно будет реализовать все определения шагов. Запустите gherkin:snippets, чтобы сгенерировать заготовки для них. По умолчанию рекомендуется размещать определения шагов в классе актора (Tester) и использовать его методы для реализации шагов.
Тесты против функций
Обычно думают, что сценарий BDD равен тесту. Но это не так. Не каждый тест должен быть описан как функция. Не каждый тест предназначен для проверки реальной бизнес-ценности. Например, регрессионные тесты или тесты негативных сценариев не приносят никакой ценности для бизнеса. Бизнес-аналитики не заинтересованы в воспроизведении сценария ошибки #13 или в сообщении об ошибке, отображаемом при попытке ввести неверный пароль на странице входа. Написание всех тестов внутри файлов функций приводит к информационной перегрузке.
В Codeception вы можете объединить тесты, написанные в формате Gherkin, с тестами, написанными в форматах Cept/Cest/Test. Таким образом, вы можете сохранить файлы функций компактными с минимальным набором сценариев и написать обычные тесты, чтобы охватить все случаи.
Соответствующие функции и тесты могут быть прикреплены к одной и той же группе. И что ещё интереснее, вы можете сделать так, чтобы тесты зависели от сценариев функций. Допустим, у нас есть файл login.feature со сценарием «Вход обычного пользователя». В этом случае вы можете указать, что каждый тест, требующий входа, должен зависеть от сценария «Вход обычного пользователя»:
@depends login:Log regular user
Внутри блока @depends вы должны использовать подпись теста. Выполните свою функцию с dry-run, чтобы увидеть подписи для всех сценариев в ней. Помечая тесты @depends, вы гарантируете, что этот тест не будет выполнен до выполнения зависящего от него теста.
Выводы
Если вам нравится концепция «поведение-управление-разработкой» или вы предпочитаете сохранять сценарии тестов в удобочитаемом формате, Codeception позволяет вам писать и выполнять сценарии в формате Gherkin. Файлы функций — это просто ещё один формат тестов в Codeception, поэтому их можно объединить с файлами Cept и Cest в одном наборе. Определения шагов ваших сценариев могут использовать всю мощь модулей Codeception, PageObjects и StepObjects.
- Следующая глава: Настройка >
- Предыдущая глава: < Усовершенствованное использование
© 2011 Michael Bodnarchuk and contributors
Licensed under the MIT License.
https://codeception.com/docs/07-BDD