Маленькие правила, большая разница
Хорошие тесты следуют нескольким привычкам:
1. Понятные имена — test_greet_empty_name говорит, что сломалось, без открытия файла.
2. Одно поведение на тест — маленькие тесты указывают прямо на проблему.
3. Таблицы для множества случаев — один параметризованный тест вместо скопированных тестов.
4. Сообщения с полученным и ожидаемым значением — упавший тест должен объяснять себя сам.
5. Отдельные тесты, чтобы видеть всё; несколько assert в одном тесте, когда продолжать бессмысленно.
6. Запускайте тесты часто — после каждого небольшого изменения, а не один раз в конце.
7. Тесты живут рядом с кодом — в той же папке, с префиксом test_.
Сообщения об ошибках, которые помогают
Упавший тест читают в момент растерянности — сообщение должно рассказывать всю историю: какой вызов, что вернулось, что ожидалось.
Хорошая форма — func(input) = got; ожидалось want. Преобразование !r в f-строке выводит строки в кавычках, поэтому пропущенный пробел или пропущенная запятая сразу видны.
Сообщение со всей историей
greet возвращает неверное приветствие. Наше сообщение показывает вызов, полученное и ожидаемое значение, а pytest добавляет под ним своё сравнение двух строк — ошибка (не то слово, нет запятой и восклицательного знака) видна по одному выводу, без открытия кода.
Вывод: полная программа на Python
# В greet есть ошибка: не то слово, нет запятой и "!".
def greet(name):
return "Hi " + name
def test_greet():
got = greet("Ana")
want = "Hello, Ana!"
assert got == want, f"greet('Ana') = {got!r}; ожидалось {want!r}"
Одно поведение на тест
У теста, который проверяет пять вещей, пять причин упасть — а его имя может описать только одну из них. Разделение по поведению делает каждый тест маленьким, а его имя честным. Здесь это важно вдвойне, потому что неудачный assert останавливает свой тест: пять проверок в одном тесте означают, что вы всегда видите только первую ошибку.
Здесь обычное приветствие и запасной вариант для пустого имени — это два поведения, поэтому для них два теста. Когда один из них падает, его имя уже говорит, какое поведение сломалось.
Два поведения, два теста
test_greet_name проверяет обычный случай, test_greet_empty — запасной вариант. Подробный вывод перечисляет их отдельно — ошибка указала бы ровно на одно поведение.
Вывод: полная программа на Python
def greet(name):
if name == "":
return "Hello, stranger!"
return "Hello, " + name + "!"
def test_greet_name():
assert greet("Ana") == "Hello, Ana!"
def test_greet_empty():
assert greet("") == "Hello, stranger!"
Сколько протестировано: покрытие
Плагин pytest-cov (устанавливается командой pip install pytest-cov) измеряет, какую часть вашего кода тесты действительно выполняют. Запустите pytest --cov=., и после прогона тестов добавится таблица: сколько инструкций в каждом файле, сколько тесты пропустили и какой процент покрыт.
Это число — компас, а не цель: низкое значение показывает непротестированные пути — здесь тест проходит только по ветке mild, поэтому return для freezing и hot никогда не выполняются. 100% покрытия не доказывает, что код правильный, но непокрытые строки гарантированно не проверены.
Измеряем покрытие
У describe три ветки, тест проверяет одну. Отчёт насчитывает 8 инструкций, 2 пропущены, 75% покрыто — пропущены как раз return для freezing и hot. Тесты для холодной и жаркой ветки подняли бы это число.
Измеряется весь файл, включая тест, поэтому число не ниже: все строки самого теста были выполнены.
Вывод: полная программа на Python
def describe(celsius):
if celsius < 0:
return "freezing"
if celsius > 30:
return "hot"
return "mild"
def test_describe_mild():
assert describe(20) == "mild"