ATDD переворачивает обычный порядок: QA пишет Playwright acceptance-тест до того как фича существует, тест падает, и фича считается готовой когда тест проходит. Этот единственный сдвиг переводит QA из режима верификации готовой работы в режим определения что означает "готово". Статья разбирает цикл Red-Green-Refactor, как формат BDD Given/When/Then вписывается в рабочий процесс трёх участников, и что меняется в ежедневной работе QA когда разработчики уже практикуют TDD.
Цикл TDD: Red, Green, Refactor
TDD следует строгому трёхшаговому циклу. Red: написать тест который падает (потому что фича ещё не существует). Green: написать минимальный код чтобы тест прошёл. Refactor: почистить код сохраняя тесты зелёными. Цикл повторяется для каждого небольшого куска функциональности.
// Фаза Red: пишем падающий тест
test('calculateDiscount applies 10% for code SAVE10', () => {
const result = calculateDiscount(100, 'SAVE10');
expect(result).toBe(90);
});
// Запуск теста падает: calculateDiscount не определена// Фаза Green: пишем минимальный код для прохождения
function calculateDiscount(price: number, code: string): number {
if (code === 'SAVE10') {
return price * 0.9;
}
return price;
}
// Теперь тест проходит// Фаза Refactor: чистим код не ломая тест
const DISCOUNT_CODES: Record<string, number> = {
SAVE10: 0.1,
SAVE20: 0.2,
SAVE50: 0.5,
};
function calculateDiscount(price: number, code: string): number {
const discount = DISCOUNT_CODES[code] || 0;
return price * (1 - discount);
}
// Тесты по-прежнему проходят: рефакторинг ничего не сломалПреимущества TDD влияющие на QA
Естественно тестируемый код. Код написанный через TDD как правило имеет небольшие функции с чёткими входными и выходными данными: проще тестировать на каждом уровне. Защита от регрессий. Каждое поведение защищено тестом. Когда QA находит баг, разработчики могут воспроизвести его тестом, исправить, и тест предотвращает регрессию. Документация через тесты. TDD-тесты описывают что должен делать код в конкретных сценариях. QA может читать тесты чтобы понять ожидаемое поведение. Раннее обнаружение багов. Проблемы пойманные при написании юнит-тестов стоят на порядки дешевле в исправлении чем проблемы найденные в QA или продакшне.Behaviour-Driven Development (BDD)
BDD расширяет TDD в направлении бизнес-уровня. Где TDD фокусируется на том как работает код, BDD фокусируется на том какое поведение требуется.
Три участника: разработчик, QA и продукт-менеджер, совместно определяют поведение в формате Given/When/Then до написания любого кода.
Feature: Discount codes
Scenario: Valid discount code reduces price
Given I have a cart with items totaling $100
When I apply discount code "SAVE10"
Then the total should be $90
And the discount amount should be $10
Scenario: Invalid discount code shows error
Given I have a cart with items totaling $100
When I apply discount code "INVALID"
Then I should see "Invalid discount code"
And the total should remain $100
Scenario: Expired discount code shows error
Given I have a cart with items totaling $100
When I apply discount code "EXPIRED20"
Then I should see "This discount code has expired"Как QA может применять мышление TDD
Даже не пиша юнит-тесты, можно применять мышление TDD.
Пиши тест-кейсы до начала ручного тестирования
Вместо исследовательского тестирования с нуля: сначала записывай ожидаемые поведения.
Feature: Password Reset
Что я ожидаю:
1. Ввод валидного email отправляет письмо сброса в течение 2 минут
2. Ввод невалидного email показывает "Email не найден"
3. Ссылка сброса истекает через 24 часа
4. Использование истёкшей ссылки показывает "Ссылка устарела" с опцией запросить новую
5. После успешного сброса старый пароль больше не работает
6. Ссылку сброса можно использовать только один разТест-план готов до начала тестирования.
Acceptance Test-Driven Development (ATDD)
Сотрудничай с разработчиками чтобы писать acceptance-тесты до того как фича построена:
// Написан до того как фича существует
test('checkout with valid card completes purchase', async ({ page }) => {
// Подготовка: пользователь с товарами в корзине
// ...
// Выполнить чекаут
await page.fill('[data-testid="card-number"]', '4242 4242 4242 4242');
await page.fill('[data-testid="card-expiry"]', '12/28');
await page.fill('[data-testid="card-cvc"]', '123');
await page.click('[data-testid="pay-now"]');
// Ожидаемые результаты
await expect(page).toHaveURL('/order-confirmation');
await expect(page.getByTestId('order-number')).toBeVisible();
await expect(page.getByTestId('success-message')).toContainText('Payment successful');
});Эти тесты падают пока фича не построена. Когда проходят: фича готова.
Когда разработчики практикуют TDD: что QA должен знать
Что меняется для QA
Покрытие юнит-тестами уже существует. Разработчики уже протестировали edge case на уровне кода. QA может фокусироваться на точках интеграции, пользовательских флоу и бизнес-логике, а не базовом поведении функций. Сбои тестов: информативные. Когда CI TDD-команды ломается: конкретно. "Функция X возвращает неверное значение для edge case Y", а не "приложение где-то сломано". Рефакторинг безопаснее. При всестороннем покрытии юнит-тестами QA не нужно перетестировать базовую функциональность после рефакторинга: тесты уже покрывают её.Что QA по-прежнему должен делать
Интеграционное тестирование. Юнит-тесты тестируют функции в изоляции. QA тестирует как компоненты работают вместе: база данных, API, фронтенд, система email. Тестирование с пользовательской перспективы. Разработчики тестируют "работает ли код?". QA тестирует "понятна ли фича в использовании?". Обнаружение edge case. Исследовательское тестирование QA находит сценарии которые не были специфицированы. TDD покрывает только то о чём думали при написании тестов. Производительность и надёжность. TDD не покрывает время ответа, одновременных пользователей или поведение под нагрузкой.Практический TDD для тест-автоматизации
При построении тест-фреймворка применяй принципы TDD:
// Вместо написания полного хелпера сначала: пишем тест для желаемого поведения
test('login helper should redirect to dashboard after login', async ({ page }) => {
await loginAs(page, 'member'); // Функция ещё не существует
await expect(page).toHaveURL('/dashboard');
});
// Теперь пишем минимальную реализацию
async function loginAs(page: Page, role: 'admin' | 'member') {
const credentials = {
admin: { email: 'admin@test.com', password: 'AdminPass1' },
member: { email: 'member@test.com', password: 'MemberPass1' },
};
await page.goto('/login');
await page.fill('[data-testid="email"]', credentials[role].email);
await page.fill('[data-testid="password"]', credentials[role].password);
await page.click('[data-testid="submit"]');
await page.waitForURL('/dashboard');
}Это предотвращает оверинжиниринг. Строишь ровно то что нужно, и тест подтверждает что работает.
Пирамида тестирования в контексте TDD
TDD напрямую формирует пирамиду тестирования:
/\
/E2E\ <- QA автоматизация (мало, медленно)
/------\
/ Integr.\ <- Интеграционные тесты (умеренно)
/----------\
/ Unit Tests \ <- Вывод TDD (много, быстро)
/--------------\TDD производит широкое основание юнит-тестов. QA строит интеграционный и E2E-слои сверху. Вместе они образуют пирамиду где большинство багов ловится дёшево на уровне юнит-тестов, с небольшим сфокусированным набором дорогих E2E-тестов.
Итог
- Цикл TDD: Red (падающий тест) → Green (сделать проходящим) → Refactor (почистить)
- TDD производит естественно тестируемый, хорошо структурированный код
- BDD расширяет TDD к бизнес-поведению через сценарии Given/When/Then
- ATDD: QA пишет acceptance-тесты до начала разработки: тесты падают пока фича не построена
- Когда разработчики практикуют TDD: QA сдвигает фокус на интеграцию, пользовательские флоу и edge case
- Применяй мышление TDD к тест-автоматизации: сначала пиши интерфейс теста, потом реализуй
TDD и QA дополняют друг друга. TDD ловит баги уровня юнит-тестов рано. QA ловит проблемы интеграции и пользовательского опыта. Вместе они производят ПО которое работает корректно на каждом уровне.
→ See also: Разработка через тестирование: руководство QA-инженера | Shift-left тестирование: что это значит и как практиковать | Пирамида тестирования: объяснение для QA-инженеров