Проблема с Традиционными Требованиями

Традиционные документы требований страдают от фундаментальных недостатков:

  • Неоднозначность: “Система должна быть user-friendly” означает разные вещи для разных людей
  • Устаревание: Документация устаревает в момент изменения кода
  • Коммуникационные пробелы: Бизнес-стейкхолдеры и разработчики интерпретируют требования по-разному
  • Отсутствие валидации: Нет способа проверить, соответствует ли реализация намерению

Specification by Example (SbE) решает эти проблемы, используя конкретные примеры как основной артефакт для спецификации, имплементации и тестирования ПО.

Что Такое Specification by Example?

Specification by Example — это коллаборативный подход к определению требований и тестов. Вместо написания абстрактных спецификаций команды создают живую документацию, состоящую из исполняемых примеров, иллюстрирующих поведение системы.

Основные Принципы

1. Выводить скоуп из целей: Начинать с бизнес-целей, а не фич 2. Специфицировать коллаборативно: Бизнес, разработка и тестирование работают вместе 3. Иллюстрировать используя примеры: Конкретные сценарии, а не абстрактные описания 4. Уточнять спецификацию: Итерировать на примерах, пока они не станут четкими и тестируемыми 5. Автоматизировать валидацию: Превращать примеры в исполняемые тесты 6. Валидировать часто: Примеры служат как регрессионные тесты 7. Эволюционировать документацию: Обновлять примеры по мере эволюции системы

Процесс SbE

1. Воркшопы Примеров (Три Амиго)

Представители трех перспектив коллаборируют:

Бизнес (Product Owner): Какую ценность мы доставляем? Разработка (Инженер): Как мы это построим? Тестирование (QA): Что может пойти не так?

Повестка Воркшопа:

  • Представить user story/фичу
  • Мозговой штурм примерных сценариев
  • Идентифицировать счастливые пути, граничные случаи, условия ошибок
  • Уточнить примеры, пока все не согласятся
  • Документировать в структурированном формате

2. Формат Примера

Примеры обычно следуют формату Given-When-Then (синтаксис Gherkin):

Сценарий: Премиум клиент получает скидку лояльности
  Дан клиент с премиум членством
  И они были членом 3 года
  Когда они покупают товар стоимостью $100
  Тогда они получают скидку 15%
  И итог $85

3. Автоматизация

Примеры автоматизируются используя инструменты вроде:

  • Cucumber: BDD фреймворк для Gherkin спецификаций
  • FitNesse: Вики-основанный фреймворк acceptance тестирования
  • Concordion: Превращает спецификации на простом языке в исполняемые тесты
  • SpecFlow: .NET имплементация Cucumber

4. Валидация

Автоматизированные примеры выполняются непрерывно:

  • Во время разработки (быстрая обратная связь)
  • В CI/CD пайплайне (регрессионная безопасность)
  • Как живая документация (всегда актуальна)

Конкретный Пример: E-Commerce Скидки

Традиционный Документ Требований

Система будет применять скидки на основе tier лояльности клиента.
Премиум клиенты получают более высокие скидки, чем стандартные клиенты.
Скидки варьируются в зависимости от длительности членства и суммы покупки.

Проблемы:

  • Какие именно процентные скидки?
  • Что определяет “премиум” клиента?
  • Как длительность членства влияет на скидки?
  • Что происходит в граничных условиях?

Версия Specification by Example

Feature: Скидки на Основе Лояльности
  Как владелец бизнеса
  Я хочу вознаграждать лояльных клиентов скидками
  Чтобы они продолжали делать покупки у нас

Background:
  Даны следующие tier скидок:
    | Тип Членства | Годы | Скидка |
    | Стандарт    | 0-1  | 0%     |
    | Стандарт    | 2-4  | 5%     |
    | Стандарт    | 5+   | 10%    |
    | Премиум     | 0-1  | 10%    |
    | Премиум     | 2-4  | 15%    |
    | Премиум     | 5+   | 20%    |

Сценарий: Новый стандартный клиент не получает скидку
  Дан стандартный клиент, который присоединился 6 месяцев назад
  Когда он покупает товары на общую сумму $100
  Тогда примененная скидка $0
  И итоговая цена $100

Сценарий: Долгосрочный премиум клиент получает максимальную скидку
  Дан премиум клиент, который присоединился 6 лет назад
  Когда он покупает товары на общую сумму $100
  Тогда примененная скидка $20
  И итоговая цена $80

Сценарий: Клиент обновился до премиум в середине tenure
  Дан клиент, который был стандартным 3 года
  И затем обновился до премиум 2 года назад
  Когда он покупает товары на общую сумму $100
  Тогда его общий tenure 5 лет
  И он получает премиум скидку 5+ лет в 20%
  И итоговая цена $80

Сценарий: Скидка не превышает сумму покупки
  Дан премиум клиент с 6 годами членства
  Когда он покупает товары на общую сумму $10
  Тогда примененная скидка $2
  И итоговая цена $8
  Но скидка никогда не превышает общую сумму покупки

Преимущества:

  • Недвусмысленно: Точные проценты и условия специфицированы
  • Комплексно: Граничные случаи (изменения членства, граничные значения) покрыты
  • Тестируемо: Может быть автоматизировано немедленно
  • Валидировано: Примеры проверены всеми тремя амиго

FitNesse: Вики-Основанные Спецификации

FitNesse превращает вики-страницы в исполняемые тесты, делая спецификации доступными для нетехнических стейкхолдеров.

Пример FitNesse: Корзина Покупок

Вики Страница (Читаемая Бизнес Стейкхолдерами):

!3 Расчет Корзины Покупок

Даны эти товары в корзине:

|script                              |
|start|shopping cart                 |
|add item|Widget       |price|10.00 |
|add item|Gadget       |price|25.00 |
|add item|Doohickey    |price|15.00 |
|check   |subtotal            |50.00 |
|apply tax|8%                        |
|check   |tax amount          | 4.00 |
|check   |total               |54.00 |

Что если мы применим купон?

|script                              |
|start   |shopping cart              |
|add item|Widget       |price|10.00 |
|add item|Gadget       |price|25.00 |
|apply coupon|SAVE10                 |
|check   |subtotal            |50.00 |
|check   |discount            | 5.00 |
|check   |subtotal after discount|45.00|
|apply tax|8%                        |
|check   |total               |48.60 |

Java Имплементация (Fixture):

public class ShoppingCart {
    private BigDecimal subtotal = BigDecimal.ZERO;
    private BigDecimal taxRate = BigDecimal.ZERO;
    private BigDecimal discount = BigDecimal.ZERO;

    public void start() {
        subtotal = BigDecimal.ZERO;
        discount = BigDecimal.ZERO;
    }

    public void addItem(String name, BigDecimal price) {
        subtotal = subtotal.add(price);
    }

    public BigDecimal subtotal() {
        return subtotal;
    }

    public void applyTaxPercent(BigDecimal percent) {
        taxRate = percent.divide(new BigDecimal("100"));
    }

    public BigDecimal taxAmount() {
        return subtotalAfterDiscount().multiply(taxRate)
            .setScale(2, RoundingMode.HALF_UP);
    }

    public void applyCoupon(String code) {
        if ("SAVE10".equals(code)) {
            discount = subtotal.multiply(new BigDecimal("0.10"));
        }
    }

    public BigDecimal discountAmount() {
        return discount;
    }

    public BigDecimal subtotalAfterDiscount() {
        return subtotal.subtract(discount);
    }

    public BigDecimal total() {
        return subtotalAfterDiscount().add(taxAmount());
    }
}

Выполнение:

  • Бизнес стейкхолдеры пишут примеры в вики
  • Разработчики имплементируют фикстуры
  • FitNesse запускает примеры против кода
  • Зеленый = спецификация соответствует имплементации
  • Красный = несоответствие между спецификацией и кодом

Преимущества Живой Документации

Традиционная документация устаревает. Живая документация эволюционирует с кодовой базой:

Традиционные Доки:

Последнее обновление: Март 2024
Статус: Неизвестно, все ли еще точно
Тесты: Отдельно от документации

Живая Документация:

Последний запуск: 2 минуты назад (CI билд #4523)
Статус: Все 127 примеров проходят
Тесты: Документация ЯВЛЯЮТСЯ тестами

Документация как Страховочная Сеть

Когда разработчики изменяют код:

Без Живых Доков:

  1. Изменить код
  2. Надеяться, что тесты поймают проблемы
  3. Вручную проверить доки
  4. Может быть обновить доки (вероятно нет)
  5. Деплоить и молиться

С Живыми Доками:

  1. Изменить код
  2. Примеры автоматически падают, если поведение изменилось
  3. Обновить примеры для соответствия новому поведению
  4. Документация автоматически актуальна
  5. Деплоить уверенно

Specification by Example vs. BDD

SbE и Behavior-Driven Development (BDD) тесно связаны:

BDD — это методология разработки ПО, которая подчеркивает коллаборацию и использует примеры. Это “как” построения ПО.

SbE — это техника для захвата требований используя примеры. Это “что” мы строим.

Отношение:

  • BDD использует SbE как свою основную практику
  • Вы можете практиковать SbE без полного принятия BDD
  • BDD добавляет дополнительные практики (вездесущий язык, разработка снаружи внутрь)

Пример:

Feature: Сброс Пароля                         # SbE: Что мы строим

Сценарий: Пользователь запрашивает сброс пароля  # SbE: Пример спецификации
  Дан пользователь с email "alice@example.com"
  Когда он запрашивает сброс пароля
  Тогда он получает email сброса
  И email содержит валидную ссылку сброса

Синтаксис Gherkin служит как SbE документация, так и BDD тестовая автоматизация.

Реальная Имплементация: Фича Банковского Перевода

Традиционный Подход

Док Требований:

Система позволит пользователям переводить деньги между счетами.
Переводы должны валидировать достаточность средств.
Применяются дневные лимиты переводов.

Отдельные Тест-Кейсы:

TC-001: Тест успешного перевода
TC-002: Тест недостаточности средств
TC-003: Тест превышения дневного лимита

Результат: Разрыв между требованиями, тестами и имплементацией.

Подход Specification by Example

Коллаборативный Воркшоп Примеров:

Product Owner: “Мы хотим разрешить переводы средств до $10,000 в день.” Разработчик: “Что происходит, если кто-то пытается перевести $9,000 дважды в один день?” Тестировщик: “Что насчет переводов между разными валютами?” PO: “Второй перевод должен провалиться. Пока только USD.” Разработчик: “Считаются ли ожидающие переводы к лимиту?” Тестировщик: “Что если кто-то переводит точно $10,000.00 против $10,000.01?”

Результирующая Спецификация:

Feature: Переводы Счета с Дневными Лимитами

Background:
  Дан дневной лимит перевода $10,000 USD
  И лимиты сбрасываются в полночь Тихоокеанского времени

Сценарий: Успешный перевод в пределах лимита
  Дана у Алисы $5,000 на её текущем счете
  И она не совершала переводов сегодня
  Когда она переводит $1,000 на счет Боба
  Тогда перевод успешен
  И баланс Алисы $4,000
  И её оставшийся дневной лимит $9,000

Сценарий: Перевод отклонен из-за недостатка средств
  Дана у Алисы $500 на её текущем счете
  Когда она пытается перевести $1,000 Бобу
  Тогда перевод проваливается
  И ошибка "Недостаточно средств"
  И баланс Алисы остается $500

Сценарий: Несколько переводов приближаются к дневному лимиту
  Дана у Алисы $20,000 на её счете
  И она не совершала переводов сегодня
  Когда она переводит $6,000 Бобу
  И переводит $3,000 Кэрол
  Тогда оба перевода успешны
  И её оставшийся дневной лимит $1,000

Сценарий: Перевод отклонен при превышении дневного лимита
  Дана у Алисы $20,000 на её счете
  И она уже перевела $9,500 сегодня
  Когда она пытается перевести $1,000 Бобу
  Тогда перевод проваливается
  И ошибка "Превышен дневной лимит перевода"
  И её оставшийся дневной лимит $500

Сценарий: Граничный тест на точном лимите
  Дана у Алисы $15,000 на её счете
  И она уже перевела $9,999.99 сегодня
  Когда она переводит $0.01 Бобу
  Тогда перевод успешен
  И её оставшийся дневной лимит $0.00

Сценарий: Ожидающие переводы считаются к лимиту
  Дана у Алисы $15,000 на её счете
  И у неё есть ожидающий перевод $8,000
  Когда она пытается перевести $3,000 Бобу
  Тогда перевод проваливается
  И ошибка "Превышен дневной лимит перевода (включая ожидающие)"

Результаты:

  • Ясность: Все знают точно, как работают лимиты
  • Граничные случаи: Граничные значения (напр., $9,999.99 + $0.01) идентифицированы
  • Валидация: Бизнес-логика кодифицирована в исполняемых тестах
  • Документация: Всегда отражает текущее поведение системы

Частые Ошибки и Как Их Избежать

1. Написание Деталей Имплементации в Примерах

Плохо:

Дана таблица базы данных "users" содержит строку с id=123
И колонка "premium_member" true
И "membership_date" "2019-01-01"

Хорошо:

Дан премиум клиент, который присоединился 1 января 2019

Решение: Использовать бизнес-язык, скрывать детали имплементации.

2. Слишком Много Примеров

Проблема: 50 сценариев для одной фичи, в основном избыточные.

Решение:

  • Фокусироваться на ключевых примерах, иллюстрирующих разные поведения
  • Использовать таблицы данных для комбинаторного тестирования
  • Извлекать общие паттерны в секцию Background

3. Примеры Без Assertions

Плохо:

Когда пользователь кликает кнопку submit
Тогда форма отправлена

Хорошо:

Когда пользователь кликает кнопку submit
Тогда он видит сообщение подтверждения "Спасибо за ваш submit"
И получает email подтверждения
И submit появляется в админ панели

Решение: Включать наблюдаемые результаты, а не только действия.

4. Только Технические Стейкхолдеры

Проблема: Разработчики пишут все примеры в изоляции.

Решение: Всегда включать трех амиго (бизнес, dev, QA) в воркшопы.

5. Трактовка Примеров Только Как Тестов

Проблема: Примеры написаны после завершения кода, только для тестирования.

Решение: Примеры управляют разработкой—писать их ПЕРЕД кодированием (как TDD/BDD).

Сравнение Инструментов

ИнструментЯзыкФорматКривая ОбученияЛучше Для
CucumberМногиеGherkinСредняяКоманды знакомые с BDD
FitNesseJava, .NETВики таблицыНизкаяНетехнические стейкхолдеры
ConcordionJava, .NETHTML/MarkdownСредняяКоманды фокусированные на документации
SpecFlow.NET/C#GherkinСредняя.NET магазины делающие BDD
GaugeМногиеMarkdownНизкаяКоманды желающие читаемые спецификации
Robot FrameworkPythonKeyword-drivenСредняяКоманды тестовой автоматизации

Начало Работы с Specification by Example

Неделя 1: Пилотная Фича

  • Выбрать одну маленькую фичу
  • Провести воркшоп трех амиго
  • Написать 3-5 ключевых примеров
  • Автоматизировать с выбранным инструментом
  • Рефлексировать о том, что сработало

Неделя 2-4: Расширение

  • Добавить SbE к новым фичам
  • Уточнить процесс воркшопа
  • Построить библиотеку фикстур
  • Обучить членов команды

Месяц 2-3: Зрелая Практика

  • Ретроспектива существующих фич с примерами
  • Интегрировать в CI/CD
  • Измерить покрытие документации
  • Праздновать успех живых доков

Заключение: Примеры как Источник Истины

Specification by Example трансформирует требования из статичных документов в живые, валидированные артефакты. Делая примеры:

  • Конкретными: Без неоднозначности относительно ожидаемого поведения
  • Коллаборативными: Бизнес, dev и QA выравниваются рано
  • Исполняемыми: Примеры верифицируют имплементацию
  • Живыми: Документация эволюционирует с кодом

Результат — ПО, которое доказуемо делает то, что ожидают стейкхолдеры, документированное в формате, понятном всем, валидируемое непрерывно и всегда актуальное.

Традиционные требования спрашивают “Мы построили это правильно?” Specification by Example гарантирует, что мы строим правильную вещь, правильным способом, с доказательством, что это работает.

Начните с малого, коллаборируйте активно и позволяйте примерам управлять вашей разработкой. Ясность и уверенность, которые они приносят, трансформируют то, как ваша команда строит ПО.