Признаки хорошего теста

Тесты, которым доверяют: быстрые, независимые, стабильные и понятные.

Хороший тест — быстрый, изолированный, детерминированный и читаемый. Эти свойства часто запоминают аббревиатурой FIRST: Fast, Isolated, Repeatable, Self-validating, Timely.

Четыре главных свойства

  • Быстрый (Fast). Тысячи тестов должны проходить за секунды. Медленный набор перестают запускать — и он умирает.
  • Изолированный (Isolated). Тест не зависит от других тестов и порядка запуска. Каждый сам готовит своё состояние.
  • Детерминированный (Repeatable). Один и тот же тест на одном коде всегда даёт один результат — без зависимости от времени, случайности, сети.
  • Читаемый и самопроверяющийся (Self-validating). Тест сам говорит «прошёл/не прошёл» через assert, и из него понятно, что проверяется.

Изолированность на примере

Плохо, когда тесты делят общее изменяемое состояние — порядок запуска начинает влиять на результат. Хорошо — когда каждый тест создаёт своё.

def add_item(cart, item):
    cart.append(item)
    return cart


# ИЗОЛИРОВАННЫЕ тесты: каждый создаёт свою корзину
def test_first_item():
    cart = []                       # своё состояние
    add_item(cart, "книга")
    assert cart == ["книга"]

def test_second_item():
    cart = []                       # независимо от первого теста
    add_item(cart, "ручка")
    assert cart == ["ручка"]

test_first_item()
test_second_item()
print("Оба теста изолированы: порядок запуска не влияет на результат")

Вывод:

Оба теста изолированы: порядок запуска не влияет на результат

Детерминированность

Тест, опирающийся на random или текущее время без фиксации, иногда зелёный, иногда красный — это флаки-тест (о нём в следующем уроке). Детерминированный тест фиксирует все источники недетерминизма: передаёт время и случайность снаружи или задаёт seed.

import random

def shuffle_deck(seed):
    """Перемешивает колоду детерминированно по seed."""
    deck = list(range(1, 6))
    rnd = random.Random(seed)       # фиксируем источник случайности
    rnd.shuffle(deck)
    return deck


# Детерминированно: один seed -> всегда один результат
assert shuffle_deck(42) == shuffle_deck(42)
print("Детерминированный тест: одинаковый seed даёт одинаковый результат")

Вывод:

Детерминированный тест: одинаковый seed даёт одинаковый результат

Итог

  • Хороший тест: быстрый, изолированный, детерминированный, читаемый (FIRST).
  • Изоляция — своё состояние в каждом тесте, независимость от порядка.
  • Детерминизм — фиксируем время и случайность, иначе получим флаки-тесты.
Проверьте себя
1. Что значит «изолированный» тест?
AОн запускается на отдельном сервере
BОн не зависит от других тестов и от порядка запуска, сам готовит своё состояние
CОн проверяет изоляцию проводов
DОн написан в отдельном файле
2. Почему важна детерминированность теста?
AЧтобы тест был длиннее
BЧтобы на одном коде он всегда давал один результат, а не «иногда красный»
CЧтобы использовать больше random
DЧтобы зависеть от текущего времени
3. Что расшифровывает аббревиатура FIRST применительно к тестам?
AСтиль форматирования кода
BFast, Isolated, Repeatable, Self-validating, Timely
CНазвание фреймворка
DЭтапы релиза
Поддержать проект