Что такое принцип FIRST в тестировании?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Принцип FIRST в тестировании
FIRST — это аббревиатура, которая определяет пять ключевых критериев, которым должны соответствовать хорошие автоматические тесты. Этот принцип был сформулирован Робертом Мартином (Uncle Bob) и помогает писать эффективные, надёжные и поддерживаемые тесты.
Аббревиатура FIRST
F — Fast (Быстрые)
Тесты должны выполняться очень быстро (миллисекунды, максимум несколько секунд).
Почему это важно:
- Разработчик часто запускает тесты (на каждое изменение)
- Медленные тесты отбивают охоту к тестированию
- CI/CD время выполнения критично
Как сделать тесты быстрыми:
# Плохо: обращение к БД, сетевые запросы
def test_user_creation():
import requests
response = requests.post('https://api.example.com/users',
json={'name': 'John'})
assert response.status_code == 201 # МЕДЛЕННО!
# Хорошо: моки и заглушки
import unittest.mock as mock
def test_user_creation():
with mock.patch('requests.post') as mock_post:
mock_post.return_value.status_code = 201
# Выполнится за миллисекунды
Рекомендуемое время: unit тесты < 100 ms, интеграционные < 1 сек.
I — Independent (Независимые)
Тесты должны быть независимы друг от друга. Порядок выполнения не должен влиять на результат.
Почему это важно:
- Легче найти и исправить проблемы
- Можно запускать тесты в параллели
- Результат предсказуем
Неправильно: зависимые тесты
class TestUser:
user_id = None # Глобальное состояние
def test_1_create_user(self):
# Создаём пользователя
TestUser.user_id = 123
def test_2_update_user(self):
# Зависит от test_1!
user = User.objects.get(id=TestUser.user_id) # ОШИБКА если тесты в другом порядке
Правильно: независимые тесты
def test_create_user():
user = User.objects.create(name="John")
assert user.id is not None
def test_update_user():
user = User.objects.create(name="John") # Создаём в каждом тесте
user.name = "Jane"
user.save()
assert user.name == "Jane"
Или используй fixtures для повторного использования данных:
import pytest
@pytest.fixture
def user():
return User.objects.create(name="John")
def test_create_user(user):
assert user.id is not None
def test_update_user(user): # Новый экземпляр для каждого теста
user.name = "Jane"
user.save()
assert user.name == "Jane"
R — Repeatable (Повторяемые)
Тесты должны давать одинаковый результат при каждом запуске (на разных машинах, в разное время).
Почему это важно:
- Нельзя доверять тесту, который "иногда" падает
- Затрудняет CI/CD
- Сложно отлаживать
Проблемы и решения:
# Плохо: зависит от текущего времени
def test_expiration():
token = generate_token()
import time
time.sleep(1) # Ненадежно: может выполниться быстрее
assert token.is_expired()
# Хорошо: используй frozen time
import freezegun
@freezegun.freeze_time('2023-01-01 10:00:00')
def test_expiration():
token = generate_token(expires_in_seconds=0)
assert token.is_expired()
# Плохо: зависит от случайности
def test_random_generator():
value = random_int(1, 100)
assert value > 0 # Работает, но не проверяет логику
# Хорошо: используй детерминированные значения
def test_random_generator():
import random
random.seed(42) # Фиксируем seed
value = random_int(1, 100)
assert 1 <= value <= 100
S — Self-checking (Самопроверяющиеся)
Тест должен сам определить, прошёл он или нет. Не нужны ручные проверки.
Почему это важно:
- Полная автоматизация
- Явные ожидания
- Легко понять, что сломалось
Неправильно: требует ручной проверки
def test_api_response():
response = requests.get('https://api.example.com/users')
print(response.json()) # Нужно смотреть в консоль и проверить вручную!
# Нет явных утверждений
Правильно: явные проверки
def test_api_response():
response = requests.get('https://api.example.com/users')
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
assert len(data) > 0
assert 'id' in data[0]
T — Timely (Своевременные)
Тесты должны быть написаны непосредственно перед кодом (TDD) или как минимум вскоре после.
Почему это важно:
- Тесты в конце часто не пишутся вообще
- Проще тестировать, пока помнишь требования
- TDD улучшает дизайн кода
Правильный порядок (TDD):
# Шаг 1: Написать падающий тест (RED)
def test_calculate_discount():
price = 100
discount = calculate_discount(price, 0.1) # Функция ещё не существует
assert discount == 10 # Падает!
# Шаг 2: Написать минимальный код (GREEN)
def calculate_discount(price, rate):
return price * rate
# Шаг 3: Рефакторить (REFACTOR)
def calculate_discount(price: float, discount_rate: float) -> float:
"""Вычисляет размер скидки.
Args:
price: исходная цена
discount_rate: процент скидки (0.0-1.0)
Returns:
размер скидки
"""
if not 0 <= discount_rate <= 1:
raise ValueError("Скидка должна быть между 0 и 1")
return round(price * discount_rate, 2)
Практический пример: хороший тест (FIRST)
import pytest
from unittest.mock import patch, MagicMock
def calculate_total_price(items: list[dict]) -> float:
"""Вычисляет общую цену корзины."""
return sum(item['price'] * item['quantity'] for item in items)
# Fast: нет I/O, выполняется быстро
# Independent: данные создаются в самом тесте
# Repeatable: детерминированный результат
# Self-checking: явное assert
# Timely: написан сразу после функции
def test_calculate_total_price():
items = [
{'price': 100, 'quantity': 2},
{'price': 50, 'quantity': 1},
]
result = calculate_total_price(items)
expected = 100 * 2 + 50 * 1 # 250
assert result == expected
def test_calculate_total_price_empty():
assert calculate_total_price([]) == 0
def test_calculate_total_price_single_item():
items = [{'price': 99.99, 'quantity': 1}]
assert calculate_total_price(items) == 99.99
Чеклист FIRST
- ✓ Тест выполняется менее чем за 100ms (Fast)
- ✓ Можно запустить в любом порядке (Independent)
- ✓ Результат одинаков каждый раз (Repeatable)
- ✓ Содержит явные assert'ы (Self-checking)
- ✓ Написан до или сразу после кода (Timely)
Принцип FIRST — основа качественного тестирования и чистого кода.