← Назад к вопросам

Что такое принцип FIRST в тестировании?

2.7 Senior🔥 101 комментариев
#DevOps и инфраструктура#Django

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Принцип 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 — основа качественного тестирования и чистого кода.