Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Пирамида тестирования
Пирамида тестирования — это концепция, которая показывает оптимальное распределение тестов по их типам и уровням. Впервые её популяризировал Mike Cohn в своей книге "Succeeding with Agile".
Классическая пирамида (3 уровня)
^^
/ \
/ E2E \ 5% (1-2 часа выполнения)
/______\
/ \
/Integration\ 15% (15-30 минут)
/___________\
/ \
/ Unit Tests \ 80% (секунды)
/__________________\
Уровни пирамиды
1. Unit Tests (основание, 80%)
Что: тесты отдельных функций/методов в изоляции.
Преимущества:
- Очень быстрые (выполняются за ms)
- Дешёвые в поддержке
- Много их можно написать
- Выявляют баги на ранних стадиях
- Просто отладить при падении
Недостатки:
- Не тестируют интеграцию компонентов
- Не имитируют реальные сценарии
Пример:
import pytest
from calculator import Calculator
@pytest.mark.parametrize("a,b,expected", [
(2, 3, 5),
(10, -5, 5),
(0, 0, 0),
])
def test_add(a, b, expected):
calc = Calculator()
assert calc.add(a, b) == expected
def test_divide_by_zero():
calc = Calculator()
with pytest.raises(ZeroDivisionError):
calc.divide(10, 0)
Инструменты: pytest, unittest, Jest, Vitest
2. Integration Tests (середина, 15%)
Что: тесты взаимодействия нескольких компонентов (например, микросервис + база данных).
Преимущества:
- Проверяют реальные взаимодействия
- Выявляют проблемы интеграции
- Выполняются относительно быстро
Недостатки:
- Медленнее unit тестов
- Требуют настройки окружения (БД, очереди, API моки)
- Сложнее отладить при падении
Пример:
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from app.models import User
from app.services import UserService
@pytest.fixture
def test_db():
engine = create_engine('sqlite:///:memory:')
Session.configure(bind=engine)
Base.metadata.create_all(engine)
return Session()
def test_create_user_in_database(test_db):
"""Тест взаимодействия UserService и БД"""
service = UserService(test_db)
user = service.create_user(name="Alice", email="alice@test.com")
# Проверяем, что пользователь реально сохранён в БД
saved_user = test_db.query(User).filter_by(email="alice@test.com").first()
assert saved_user is not None
assert saved_user.name == "Alice"
Тестирование API:
from fastapi.testclient import TestClient
from app import app
client = TestClient(app)
def test_get_users_api():
response = client.get("/api/users")
assert response.status_code == 200
assert len(response.json()) > 0
def test_create_user_api():
response = client.post("/api/users", json={
"name": "Bob",
"email": "bob@test.com"
})
assert response.status_code == 201
assert response.json()["id"] is not None
Инструменты: pytest, requests, httpx, TestClient (FastAPI)
3. End-to-End Tests (вершина, 5%)
Что: тесты полного пользовательского сценария через интерфейс приложения.
Преимущества:
- Тестируют реальный пользовательский сценарий
- Выявляют проблемы, которые юнит и интеграционные не видят
- Дают уверенность перед production deployment
Недостатки:
- Очень медленные (1-5 минут за тест)
- Нестабильные (flaky) из-за асинхронности
- Дорогие в поддержке
- Сложно отладить
- Требуют запущенного приложения
Пример:
import pytest
from playwright.sync_api import sync_playwright
def test_complete_checkout_flow():
"""E2E тест: юзер добавляет товар в корзину и покупает"""
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
# Переход на сайт
page.goto("https://shop.example.com")
# Поиск товара
page.fill("input[placeholder='Search']", "laptop")
page.click("button[type='submit']")
page.click("text=Laptop Pro")
# Добавление в корзину
page.click("button:has-text('Add to Cart')")
page.click("a:has-text('View Cart')")
# Оформление
page.click("button:has-text('Proceed to Checkout')")
page.fill("input[name='email']", "customer@test.com")
page.fill("input[name='card']", "4111111111111111")
page.click("button:has-text('Complete Purchase')")
# Проверка
assert "Order Confirmed" in page.text_content()
browser.close()
Инструменты: Playwright, Cypress, Selenium, Puppeteer
Почему пирамида, а не куб?
Анти-паттерн: Ледяной конус (Ice Cream Cone)
^
/|\
/ | \
/E2E \
/______\
/ \
/ Integration
/____________\
| | <- Много ручного тестирования
| Manual Tests |
|______________|
Проблемы:
- Медленная обратная связь (часы вместо секунд)
- Дорого поддерживать
- Мало unit тестов = много багов пропускают
- Большинство тестов падают медленно (E2E)
Современная вариация: Пирамида с API слоем
В modern приложениях часто добавляют слой API тестов:
/\
/E2E\
/______\
/ \
/ API \ <- API Contract Testing, GraphQL
/___________\
/ \
/Integration \
/________________\
/ \
/ Unit Tests \
/____________________\
API тесты:
- Быстрее E2E, но медленнее unit
- Проверяют контракт между сервисами
- Идеальны для микросервисной архитектуры
import requests
def test_api_response_format():
"""Contract testing: проверяем формат API ответа"""
response = requests.get("https://api.example.com/users/123")
assert response.status_code == 200
data = response.json()
# Структура ответа должна быть точной
assert "id" in data
assert "name" in data
assert "email" in data
assert isinstance(data["id"], int)
assert isinstance(data["name"], str)
Практические метрики пирамиды
| Тип | Кол-во | Время выполнения |
|---|---|---|
| Unit | 80-85% | Секунды (5-10 мин total) |
| Integration | 10-15% | Минуты (10-20 мин total) |
| E2E | 5-10% | ~1 мин на тест (10-20 мин total) |
| Total | 100% | ~30-40 минут full suite |
Как я применяю пирамиду в практике
Фаза 1: Coverage Analysis
# Смотрю current state
pytest --cov=app --cov-report=html
# Если покрытие < 70% → нужны unit тесты
Фаза 2: Построение base
# Сначала пишу unit тесты на критичную бизнес-логику
def test_calculate_price():
"""Unit: функция расчёта скидки"""
assert calculate_discount(100, vip=False) == 100
assert calculate_discount(100, vip=True) == 90
Фаза 3: Integration layer
# Затем тестирую взаимодействие с БД, API
def test_create_order_with_discount():
"""Integration: создание заказа в БД с применением скидки"""
order = OrderService().create(amount=100, vip=True)
assert order.final_price == 90
Фаза 4: E2E критичные пути
# И только для самых критичных путей — E2E
def test_user_can_purchase():
"""E2E: полный сценарий покупки через UI"""
# ...
Красные флаги (когда пирамида сломана)
🚩 Много E2E тестов, мало unit тестов 🚩 Все тесты fall медленнее 5 минут 🚩 50%+ тестов flaky 🚩 Время на запуск full suite > 1 часа 🚩 Тесты никто не смотрит (CI зелёный всегда)
Заключение
Пирамида тестирования — не догма, а руководство:
- Unit — фундамент, должны быть быстрыми и надёжными
- Integration — проверяют реальные взаимодействия
- E2E — страховка для критичных путей
Оптимальное распределение зависит от проекта, но правило простое: максимум быстрых тестов, минимум медленных.