Основы тестирования кода: unittest и pytest
Почему тестирование — это круто? 🧪
Представь: ты написал сложную функцию, она работает... вроде бы. Но вдруг в продакшене ломается из-за неожиданных данных. Тестирование — это твой страховочный трос! Оно:
- Ускоряет разработку (меньше дебага)
- Делает код надежнее
- Позволяет рефакторить без страха
В Python два главных инструмента: unittest (из коробки) и pytest (гибкость и мощь).
unittest — стандартный подход
Базовый пример
import unittest
def add(a, b):
return a + b
class TestMath(unittest.TestCase):
def test_add(self):
self.assertEqual(add(2, 3), 5) # Проверка равенства
self.assertNotEqual(add(2, 2), 5) # Проверка неравенства
def test_add_negative(self):
self.assertEqual(add(-1, -1), -2)
if __name__ == '__main__':
unittest.main()
Разберем по шагам:
- Создаем класс, наследующий
unittest.TestCase - Каждый тест — метод, начинающийся с
test_ - Используем
assert-методы (assertEqual,assertTrueи др.) - Запускаем через
unittest.main()
Фикстуры: подготовка и очистка
class TestDatabase(unittest.TestCase):
@classmethod
def setUpClass(cls):
# Выполняется 1 раз для всех тестов
cls.db = connect_to_test_db()
def setUp(self):
# Выполняется перед КАЖДЫМ тестом
self.db.clear_tables()
def test_insert(self):
self.db.insert('data')
self.assertTrue(self.db.contains('data'))
def tearDown(self):
# Выполняется после КАЖДОГО теста
self.db.rollback()
Когда использовать:
setUpClass— дорогие операции (например, подключение к БД)setUp/tearDown— подготовка контекста для каждого теста
pytest — магия простоты
Основные преимущества
- Нет необходимости в классах
- Подробные сообщения об ошибках
- Фикстуры с декларативным синтаксисом
- Плагины (например,
pytest-covдля покрытия кода)
Пример теста
# test_math.py
def multiply(a, b):
return a * b
def test_multiply():
assert multiply(3, 4) == 12
assert multiply(0, 10) == 0
Запуск: pytest test_math.py -v (-v для подробного вывода)
Мощные фикстуры pytest
import pytest
@pytest.fixture
def database():
db = connect_to_db()
yield db # Это точка, где тест получает объект
db.disconnect() # Выполнится после теста
def test_query(database): # Фикстура передается как аргумент!
result = database.query("SELECT 1")
assert result == 1
Почему это круто:
- Фикстуры можно переиспользовать
- Автоматическая очистка через
yield - Можно делать область видимости (
@pytest.fixture(scope="module"))
Параметризация: тестируем все случаи
import pytest
@pytest.mark.parametrize("a, b, expected", [
(2, 3, 5),
(0, 0, 0),
(-1, 1, 0),
])
def test_add(a, b, expected):
assert add(a, b) == expected
Что происходит:
- pytest запустит тест 3 раза с разными параметрами
- Если один случай упадет — остальные проверятся
- Идеально для edge-кейсов!
Что выбрать: unittest или pytest?
| Критерий | unittest | pytest |
|---|---|---|
| Сложность | Проще для новичков | Более гибкий |
| Фикстуры | Классовые методы | Декларативные |
| Параметризация | Через subTest | Встроенная поддержка |
| Интеграция | В стандартной библиотеке | Требует установки |
Совет от Данилы Бежина: Начинайте с unittest, затем переходите на pytest для сложных проектов. Подробнее — в его видео.
Практический лайфхак: покрытие тестами
Установите pytest-cov:
pip install pytest-cov
Запустите с проверкой покрытия:
pytest --cov=my_project tests/
Вы увидите процент кода, который покрыт тестами. Цель — 70-90%, не стремитесь к 100% там, где это неоправданно!
Name Stmts Miss Cover
-------------------------------------
my_project/math.py 10 2 80%