Зачем вообще нужны тесты
Тесты — это не просто способ сделать CI «зелёным».
Это один из фундаментальных слоёв любого проекта.
Хорошие тесты:
- обеспечивают стабильность системы
- сохраняют бизнес-логику, когда детали уже забыты
- позволяют безопасно проводить рефакторинг
- служат документацией поведения системы
Плохие тесты:
- замедляют разработку
- ломаются без причины
- не дают уверенности в коде
- мешают рефакторингу
Принципы FIRST
При написании тестов придерживайтесь принципов FIRST:
Fast (быстрые)
Тесты должны выполняться быстро.
- избегайте реального I/O (сеть, файловая система, БД)
- не используйте
sleep - минимизируйте тяжёлые setup’ы
Independent (независимые)
Тесты не должны зависеть друг от друга.
- порядок запуска не должен иметь значения
- никакого shared state
- каждый тест подготавливает своё окружение
Repeatable (повторяемые)
Тесты должны стабильно проходить в любом окружении.
- не зависят от времени (
datetime.now()) - не зависят от случайности (
random) - не зависят от внешних сервисов
Self-validating (самопроверяемые)
Результат теста должен быть однозначным.
- никаких ручных проверок
- чёткие
assert - понятные сообщения об ошибках
Timely (своевременные)
Тесты должны быть простыми в поддержке.
- пишутся рядом с кодом
- легко читаются
- легко изменяются при изменении логики
Структура теста (AAA)
Каждый тест должен иметь чёткую структуру:
- Arrange — подготовка данных
- Act — выполнение действия
- Assert — проверка результата
Пример:
def test_user_can_send_message():
# Arrange
user = User()
chat = Chat()
# Act
chat.send_message(user, "hello")
# Assert
assert chat.messages_count == 1Один тест — одна ответственность
Тест должен проверять одну логическую вещь.
❌ Плохо:
def test_chat():
#...
assert response.messages
message_added = response.messages[0]
assert message_added.text == "hello"
assert message_added.author == user✅ Хорошо:
def test_messages_return():
#...
assert response.messages
def test_add_message():
#...
message_added = response.messages[0]
assert message_added.text == "hello"
assert message_added.author == userРазделяйте тесты по сценариям
Не делайте «простыни» на 1000 строк.
❌ Плохо:
- один файл
- все сценарии вперемешку
- сложно ориентироваться
✅ Хорошо:
- группировка по контекстам / модулям
- отдельные файлы/классы для разных сценариев
- понятная структура
Пример:
api/customers/events/tests/
test_get.py
test_edit.py
test_filters.py
test_meta.py
Минимизируйте setup
Тест должен быть максимально простым.
❌ Плохо:
- сложные фабрики
- много неиспользуемых данных
- «магия» в фикстурах
✅ Хорошо:
- только необходимые данные
- явный setup
- читаемость важнее «умности»
Избегайте реальных зависимостей
Не используйте в тестах:
- реальные БД
- сеть
- файловую систему (если можно избежать)
Используйте:
- mock / stub / fake
- in-memory реализации
Избегайте нестабильных (flaky) тестов
Проблемные признаки:
- тест «иногда падает»
- зависит от времени
- зависит от порядка запуска
Решение:
- фиксируйте время
- убирайте randomness
- изолируйте состояние
Именование тестов
Имя теста должно объяснять поведение, а не реализацию.
❌ Плохо:
def test_1():
def test_chat():✅ Хорошо:
def test_user_can_send_message():
def test_message_is_not_sent_without_permission():Рефакторинг тестов
Тесты — это тоже код. Их нужно поддерживать.
При работе над фичей:
- увидели сложный тест → упростите
- тест проверяет несколько вещей → разделите
- есть дублирование → уберите
- файл слишком большой → разбейте
Не откладывайте — делайте это сразу.
Антипаттерны (“запах” плохих тестов)
- ❌ Огромные тесты на сотни строк
- ❌ Много логики внутри теста
- ❌ Один тест проверяет всё
- ❌ Нестабильные тесты
- ❌ Тесты, которые сложно читать
Итог
Хорошие тесты:
- быстрые
- простые
- изолированные
- читаемые
- отражают бизнес-логику
Главная цель:
Тесты должны помогать понимать систему и безопасно её менять, а не мешать разработке.