Хорошие тесты: принципы FIRST
Признаки хороших тестов: быстрые, изолированные, детерминированные, понятные.
FIRST — набор принципов хорошего теста: Fast, Independent, Repeatable, Self-validating, Timely.
Почему важно качество тестов, а не только их наличие
Плохие тесты хуже, чем их отсутствие: они медленные, падают случайно, и им перестают доверять. Команда начинает игнорировать красный прогон — и весь смысл теряется. Хороший тест-набор подчиняется принципам FIRST.
F — Fast (быстрые)
Юнит-тесты должны выполняться за миллисекунды. Тогда их гоняют часто — после каждого изменения. Если набор идёт минуты, его запускают редко, и баги накапливаются. Главный враг скорости — реальные обращения к БД, сети, диску. Их убирают моками (раздел про дублёры).
I — Independent (изолированные)
Тесты не должны зависеть друг от друга или от порядка запуска. Помните: unittest идёт по алфавиту, а не по порядку в файле. Тест, который полагается на данные, оставленные другим тестом, — мина замедленного действия.
import unittest
class Counter:
total = 0 # ОПАСНО: состояние класса общее для всех
class TestBad(unittest.TestCase):
def setUp(self):
# Правильно: сбрасываем общее состояние перед каждым тестом
Counter.total = 0
def test_one(self):
Counter.total += 1
self.assertEqual(Counter.total, 1)
def test_two(self):
Counter.total += 5
self.assertEqual(Counter.total, 5) # благодаря setUp — не 6
unittest.main(argv=[''], exit=False, verbosity=2)
Вывод:
test_one (__main__.TestBad.test_one) ... ok test_two (__main__.TestBad.test_two) ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK
Без сброса в setUp второй тест ждал бы 5, но получил бы 6 (1 от первого теста + 5). Изоляция через setUp спасает.
R — Repeatable (детерминированные)
Тест обязан давать один и тот же результат при каждом запуске. «Иногда зелёный, иногда красный» (flaky-тест) — худшее, что бывает: ему нельзя верить. Источники недетерминизма — время, случайность, порядок словарей, внешние сервисы. Их фиксируют моками.
import unittest
from unittest.mock import patch
import random
def pick_winner(players):
return random.choice(players)
class TestWinner(unittest.TestCase):
@patch("random.choice", return_value="Анна")
def test_deterministic(self, mock_choice):
# Зафиксировали случайность — результат предсказуем КАЖДЫЙ раз
self.assertEqual(pick_winner(["Анна", "Борис"]), "Анна")
unittest.main(argv=[''], exit=False, verbosity=2)
Вывод:
test_deterministic (__main__.TestWinner.test_deterministic) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.001s OK
S — Self-validating (самопроверяемые)
Тест сам решает, прошёл он или нет, — через assert'ы. Никакого «посмотрите глазами на вывод». Если для оценки результата нужен человек — это не автотест.
T — Timely (вовремя)
Тесты пишут вместе с кодом, а лучше — до него (об этом следующий урок про TDD). Тесты, отложенные «на потом», обычно не пишутся никогда.
Понятные имена и читаемость
Имя теста — документация. Когда в CI падает test_withdraw_more_than_balance_raises_error, причина ясна без чтения кода. А test_3 заставляет лезть внутрь. Хороший тест читается как утверждение о поведении.
Памятка FIRST
| Буква | Принцип |
| F | Fast — быстрые |
| I | Independent — независимые |
| R | Repeatable — детерминированные |
| S | Self-validating — с автоматической проверкой |
| T | Timely — написанные вовремя |
Итог
- Качество тестов важнее их количества: плохим тестам перестают верить.
- FIRST: быстрые, независимые, детерминированные, самопроверяемые, своевременные.
- Изоляцию дают
setUpи сброс общего состояния; детерминизм — моки времени/случайности. - Понятное имя теста = документация и быстрая диагностика.