Когда код говорит на языке бизнеса: знакомство с BDD
Представьте себе ситуацию: вы написали идеальный, с точки зрения кода, модуль оплаты. Все методы работают, исключения обрабатываются, покрытие юнит-тестами — 99%. Вы открываете шампанское, но приходит менеджер и говорит: «А почему у нас списываются деньги до подтверждения заказа?». И вы понимаете — баг не в коде, а в понимании задачи.
Знакомо? Именно для таких случаев существует BDD (Behaviour-Driven Development) — разработка, управляемая поведением. Это не просто очередная методология, а мост между «хочу, чтобы работало» и «как это реализовать на PHP». Если вы устали переписывать логику после каждого уточнения требований, эта статья для вас.
В этом материале мы разберём, как внедрить BDD на PHP с помощью фреймворка Behat, напишем реальные тесты и поймём, почему это делает код чище, а коммуникацию — прозрачнее.
Что такое BDD и чем он отличается от TDD?
Многие путают Behaviour-Driven Development с обычным TDD (Test-Driven Development). И правда, внешне они похожи: и там, и там сначала пишутся тесты. Но разница — в фокусе.
- TDD — это про «как проверить, что функция возвращает правильное число». Тесты пишутся на языке программиста: assert, equals, mock.
- BDD — это про «как система ведёт себя в конкретной бизнес-ситуации». Сценарии пишутся на естественном, почти человеческом языке.
Ключевая идея BDD в PHP — использовать единый язык для общения. Разработчики, тестировщики и менеджеры читают один и тот же сценарий. И если в нём написано «пользователь добавляет товар в корзину без авторизации», то все понимают это одинаково.
Инструментарий: Behat и Gherkin
В мире PHP главным инструментом для BDD является Behat. Это фреймворк, который парсит сценарии на языке Gherkin и превращает их в исполняемые тесты. Gherkin — это простой английский (или русский) язык с ключевыми словами: Feature, Scenario, Given, When, Then.
Давайте установим Behat в ваш PHP-проект:
composer require --dev behat/behat behat/mink-extension behat/mink-goutte-driver
После установки инициализируем проект:
vendor/bin/behat --init
Структура будет выглядеть так:
features/
bootstrap/
FeatureContext.php
your_feature.feature
behat.yml
Пишем первый BDD-сценарий на PHP
Предположим, у нас есть интернет-магазин. Бизнес-требование: «Зарегистрированный пользователь может добавить товар в корзину и увидеть общую сумму». Создадим файл features/cart.feature:
Feature: Корзина покупателя
Как зарегистрированный пользователь
Я хочу добавлять товары в корзину
Чтобы видеть итоговую стоимость
Scenario: Добавление одного товара
Given я захожу на сайт как пользователь "ivan@example.com"
And на странице каталога есть товар "Ноутбук" за 50000 рублей
When я нажимаю "Добавить в корзину" для товара "Ноутбук"
Then я вижу сообщение "Товар добавлен"
And итоговая сумма корзины равна 50000 рублей
Обратите внимание: этот сценарий может прочитать любой менеджер без знания PHP. Но для Behat он — исполняемый тест.
Реализация шагов (Step Definitions)
Теперь нужно «научить» Behat понимать эти шаги. Откройте features/bootstrap/FeatureContext.php:
<?php
use Behat\Behat\Context\Context;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
class FeatureContext implements Context
{
private $currentUser;
private $cart = [];
private $products = [];
/**
* @Given я захожу на сайт как пользователь :email
*/
public function iLoginAsUser($email)
{
$this->currentUser = $email;
// Здесь могла бы быть логика аутентификации
}
/**
* @Given на странице каталога есть товар :name за :price рублей
*/
public function thereIsProduct($name, $price)
{
$this->products[$name] = (int) $price;
}
/**
* @When я нажимаю :button для товара :productName
*/
public function iAddToCart($button, $productName)
{
if ($button === 'Добавить в корзину') {
$this->cart[] = $this->products[$productName];
}
}
/**
* @Then я вижу сообщение :message
*/
public function iSeeMessage($message)
{
if ($message !== 'Товар добавлен') {
throw new \Exception("Ожидалось сообщение: $message");
}
}
/**
* @Then итоговая сумма корзины равна :sum рублей
*/
public function cartTotalEquals($sum)
{
$total = array_sum($this->cart);
if ($total !== (int) $sum) {
throw new \Exception(
"Сумма корзины $total, ожидалось $sum"
);
}
}
}
Запускаем тест:
vendor/bin/behat features/cart.feature
Если всё зелёное — вы только что написали свой первый BDD-тест на PHP. Если красное — Behat покажет, какой именно шаг упал и почему.
Продвинутые сценарии: таблицы и контексты
В реальных проектах Behaviour-Driven Development становится мощнее, когда вы используете таблицы данных (Data Tables) и Outline-сценарии. Это позволяет проверить сразу несколько кейсов без дублирования кода.
Scenario Outline: проверка граничных значений
Scenario Outline: Проверка скидки на сумму корзины
Given я зарегистрированный пользователь
And в корзине товары на сумму <сумма> рублей
When я применяю промокод "SKIDKA10"
Then итоговая сумма равна <ожидаемая> рублей
Examples:
| сумма | ожидаемая |
| 1000 | 900 |
| 5000 | 4500 |
| 100 | 100 |
Обратите внимание: мы не пишем три разных сценария. Behat сам подставит значения из таблицы и выполнит тест трижды. Это экономит часы работы и делает BDD на PHP максимально эффективным.
Интеграция с Mink: тестирование UI
До сих пор мы тестировали только логику. Но BDD часто применяют для проверки пользовательского интерфейса. Для этого в связке с Behat используют Mink — эмулятор браузера.
Подключите Mink в behat.yml:
default:
extensions:
Behat\MinkExtension:
base_url