Как организовать качественное тестирование Python-приложений
Основные методы тестирования кода Python
Как использовать pytest для быстрого создания тестов?
Pytest - это современный фреймворк для тестирования, который требует минимального шаблонного кода. Для установки нужно выполнить pip install pytest. Основная идея: тестовые функции с префиксом test_ автоматически обнаруживаются.
Пример простого теста функции сложения:
# test_math.py
def add(a, b):
return a + b
def test_add():
assert add(2, 3) == 5
assert add(-1, 1) == 0тестовый код python (тестирование кода python)
Запуск: pytest test_math.py. Pytest выдаст отчет о прохождении.
test_ или поместить тест в класс, не начинающийся с Test. В таких случаях pytest не найдет тест. Решение: следовать соглашению об именах.Pytest поддерживает параметризацию, что позволяет запускать один тест с разными данными:
import pytest
@pytest.mark.parametrize("a,b,expected", [
(1, 2, 3),
(0, 0, 0),
(-1, 1, 0),
(100, -50, 50)
])
def test_add_params(a, b, expected):
assert a + b == expectedРезультат: 4 теста выполнено.
Для тестирования исключений используется pytest.raises:
def divide(a, b):
if b == 0:
raise ValueError("Division by zero")
return a / b
def test_divide_zero():
with pytest.raises(ValueError, match="Division by zero"):
divide(10, 0)Этот подход гарантирует, что исключение выбрасывается с правильным сообщением.
Фикстуры pytest (@pytest.fixture) позволяют подготавливать данные для тестов:
import pytest
@pytest.fixture
def sample_data():
return {"name": "Alice", "age": 30}
def test_user_name(sample_data):
assert sample_data["name"] == "Alice"Фикстуры могут иметь область видимости (module, session) для повторного использования.
Какие возможности предоставляет unittest для структурирования тестов?
Unittest входит в стандартную библиотеку Python. Тесты оформляются в виде класса, наследующего unittest.TestCase. Методы с префиксом test считаются тестами.
import unittest
def add(a, b):
return a + b
class TestMath(unittest.TestCase):
def test_add_positive(self):
self.assertEqual(add(2, 3), 5)
def test_add_negative(self):
self.assertEqual(add(-1, 1), 0)
if __name__ == "__main__":
unittest.main()Unittest предоставляет множество assert-методов: assertEqual, assertTrue, assertRaises и др. Для проверки исключений:
def divide(a, b):
if b == 0:
raise ValueError("Division by zero")
return a / b
class TestDivide(unittest.TestCase):
def test_divide_zero(self):
with self.assertRaises(ValueError):
divide(10, 0)Для настройки окружения используются методы setUp и tearDown:
class TestDatabase(unittest.TestCase):
def setUp(self):
self.db = create_database() # инициализация
def tearDown(self):
self.db.close()
def test_insert(self):
self.db.insert("test")
self.assertTrue(self.db.exists("test"))unittest.main() при запуске скрипта, или расположить тестовые методы не в том классе. Решение: четко следовать шаблону наследования.Как встроить тесты прямо в документацию с помощью doctest?
Модуль doctest позволяет писать тесты внутри docstring, имитируя интерактивную сессию. Это удобно для простых примеров использования.
def multiply(a, b):
"""
Возвращает произведение a и b.
>>> multiply(2, 3)
6
>>> multiply(0, 5)
0
>>> multiply(-1, -1)
1
"""
return a * b
if __name__ == "__main__":
import doctest
doctest.testmod()При запуске скрипта doctest проверяет, что вывод соответствует ожидаемому. Если тест не проходит, выдается подробное сообщение об ошибке.
Расширенные примеры тестирования
Пример тестирования с mock (объект-заглушка) для имитации внешних сервисов:
# test_api.py
import pytest
from unittest.mock import Mock, patch
def fetch_data(api):
return api.get_data()
def test_fetch_data():
mock_api = Mock()
mock_api.get_data.return_value = {"key": "value"}
result = fetch_data(mock_api)
assert result == {"key": "value"}
mock_api.get_data.assert_called_once()Тест проходит, проверяет вызов метода get_data ровно один раз.
Пример параметризованного теста с несколькими наборами данных и проверкой исключений:
import pytest
from math import sqrt
def sqrt_safe(x):
if x < 0:
raise ValueError("Negative input")
return sqrt(x)
@pytest.mark.parametrize("input_val,expected,should_raise", [
(4, 2.0, False),
(0, 0.0, False),
(-1, None, True),
(9, 3.0, False)
])
def test_sqrt_safe(input_val, expected, should_raise):
if should_raise:
with pytest.raises(ValueError):
sqrt_safe(input_val)
else:
assert sqrt_safe(input_val) == pytest.approx(expected)Все четыре сценария выполнены, включая негативный.
Пример тестирования асинхронного кода с pytest-asyncio:
# test_async.py
import pytest
import asyncio
async def fetch_data():
await asyncio.sleep(0.1)
return 42
@pytest.mark.asyncio
async def test_fetch_data():
result = await fetch_data()
assert result == 42Требуется установка pytest-asyncio. Тест выполняется в асинхронном режиме.
Пример использования coverage для оценки покрытия кода тестами:
# Установка: pip install pytest-cov
# Запуск: pytest --cov=my_module tests/Выводит процент покрытия каждой строки кода.