Введение
Представьте ситуацию: ваш CI/CD пайплайн падает. Вы перезапускаете тесты — они проходят. Через час снова падение на том же тесте. Вы не меняли код. Что происходит?
Это Flaky tests — нестабильные тесты, которые иногда проходят, иногда падают без явных причин. По статистике Google, в крупных проектах до 16% тестов являются Flaky. Это тормозит разработку, подрывает доверие к тестам и стоит огромных денег.
💸 Цена нестабильных тестов
Реальная статистика:
- ⏱ 2-5 часов в неделю на перезапуск тестов (каждый разработчик)
- 🔄 30% времени CI/CD тратится на повторные запуски
- 😤 Потеря доверия: команда перестает верить тестам
- 🐛 Пропущенные баги: "А, это опять Flaky, игнорим"
- 💰 $100,000+ в год для команды из 10 человек (упущенная производительность)
В этой статье вы узнаете:
- ✅ Что такое Flaky tests и почему они возникают
- ✅ 5 главных причин нестабильности API тестов
- ✅ Как Mock API решает проблему на 100%
- ✅ Детерминированные тесты: теория и практика
- ✅ Примеры кода до/после
- ✅ Метрики стабильности тестов
📋 Содержание
Что такое Flaky tests
📖 Определение
Flaky test (нестабильный тест) — это тест, который иногда проходит, иногда падает, при этом код приложения не менялся. Тест дает разные результаты при одинаковых условиях.
Признаки Flaky теста
| Признак | Описание | Как проявляется |
|---|---|---|
| 🔄 Нестабильность | Тест падает случайно | Сегодня ✅, завтра ❌, послезавтра ✅ |
| 🎲 Непредсказуемость | Невозможно предсказать результат | Локально ✅, в CI ❌ |
| 🔁 Повторный запуск помогает | Тест проходит после перезапуска | "Перезапусти — должно пройти" |
| 🤷 Нет явной причины | Код не менялся | "Почему он упал? Не знаю" |
Типичный сценарий
$ npm test
✓ User login test
✓ Create order test
✗ Get order details test // Упал!
$ npm test // Перезапуск без изменений
✓ User login test
✓ Create order test
✓ Get order details test // Прошел! 🤔
// Через час
✗ Get order details test // Снова упал! 😡
🎯 Масштаб проблемы
Исследование Google (2016):
- 16% всех тестов в Google — Flaky
- 84% разработчиков сталкивались с Flaky tests
- 66% считают их серьезной проблемой
Исследование Microsoft (2020):
- 30% времени CI/CD уходит на повторные запуски
- Flaky tests снижают производительность на 20-40%
5 причин нестабильности API тестов
1. Зависимость от внешних API
Проблема: Тесты зависят от реальных внешних API (платежи, уведомления, погода, карты и т.д.)
⚠️ Что может пойти не так:
- API недоступен: Сервис упал, maintenance, DDoS
- Rate limiting: Превышен лимит запросов
- Изменения API: Провайдер изменил структуру ответа
- Медленные ответы: Таймаут из-за перегрузки сервера
- Нестабильная сеть: Потеря пакетов, высокий ping
Пример Flaky теста:
// ❌ Нестабильный тест
test('Should get weather data', async () => {
// Зависит от внешнего API
const response = await fetch('https://api.weather.com/forecast');
const data = await response.json();
expect(data.temperature).toBeGreaterThan(0);
// ⚠️ API может:
// - Не ответить (timeout)
// - Вернуть ошибку 429 (rate limit)
// - Быть недоступным
});
2. Race Conditions и асинхронность
Проблема: Тесты не учитывают асинхронную природу API запросов.
// ❌ Race condition
test('Should create and retrieve order', async () => {
// Создаем заказ
await fetch('/api/orders', {
method: 'POST',
body: JSON.stringify({ item: 'Laptop' })
});
// Сразу пытаемся получить
const response = await fetch('/api/orders/latest');
const order = await response.json();
expect(order.item).toBe('Laptop');
// ⚠️ Может упасть, если:
// - База не успела обновиться
// - Есть задержка репликации
// - Кэш не инвалидировался
});
3. Случайные и динамические данные
Проблема: API возвращает разные данные при каждом запросе.
// ❌ Нестабильные данные
test('Should get user profile', async () => {
const response = await fetch('/api/users/123');
const user = await response.json();
// ⚠️ Поля могут меняться:
expect(user.id).toBe('123');
expect(user.lastLogin).toBe('2025-10-20T10:00:00Z'); // Разное каждый раз!
expect(user.notifications).toHaveLength(5); // Может быть 4, 5, 6...
expect(user.sessionToken).toBe('abc123'); // Новый каждый раз!
});
4. Таймауты и медленные API
Проблема: API отвечает медленно, тесты падают по таймауту.
// ❌ Таймауты
test('Should process payment', async () => {
const response = await fetch('/api/payments', {
method: 'POST',
body: JSON.stringify({ amount: 100 })
});
// ⚠️ Может упасть если:
// - API отвечает медленно (> 5 сек)
// - База данных под нагрузкой
// - Очередь обработки заполнена
}, 5000); // timeout 5 секунд
5. Нестабильное окружение (Staging/Dev)
Проблема: Тесты зависят от staging/dev окружения, которое нестабильно.
⚠️ Проблемы staging окружения:
- Деплой во время тестов: Окружение обновляется
- Shared state: Другие разработчики меняют данные
- Нехватка ресурсов: Staging сервер перегружен
- Неполные данные: База не полностью синхронизирована
- Конфликты версий: Backend обновлен, frontend — нет
Как Mock API решает проблему
Mock API предоставляет полный контроль над API ответами, устраняя все 5 причин нестабильности.
✅ Преимущества Mock API для стабильных тестов
- Детерминированные ответы: Одинаковые данные при каждом запуске
- Нет внешних зависимостей: Не зависит от интернета, сторонних сервисов
- Мгновенные ответы: Нет таймаутов, всегда быстро
- Контроль над данными: Точно знаете, что вернется
- Изоляция: Каждый тест работает независимо
- 100% доступность: Работает всегда, без сбоев
Сравнение: Real API vs Mock API
| Аспект | Real API | Mock API |
|---|---|---|
| Доступность | ❌ 95-99% | ✅ 100% |
| Скорость ответа | ❌ 100-2000ms | ✅ 10-50ms |
| Детерминированность | ❌ Разные данные | ✅ Одинаковые всегда |
| Rate limiting | ❌ Да (100-1000 req/min) | ✅ Нет ограничений |
| Зависимость от сети | ❌ Требует интернет | ✅ Работает офлайн |
| Стабильность | ❌ 70-85% | ✅ 100% |
Детерминированные тесты
📖 Что такое детерминированный тест?
Детерминированный тест — это тест, который при одинаковых входных данных всегда возвращает один и тот же результат. Нет случайности, нет зависимости от внешних факторов.
Принципы детерминированных тестов
- Изолированность: Тест не зависит от других тестов
- Воспроизводимость: Можно запустить 100 раз — 100 раз одинаковый результат
- Предсказуемость: Известно, что вернет API
- Независимость: Не зависит от времени, сети, внешних API
Примеры до/после
Пример 1: Тестирование создания заказа
❌ ДО (Flaky test)
test('Create order', async () => {
// Реальный API
const res = await fetch(
'https://staging.myapi.com/orders',
{
method: 'POST',
body: JSON.stringify({
userId: 123,
item: 'Laptop'
})
}
);
const order = await res.json();
// ⚠️ Проблемы:
// - Staging может быть недоступен
// - userId 123 может не существовать
// - Таймаут если сервер медленный
// - Конфликт если другой тест
// уже создал заказ
expect(order.id).toBeDefined();
expect(order.status).toBe('pending');
});
// 🎲 Стабильность: 60-70%
✅ ПОСЛЕ (Stable test)
test('Create order', async () => {
// Mock API
const res = await fetch(
'https://yourdomain.lightboxapi.ru/orders',
{
method: 'POST',
body: JSON.stringify({
userId: 123,
item: 'Laptop'
})
}
);
const order = await res.json();
// ✅ Всегда возвращает:
// {
// "id": "order_123",
// "userId": 123,
// "item": "Laptop",
// "status": "pending",
// "createdAt": "2025-10-20T10:00:00Z"
// }
expect(order.id).toBe('order_123');
expect(order.status).toBe('pending');
});
// ✅ Стабильность: 100%
Пример 2: Интеграционный тест с платежами
❌ ДО (Flaky test)
test('Process payment', async () => {
// Реальный Stripe API
const payment = await stripe.charges.create({
amount: 2000,
currency: 'usd',
source: 'tok_visa'
});
// ⚠️ Проблемы:
// - Stripe может быть недоступен
// - Rate limiting (100 req/sec)
// - Таймауты при нагрузке
// - Случайные ошибки сети
// - Стоимость: $0.01 за тест
expect(payment.status).toBe('succeeded');
});
// 🎲 Стабильность: 75-80%
// 💰 Стоимость: $10/месяц (1000 тестов)
✅ ПОСЛЕ (Stable test)
test('Process payment', async () => {
// Mock Stripe API
const payment = await fetch(
'https://yourdomain.lightboxapi.ru/stripe/charges',
{
method: 'POST',
body: JSON.stringify({
amount: 2000,
currency: 'usd',
source: 'tok_visa'
})
}
).then(r => r.json());
// ✅ Детерминированный ответ
// Мгновенно, бесплатно, стабильно
expect(payment.status).toBe('succeeded');
expect(payment.amount).toBe(2000);
});
// ✅ Стабильность: 100%
// 💰 Стоимость: $0
Пример 3: E2E тест с Cypress
❌ ДО (Flaky test)
// cypress/e2e/orders.cy.js
describe('Order flow', () => {
it('Creates and views order', () => {
cy.visit('/orders/new');
cy.get('#item').type('Laptop');
cy.get('#submit').click();
// ⚠️ Ждем ответа от реального API
cy.wait(3000); // Race condition!
cy.get('.order-id')
.should('be.visible');
// ⚠️ Может упасть если:
// - API медленно отвечает
// - Staging недоступен
// - Database под нагрузкой
});
});
// 🎲 Стабильность: 65-70%
✅ ПОСЛЕ (Stable test)
// cypress/e2e/orders.cy.js
describe('Order flow', () => {
beforeEach(() => {
// Перехватываем API с Mock
cy.intercept('POST', '/api/orders', {
statusCode: 200,
body: {
id: 'order_123',
status: 'pending'
},
delay: 100 // Детерминированная задержка
}).as('createOrder');
});
it('Creates and views order', () => {
cy.visit('/orders/new');
cy.get('#item').type('Laptop');
cy.get('#submit').click();
// ✅ Ждем Mock ответ
cy.wait('@createOrder');
cy.get('.order-id')
.should('contain', 'order_123');
});
});
// ✅ Стабильность: 100%
Метрики стабильности тестов
Как измерять Flaky tests
📊 Главные метрики:
- Flaky Rate: % тестов, которые нестабильны
- Rerun Rate: Сколько раз перезапускаем тесты
- Pass Rate: % успешных прогонов
- Mean Time to Green: Среднее время до успешного прогона
Формула Flaky Rate
Flaky Rate = (Количество Flaky тестов / Общее количество тестов) × 100%
Пример:
- Всего тестов: 500
- Flaky тестов: 50
- Flaky Rate = (50 / 500) × 100% = 10%
Бенчмарки стабильности
| Flaky Rate | Оценка | Действия |
|---|---|---|
| < 5% | ✅ Отлично | Поддерживать уровень |
| 5-10% | ⚠️ Приемлемо | Планировать улучшения |
| 10-20% | ❌ Проблема | Срочно стабилизировать |
| > 20% | 🔥 Критично | Приостановить новые фичи, фокус на стабилизации |
Реальные результаты с Mock API
Снижение (с 30% до 0.6%)
с 12 мин до 3 мин
с 50/день до 2/день
Команда верит тестам
Кейс: Команда из 8 разработчиков
📈 ДО внедрения Mock API:
- 500 API тестов
- Flaky Rate: 30% (150 нестабильных тестов)
- Время прогона: 12 минут
- Reruns в день: 50 (каждый разработчик — 6 раз)
- Потеря времени: 4 часа/день (вся команда)
- Стоимость: $48,000/год (упущенная производительность)
📈 ПОСЛЕ внедрения Mock API:
- 500 API тестов
- Flaky Rate: 0.6% (3 нестабильных теста)
- Время прогона: 3 минуты (-75%)
- Reruns в день: 2 (-96%)
- Потеря времени: 15 минут/день
- Экономия: $45,600/год
Best Practices
1. Изолируйте тесты от внешних зависимостей
✅ Правило:
Каждый тест должен быть самодостаточным. Не зависеть от:
- Интернета
- Внешних API
- Состояния базы данных
- Других тестов
- Времени суток
- Случайных данных
2. Используйте фиксированные данные
// ❌ Плохо: Случайные данные
const userId = Math.random().toString();
const timestamp = new Date().toISOString();
// ✅ Хорошо: Фиксированные данные
const userId = 'user_123';
const timestamp = '2025-10-20T10:00:00Z';
3. Контролируйте время
// ❌ Плохо: Зависит от реального времени
expect(user.createdAt).toBe(new Date().toISOString());
// ✅ Хорошо: Mock времени
jest.useFakeTimers();
jest.setSystemTime(new Date('2025-10-20T10:00:00Z'));
expect(user.createdAt).toBe('2025-10-20T10:00:00Z');
4. Логируйте и мониторьте Flaky tests
# .github/workflows/tests.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run tests
run: npm test
- name: Upload test results
if: always()
uses: actions/upload-artifact@v3
with:
name: test-results
path: test-results.xml
# Отслеживание Flaky tests
- name: Detect Flaky Tests
uses: test-analytics/action@v1
with:
results: test-results.xml
5. Автоматически помечайте Flaky тесты
// jest.config.js
module.exports = {
testRunner: 'jest-circus/runner',
reporters: [
'default',
['jest-flaky-test-reporter', {
threshold: 2, // Помечать после 2 падений
markAsFlaky: true
}]
]
};
FAQ
❓ Как быстро найти все Flaky тесты?
Ответ: Запустите все тесты 10 раз подряд:
# Bash
for i in {1..10}; do npm test; done
# Или с Jest
npm test -- --repeat=10
Тесты, которые хотя бы раз упали — Flaky. Или используйте инструменты:
- Flaky Test Detection (GitHub Actions)
- BuildPulse (CI/CD аналитика)
- Datadog CI (мониторинг тестов)
❓ Можно ли использовать Mock API только для Flaky тестов?
Ответ: Да! Гибридный подход:
- Unit тесты: Всегда Mock API (изоляция)
- Integration тесты: Mock API для нестабильных зависимостей
- E2E тесты: 90% Mock API, 10% real API (smoke tests)
❓ Не потеряем ли мы покрытие, используя Mock API?
Ответ: Нет, если правильно спроектировать тесты:
- Mock API тесты: Проверяют логику вашего приложения (95% тестов)
- Contract Tests: Проверяют, что Mock соответствует реальному API
- Smoke Tests: 5-10 критических сценариев с real API (раз в день)
Результат: Покрытие увеличивается, т.к. тесты стабильны и команда им доверяет.
❓ Как убедить команду перейти на Mock API?
Ответ: Покажите цифры:
- Измерьте текущий Flaky Rate
- Посчитайте стоимость (время × ставка)
- Сделайте pilot с 10 самыми проблемными тестами
- Покажите результаты: -95% Flaky, -70% времени
Заключение
🎯 Главные выводы
- Flaky tests — серьезная проблема: Стоят $100,000+ в год для команды из 10 человек
- 5 причин нестабильности: Внешние API, race conditions, случайные данные, таймауты, нестабильное окружение
- Mock API — решение: 100% стабильные, детерминированные тесты
- Детерминированность: Одинаковые входные данные → одинаковый результат
- Измеряйте метрики: Flaky Rate < 5% — отлично
- Best Practices: Изолируйте тесты, фиксируйте данные, мониторьте стабильность
🚀 План действий
- Неделя 1: Измерьте Flaky Rate (запустите тесты 10 раз)
- Неделя 2: Выберите 10 самых проблемных тестов
- Неделя 3-4: Настройте Mock API для этих тестов
- Неделя 5: Измерьте результаты, покажите команде
- Неделя 6+: Мигрируйте остальные тесты
Цель: Flaky Rate < 5% за 2 месяца
🎯 Попробуйте LightBox API
Хотите избавиться от Flaky tests за неделю?
LightBox API — это Mock API платформа для стабильных тестов.
- ✓ Детерминированные ответы (100% стабильность)
- ✓ Импорт Swagger/Postman за 2 минуты
- ✓ Логирование всех запросов
- ✓ Unlimited requests (без rate limiting)
- ✓ Интеграция с CI/CD (GitHub Actions, GitLab CI)
Статья обновлена: 20 октября 2025
Автор: LightBox API Team