Тест-дублёры и хрупкие тесты
Как изолировать тест от медленных и непредсказуемых зависимостей и не сделать его при этом хрупким.
Тест-дублёры (mock, stub, fake) — это подставные объекты, которые заменяют настоящие зависимости (базу, сеть, время), чтобы тест был быстрым, изолированным и предсказуемым.
Зачем подменять зависимости
Юнит-тест должен проверять одну единицу в изоляции. Но функция может звонить в реальную базу, дёргать платёжный API или зависеть от текущего времени. Тогда тест становится медленным, требует сети и даёт разный результат в разные дни. Решение — подменить зависимость дублёром.
| Дублёр | Идея |
| Stub | Возвращает заранее заданный ответ («заглушка») |
| Mock | Заглушка + проверяет, что его вызвали как ожидалось |
| Fake | Упрощённая рабочая реализация (например, словарь вместо БД) |
Пример: stub вместо реальной зависимости
Функция считает приветствие в зависимости от времени суток. Чтобы тест не зависел от реальных часов, передаём «час» как параметр-стаб — фиксированное значение.
def greeting(hour):
"""Приветствие по часу суток (0-23). Час передаётся снаружи."""
if hour < 6:
return "Доброй ночи"
if hour < 12:
return "Доброе утро"
if hour < 18:
return "Добрый день"
return "Добрый вечер"
# Вместо реального времени подставляем фиксированные часы (stub)
assert greeting(3) == "Доброй ночи"
assert greeting(9) == "Доброе утро"
assert greeting(15) == "Добрый день"
assert greeting(21) == "Добрый вечер"
print("Тест детерминирован: время подменено стабом, результат стабилен")
Вывод:
Тест детерминирован: время подменено стабом, результат стабилен
Обратите внимание: мы заранее спроектировали функцию так, чтобы час передавался, а не брался изнутри. Это называют «внедрением зависимости» — и оно делает код тестируемым без сложных моков.
Fake вместо настоящей базы
Простой словарь может играть роль «базы данных» в тесте — это fake. Он работает по-настоящему, но в памяти и мгновенно.
class FakeUserDB:
"""Поддельная БД в памяти вместо настоящей."""
def __init__(self):
self._data = {}
def save(self, user_id, name):
self._data[user_id] = name
def get(self, user_id):
return self._data.get(user_id)
def register(db, user_id, name):
db.save(user_id, name)
return db.get(user_id)
db = FakeUserDB() # fake вместо реальной БД
assert register(db, 1, "Анна") == "Анна"
print("Fake-БД сработала: тест быстрый и не трогает реальную базу")
Вывод:
Fake-БД сработала: тест быстрый и не трогает реальную базу
Опасность: хрупкие тесты
Перебор с моками ведёт к хрупким тестам — тем, что проверяют не результат, а внутреннюю реализацию: «функция вызвала метод X ровно дважды в таком порядке». Стоит чуть изменить устройство кода (не меняя поведения) — и такой тест ломается без реальной причины. Правило: проверяйте наблюдаемое поведение (результат), а не внутренние шаги. Меньше моков — крепче тесты.
Итог
- Тест-дублёры (stub/mock/fake) изолируют тест от базы, сети и времени.
- Внедрение зависимости делает код тестируемым без сложных моков.
- Тесты на детали реализации хрупки — проверяйте результат, а не внутренние вызовы.