Какие техники тест дизайна чаще всего используешь в работе?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Техники тест-дизайна в практике
В своей работе использую несколько техник, комбинируя их в зависимости от типа функционала и его сложности.
1. Граничные значения (Boundary Value Analysis)
Когда использую: когда функция работает с числовыми диапазонами, датами, размерами файлов.
Пример: Тестирование поля возраста пользователя (валидный диапазон 18-120 лет).
def test_age_validation():
# Граничные значения
assert validate_age(17) == False # Ниже границы
assert validate_age(18) == True # На нижней границе
assert validate_age(60) == True # Внутри диапазона
assert validate_age(120) == True # На верхней границе
assert validate_age(121) == False # Выше границы
Результат: покрыл критичные точки тремя тестами вместо сотни случайных значений.
2. Эквивалентное разбиение (Equivalence Partitioning)
Когда использую: когда входные данные можно разделить на классы, которые ведут себя одинаково.
Пример: тестирование статуса заказа.
def test_order_processing():
# Класс 1: новые заказы
assert process_order(status="new") == "processing"
# Класс 2: завершённые заказы
assert process_order(status="completed") == "archived"
# Класс 3: отменённые заказы
assert process_order(status="cancelled") == "archived"
# Класс 4: невалидные статусы
with pytest.raises(InvalidOrderStatus):
process_order(status="unknown")
Результат: вместо тестирования каждого статуса отдельно, проверяю представителя каждого класса.
3. Таблица решений (Decision Table)
Когда использую: когда есть несколько условий и их комбинации влияют на результат.
Пример: скидка для покупателя зависит от количества и суммы покупки.
# Таблица решений:
# Quantity | Amount | VIP | Expected Discount
# <=10 | <100 | No | 0%
# <=10 | <100 | Yes | 5%
# <=10 | >=100 | No | 5%
# <=10 | >=100 | Yes | 10%
# >10 | <100 | No | 5%
# >10 | <100 | Yes | 10%
# >10 | >=100 | No | 10%
# >10 | >=100 | Yes | 20%
import pytest
@pytest.mark.parametrize("quantity,amount,is_vip,expected", [
(5, 50, False, 0),
(5, 50, True, 5),
(5, 150, False, 5),
(5, 150, True, 10),
(15, 50, False, 5),
(15, 50, True, 10),
(15, 150, False, 10),
(15, 150, True, 20),
])
def test_discount_calculation(quantity, amount, is_vip, expected):
discount = calculate_discount(quantity, amount, is_vip)
assert discount == expected
Результат: систематически покрыл все комбинации условий.
4. Тестирование состояний и переходов (State Transition Testing)
Когда использую: когда объект проходит через разные состояния (заказ: новый → обработка → доставка → доставлен).
Пример: Payment Processing.
def test_payment_state_transitions():
payment = Payment()
# Валидные переходы
assert payment.status == "pending"
payment.authorize() # pending -> authorized
assert payment.status == "authorized"
payment.capture() # authorized -> captured
assert payment.status == "captured"
# Невалидные переходы (должны вызвать ошибку)
with pytest.raises(InvalidStateTransition):
payment.capture() # уже в captured
with pytest.raises(InvalidStateTransition):
payment.authorize() # уже прошли этот переход
Результат: проверил все корректные переходы и попытки неправильных переходов.
5. Pair-wise Testing (попарное тестирование)
Когда использую: когда параметров много, и нужно минимизировать количество тестов, но покрыть попарные взаимодействия.
Пример: Тестирование фильтров (Browser × OS × Resolution × Theme).
# Вместо 4 × 3 × 3 × 2 = 72 комбинации
# Попарное: ~10-15 тестов, покрывающих все пары
import pytest
from allpairspy import AllPairs
parameters = [
["Chrome", "Firefox", "Safari", "Edge"], # Browser
["Windows", "macOS", "Linux"], # OS
["1920x1080", "1366x768", "768x1024"], # Resolution
["light", "dark"], # Theme
]
pairs = list(AllPairs(parameters))
@pytest.mark.parametrize("browser,os,resolution,theme", pairs)
def test_app_compatibility(browser, os, resolution, theme):
driver = setup_driver(browser, os, resolution, theme)
# тест...
Результат: вместо 72 комбинаций — 15 тестов, но покрыл все попарные взаимодействия.
6. Mutation Testing
Когда использую: для проверки качества своих тестов (способны ли они поймать ошибки в коде).
# Инструмент: mutmut (Python)
pip install mutmut
mutmut run
mutmut results
Пример:
def calculate_bonus(salary: float, years: int) -> float:
if years < 1:
return 0
if years < 5:
return salary * 0.05
if years < 10:
return salary * 0.1
return salary * 0.15
def test_bonus():
assert calculate_bonus(1000, 0) == 0
assert calculate_bonus(1000, 2) == 50
assert calculate_bonus(1000, 7) == 100
assert calculate_bonus(1000, 15) == 150
mutmut попробует изменить код (например, years < 5 на years <= 5), и проверит, поймет ли тест эту мутацию. Если нет — у теста есть gaps.
7. Use Case / User Journey Testing
Когда использую: для end-to-end сценариев, реальных пользовательских путей.
def test_complete_purchase_flow():
"""Покупатель заходит в приложение и покупает товар"""
driver = get_driver()
# Use Case: Browse → Add to Cart → Checkout → Payment → Confirmation
home_page = HomePage(driver)
home_page.navigate()
search_page = home_page.search("laptop")
product_page = search_page.click_first_result()
cart_page = product_page.add_to_cart()
checkout_page = cart_page.proceed_to_checkout()
payment_page = checkout_page.fill_shipping_info()
confirmation_page = payment_page.pay_with_card("4111111111111111")
assert "Order Confirmed" in confirmation_page.get_title()
Результат: один тест проверил весь критичный путь, гарантируя, что компоненты работают вместе.
8. Exploratory Testing (в контексте автоматизации)
Когда использую: когда документация неполная или требования нечёткие, сначала исследую функционал ручно, потом автоматизирую.
Результат: разрабатываю умные, целенаправленные тесты вместо слепого автоматизирования документации.
Выбор техники: практический подход
| Тип функционала | Техника | Инструмент |
|---|---|---|
| Валидация числовых полей | Boundary Values | pytest.mark.parametrize |
| Сложная бизнес-логика | Decision Table | allpairspy |
| 워ークфлоу с состояниями | State Transition | pytest |
| Интеграционные тесты | Use Case Testing | Playwright / Selenium |
| Критичные пути | User Journey | Playwright |
| Качество тестов | Mutation Testing | mutmut |
Комбинирование техник
В реальных проектах часто комбинирую:
# Эквивалентное разбиение × Boundary Values
@pytest.mark.parametrize("age,expected_category", [
(0, "invalid"), # Граница: минимум
(1, "child"), # Класс: дети (1-17)
(17, "child"), # Граница: верх
(18, "adult"), # Граница: низ
(65, "adult"), # Класс: взрослые (18-64)
(64, "adult"), # Граница: верх
(65, "senior"), # Граница: низ
(150, "invalid"), # Граница: максимум
])
def test_age_categorization(age, expected_category):
assert categorize_age(age) == expected_category
Итог
Не использую одну технику на все случаи. Выбор зависит от:
- Типа функционала (простой → сложный)
- Количества параметров (мало → много)
- Критичности (низко → высоко)
- Времени на разработку (есть → нет)
Ключевая идея: минимум тестов, максимум покрытия рисков.