Почему код не работает, если бизнес сказал "всё верно"?
Вы когда-нибудь сталкивались с ситуацией: менеджер утверждает, что задача решена, тесты проходят зелёным, а пользователь всё равно недоволен? Классика жанра. Проблема не в коде — проблема в коммуникации. Мы говорим на разных языках: бизнес оперирует сценариями, а разработчики — методами и классами. Здесь на помощь приходит BDD (Behaviour-Driven Development) — разработка через поведение. И да, на PHP это работает блестяще.
В этой статье мы разберёмся, как BDD на PHP помогает превратить расплывчатые "хотелки" в понятные тесты, которые понимают и менеджер, и программист. Вы узнаете, чем BDD отличается от обычного TDD, как писать сценарии на Gherkin и какие инструменты (Behat, PHPSpec) реально упрощают жизнь.
Что такое BDD простыми словами?
BDD (Behaviour-Driven Development) — это эволюция TDD. Если TDD фокусируется на "как тестировать код", то BDD отвечает на вопрос "что должен делать код, чтобы бизнес был счастлив".
Представьте, что вы пишете интернет-магазин. Вместо абстрактного теста "метод applyDiscount() должен уменьшить цену", вы пишете сценарий:
- Дано: в корзине товар за 1000 рублей
- Когда: пользователь вводит промокод "SALE10"
- Тогда: стоимость корзины становится 900 рублей
Этот сценарий читает любой человек — от маркетолога до тимлида. Никакой магии, только поведение системы.
BDD vs TDD: в чём разница?
Многие путают эти понятия. Давайте расставим точки над i:
- TDD — пишем тест, потом код, потом рефакторим. Тесты пишет разработчик для разработчика.
- BDD — сначала пишем сценарий поведения на естественном языке, потом автоматизируем его. Сценарии пишутся в диалоге с бизнесом.
BDD не отменяет TDD, а дополняет его. Вы всё ещё пишете юнит-тесты, но теперь у вас есть "живая документация" в виде сценариев.
Инструменты BDD для PHP: выбираем оружие
Экосистема PHP предлагает два основных инструмента для разработки через поведение: Behat и PHPSpec. Они решают разные задачи.
Behat — универсальный солдат
Behat — это фреймворк для приёмочного тестирования. Он работает с языком Gherkin (тот самый "Дано-Когда-Тогда"). Behat идеален для интеграционных тестов, проверки API, веб-интерфейсов и бизнес-логики верхнего уровня.
PHPSpec — микроскоп для разработчика
PHPSpec — это инструмент для спецификации классов. Он ближе к TDD, но с философией BDD: вы описываете поведение объекта, а не его внутренности. PHPSpec отлично подходит для юнит-тестирования доменной логики.
В этой статье мы сосредоточимся на Behat, так как он лучше всего иллюстрирует behaviour-driven development в PHP.
Пишем первый BDD-сценарий на PHP с Behat
Давайте создадим реальный пример. Предположим, мы разрабатываем систему лояльности. Клиент получает скидку 10%, если сумма заказа превышает 5000 рублей.
Шаг 1: Установка Behat
composer require --dev behat/behat
Затем инициализируем проект:
vendor/bin/behat --init
Структура будет такой:
features/
bootstrap/
FeatureContext.php
discount.feature
Шаг 2: Пишем feature-файл на Gherkin
Создаём файл features/discount.feature:
Feature: Расчёт скидки для лояльных клиентов
As a менеджер по продажам
I want to предоставлять скидку 10% на заказы от 5000 рублей
So that стимулировать крупные покупки
Scenario: Заказ превышает порог скидки
Given в корзине товары на сумму 6000 рублей
When я запрашиваю итоговую стоимость
Then итоговая стоимость должна быть 5400 рублей
Scenario: Заказ меньше порога скидки
Given в корзине товары на сумму 3000 рублей
When я запрашиваю итоговую стоимость
Then итоговая стоимость должна быть 3000 рублей
Обратите внимание: здесь нет ни строчки PHP. Это и есть суть BDD — бизнес-сценарий понятен без кода.
Шаг 3: Реализуем контекст (FeatureContext)
Теперь нужно "оживить" эти сценарии. Открываем 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 float $cartTotal = 0;
private float $finalTotal = 0;
/**
* @Given в корзине товары на сумму :amount рублей
*/
public function вКорзинеТоварыНаСуммуРублей(float $amount): void
{
$this->cartTotal = $amount;
}
/**
* @When я запрашиваю итоговую стоимость
*/
public function яЗапрашиваюИтоговуюСтоимость(): void
{
$discountService = new DiscountService();
$this->finalTotal = $discountService->calculate($this->cartTotal);
}
/**
* @Then итоговая стоимость должна быть :expected рублей
*/
public function итоговаяСтоимостьДолжнаБытьРублей(float $expected): void
{
if ($this->finalTotal !== $expected) {
throw new \Exception(
"Ожидалось {$expected}, получено {$this->finalTotal}"
);
}
}
}
Видите, как аннотации (например, @Given) связывают текст сценария с PHP-кодом? Это и есть разработка через поведение в действии.
Шаг 4: Пишем бизнес-логику
Создаём класс DiscountService.php:
<?php
class DiscountService
{
private const THRESHOLD = 5000;
private const DISCOUNT_RATE = 0.1;
public function calculate(float $total): float
{
if ($total >= self::THRESHOLD) {
return $total * (1 - self::DISCOUNT_RATE);
}
return $total;
}
}
Шаг 5: Запускаем тесты
vendor/bin/behat
Вы увидите зелёные строки — все сценарии пройдены. Теперь, если бизнес решит изменить порог скидки, он просто редактирует feature-файл, и вы сразу видите, какие тесты сломались.