Как тестировал интервал анализа граничных значений
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как тестировал интервал анализа граничных значений
Boundary Value Analysis (BVA) — это техника тестирования, которая фокусируется на значениях на границах диапазонов, так как именно там часто находятся ошибки. Я расскажу, как я практически использую эту методику.
Суть Boundary Value Analysis
Большинство ошибок в коде находятся на границах интервалов. Например:
- Проверка
age > 18часто неправильно реализуется какage >= 18 - Проверка размера массива
length < 100может быть ошибочноlength <= 100
Если я тестирую возраст (должен быть от 18 до 65), я тестирую не просто случайные числа, а именно граничные значения.
Практический пример: интервал 18-65 лет
Определяю границы:
- Минимальная граница: 18 (включена)
- Максимальная граница: 65 (включена)
- Диапазон: [18, 65]
Граничные значения для тестирования:
| Тест | Значение | Ожидаемый результат | Тип ошибки, которую ловит |
|---|---|---|---|
| Ниже минимума | 17 | Отклонено (invalid) | Ошибка в условии > вместо >= |
| На минимуме | 18 | Принято (valid) | Правильно ли включена граница |
| Выше минимума | 19 | Принято (valid) | Не сломана ли валидация |
| Ниже максимума | 64 | Принято (valid) | Не сломана ли валидация |
| На максимуме | 65 | Принято (valid) | Правильно ли включена граница |
| Выше максимума | 66 | Отклонено (invalid) | Ошибка в условии < вместо <= |
Как я структурирую тесты
Approach 1: Отдельный тест на каждое значение
import pytest
def validate_age(age: int) -> bool:
"""Возвращает True если возраст в диапазоне [18, 65]"""
return 18 <= age <= 65
class TestAgeBoundaries:
"""Тесты граничных значений для валидации возраста"""
# Ниже минимума
def test_age_below_minimum(self):
"""Возраст 17 (на 1 ниже граници) должен быть отклонен"""
assert validate_age(17) == False
# На минимуме
def test_age_at_minimum_boundary(self):
"""Возраст 18 (точно на границе) должен быть принят"""
assert validate_age(18) == True
# Выше минимума (близко к границе)
def test_age_just_above_minimum(self):
"""Возраст 19 (на 1 выше границы) должен быть принят"""
assert validate_age(19) == True
# Ниже максимума
def test_age_just_below_maximum(self):
"""Возраст 64 (на 1 ниже границы) должен быть принят"""
assert validate_age(64) == True
# На максимуме
def test_age_at_maximum_boundary(self):
"""Возраст 65 (точно на границе) должен быть принят"""
assert validate_age(65) == True
# Выше максимума
def test_age_above_maximum(self):
"""Возраст 66 (на 1 выше границы) должен быть отклонен"""
assert validate_age(66) == False
Approach 2: Параметризированные тесты (более эффективно)
import pytest
class TestAgeBoundariesParametrized:
@pytest.mark.parametrize("age,expected", [
(17, False), # Ниже минимума
(18, True), # На минимуме
(19, True), # Выше минимума
(64, True), # Ниже максимума
(65, True), # На максимуме
(66, False), # Выше максимума
])
def test_age_validation(self, age: int, expected: bool):
"""Тестирует все граничные значения в одном параметризированном тесте"""
assert validate_age(age) == expected
Более сложный пример: диапазон доходов
Тестирую систему налогообложения, где налог зависит от дохода:
- 0-25 000 → 13%
- 25 001-50 000 → 20%
- 50 001-100 000 → 30%
- 100 001+ → 35%
Граничные значения:
def calculate_tax_rate(income: int) -> float:
if income <= 25_000:
return 0.13
elif income <= 50_000:
return 0.20
elif income <= 100_000:
return 0.30
else:
return 0.35
class TestTaxBoundaries:
@pytest.mark.parametrize("income,expected_rate", [
# Диапазон 1: [0, 25_000]
(24_999, 0.13), # Ниже первой границы
(25_000, 0.13), # На первой границе
(25_001, 0.20), # Переход на вторую ставку
# Диапазон 2: (25_000, 50_000]
(49_999, 0.20), # Ниже второй границы
(50_000, 0.20), # На второй границе
(50_001, 0.30), # Переход на третью ставку
# Диапазон 3: (50_000, 100_000]
(99_999, 0.30), # Ниже третьей границы
(100_000, 0.30), # На третьей границе
(100_001, 0.35), # Переход на четвёртую ставку
])
def test_tax_rate_calculation(self, income: int, expected_rate: float):
assert calculate_tax_rate(income) == expected_rate
Реальный пример: API тестирование с BVA
Тестирую endpoint для создания заказа, где минимальное количество товаров = 1, максимальное = 1000.
import pytest
from decimal import Decimal
from api_client import create_order
class TestOrderQuantityBoundaries:
"""Граничные тесты для количества товаров в заказе"""
@pytest.mark.parametrize("quantity,expected_status", [
(0, 400), # Ниже минимума — ошибка
(1, 201), # На минимуме — успех
(2, 201), # Выше минимума — успех
(999, 201), # Ниже максимума — успех
(1000, 201), # На максимуме — успех
(1001, 400), # Выше максимума — ошибка
])
def test_order_creation_with_boundary_quantities(self, quantity: int, expected_status: int):
response = create_order(
product_id="123",
quantity=quantity,
user_id="user_456"
)
assert response.status_code == expected_status
def test_order_quantity_validation_message(self):
"""Проверяю, что сообщение об ошибке правильное"""
response = create_order(
product_id="123",
quantity=0,
user_id="user_456"
)
assert response.status_code == 400
assert "Minimum quantity is 1" in response.json()["error"]
Комбинированные граничные тесты (Multiple Parameters)
Если функция зависит от нескольких параметров, я тестирую их комбинации на границах:
def calculate_shipping_cost(weight: int, distance: int) -> Decimal:
"""
weight: 1-100 кг
distance: 1-10000 км
"""
base_cost = Decimal(10)
weight_cost = Decimal(weight) * Decimal(0.5)
distance_cost = Decimal(distance) * Decimal(0.01)
return base_cost + weight_cost + distance_cost
class TestShippingCostBoundaries:
@pytest.mark.parametrize("weight,distance,scenario", [
# Минимальные значения
(1, 1, "min_weight_min_distance"),
# Максимальные значения
(100, 10000, "max_weight_max_distance"),
# Смешанные
(1, 10000, "min_weight_max_distance"),
(100, 1, "max_weight_min_distance"),
# На границе
(1, 5000, "min_weight_mid_distance"),
(50, 1, "mid_weight_min_distance"),
])
def test_shipping_cost_boundaries(self, weight: int, distance: int, scenario: str):
cost = calculate_shipping_cost(weight, distance)
assert cost >= Decimal(10) # Минимальная цена
# Дополнительные проверки по сценарию
Как я документирую BVA тесты
"""
Boundary Value Analysis для функции validate_age(age: int) -> bool
Диапазон: [18, 65]
Тестовые точки:
1. 17 (age = min - 1) → False
2. 18 (age = min) → True
3. 19 (age = min + 1) → True
4. 64 (age = max - 1) → True
5. 65 (age = max) → True
6. 66 (age = max + 1) → False
Ожидаемые ошибки, которые ловят эти тесты:
- Off-by-one errors (age > 18 вместо age >= 18)
- Неправильные операторы (< вместо <=)
- Copy-paste ошибки в условиях
"""
Инструменты для автоматизации BVA
# Property-based testing (автоматически генерирует граничные случаи)
pip install hypothesis
from hypothesis import given, strategies as st
class TestAgeWithHypothesis:
@given(st.integers(min_value=18, max_value=65))
def test_valid_ages(self, age):
assert validate_age(age) == True
@given(st.integers(max_value=17))
def test_invalid_ages_below(self, age):
assert validate_age(age) == False
@given(st.integers(min_value=66))
def test_invalid_ages_above(self, age):
assert validate_age(age) == False
Мой чеклист для BVA тестирования
- ✅ Определить все диапазоны значений
- ✅ Найти все граничные точки (min, max)
- ✅ Создать тест на каждую граничную точку (n-1, n, n+1)
- ✅ Определить эквивалентные классы значений
- ✅ Протестировать комбинации если параметров несколько
- ✅ Проверить error messages на граничных значениях
- ✅ Использовать параметризированные тесты для масштабируемости
- ✅ Документировать BVA стратегию
- ✅ Убедиться, что тесты ловят off-by-one ошибки
- ✅ Запустить тесты на разных окружениях
Boundary Value Analysis — это один из самых эффективных методов тестирования, потому что большинство ошибок находятся именно на границах. Опытный QA всегда применяет эту технику при тестировании любого функционала с числовыми или диапазонными параметрами.