Как организовать качественное тестирование Python-приложений

Раздел: Разработка на 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 проверяет, что вывод соответствует ожидаемому. Если тест не проходит, выдается подробное сообщение об ошибке.

Проблема: Тесты могут стать слишком объемными внутри docstring, ухудшая читаемость. Также 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/
Выводит процент покрытия каждой строки кода.

Тестирование кода Python - comments

En
тестовый код python (python)