Тестирование кода на Python: от doctest до coverage с примерами

Раздел: Python -> Отладка и тестирование

Основные подходы к тестированию Python-кода

Тестирование позволяет убедиться в корректности работы программы, выявить ошибки на ранних этапах и облегчить рефакторинг. Рассмотрим несколько популярных инструментов и методик.

Как организовать эффективные юнит-тесты с помощью pytest?

Библиотека pytest считается одним из самых удобных фреймворков для тестирования Python-кода. Она предоставляет простой синтаксис, автоматическое обнаружение тестов и множество плагинов.

# content of test_example.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_example.py. Pytest сам находит функции, начинающиеся с test_, и выполняет их.

Типичная ошибка: забыть импортировать тестируемые функции или использовать assert с неправильным ожиданием. Если тест падает, pytest выводит подробное сообщение о том, какое значение получилось и какое ожидалось.

Цель использования: быстрое написание и запуск юнит-тестов, поддержка параметризации и фикстур.

Как использовать unittest для структурирования тестов?

Встроенный модуль unittest требует создания классов, наследующих от unittest.TestCase, и методов, начинающихся с test. Он предоставляет множество assert-методов.

import unittest

class TestMath(unittest.TestCase):
    def test_add(self):
        self.assertEqual(add(2, 3), 5)

if __name__ == '__main__':
    unittest.main()

Python проверка (проверка кода python)

Проблема: излишняя шаблонность, необходимость наследования. Для простых тестов pytest удобнее.

Цель: использование в крупных проектах, где требуется строгая структура и интеграция с CI/CD.


Как добавить тесты прямо в документацию с помощью doctest?

Модуль doctest позволяет встраивать тесты в docstring функций. Примеры запускаются командой python -m doctest module.py или через unittest.

def multiply(a, b):
    """
    >>> multiply(2, 3)
    6
    >>> multiply(-1, 2)
    -2
    """
    return a * b

тест кодов python (тестирование кода python)

Ошибка: несоответствие пробелов в ожидаемом выводе, что приводит к ложному падению. Doctest чувствителен к форматированию.

Цель: документирование и тестирование простых функций одновременно. Не подходит для сложных сценариев.


Как изолировать внешние зависимости с помощью mock?

Библиотека unittest.mock (или pytest-mock) позволяет заменить реальные вызовы на имитации. Это нужно для тестирования кода, работающего с API, базой данных или файловой системой без фактического соединения.

from unittest.mock import Mock

# Имитация функции
def get_user_data(user_id):
    return {'name': 'Alice'}

def process_user(uid):
    data = get_user_data(uid)
    return data['name'].upper()

# В тесте подменяем get_user_data
mock_get = Mock(return_value={'name': 'Bob'})
result = process_user(1)
assert result == 'BOB'

проверить программу python (проверить программу на python)

Типичная ошибка: не восстановить оригинал после теста, что может повлиять на другие тесты. Рекомендуется использовать декораторы или контекстные менеджеры patch.

Цель: уменьшение времени выполнения тестов, изоляция модулей, тестирование обработки ошибок.


Как измерить покрытие кода тестами?

Утилита coverage.py анализирует, какие строки кода были выполнены во время тестов. Установка: pip install coverage. Запуск: coverage run -m pytest, затем coverage report для отчёта.

# Пример отчёта
Name                      Stmts   Miss  Cover
---------------------------------------------
test_example.py               5      0   100%
my_module.py                 10      2    80%

Проблема: 100% покрытие не гарантирует отсутствие ошибок, важно тестировать граничные случаи.

Цель: выявление не протестированных участков кода, контроль качества.

Расширенные примеры тестирования на Python

Параметризованные тесты с pytest

Позволяют запускать один тест с разными наборами данных без дублирования кода.

Пример
import pytest

@pytest.mark.parametrize("a,b,expected", [
    (1, 2, 3),
    (-1, 1, 0),
    (0, 0, 0),
    (100, -100, 0)
])
def test_add_param(a, b, expected):
    result = a + b
    assert result == expected
$ pytest test_param.py -v
test_param.py::test_add_param[1-2-3] PASSED
test_param.py::test_add_param[-1-1-0] PASSED
test_param.py::test_add_param[0-0-0] PASSED
test_param.py::test_add_param[100--100-0] PASSED

Параметры передаются как кортежи, pytest автоматически генерирует отдельные тесты с понятными именами.


Фикстуры для подготовки данных

Фикстуры (fixtures) позволяют создавать и подготавливать окружение до теста и очищать после.

Пример
import pytest

@pytest.fixture
def user_data():
    return {"id": 1, "name": "Alice", "active": True}

def test_user_active(user_data):
    assert user_data["active"] is True

def test_user_name(user_data):
    assert user_data["name"] == "Alice"
$ pytest test_fixture.py -v
test_fixture.py::test_user_active PASSED
test_fixture.py::test_user_name PASSED

Фикстуры могут иметь разную область видимости (функция, класс, модуль, сессия) и возвращать данные.


Мокирование HTTP-запросов с responses

Для тестирования кода, делающего HTTP-вызовы, удобно использовать библиотеку responses.

Пример
import requests
import responses

@responses.activate
def test_get_user():
    responses.add(
        responses.GET,
        "https://api.example.com/user/1",
        json={"name": "Bob"},
        status=200
    )
    response = requests.get("https://api.example.com/user/1")
    assert response.json() == {"name": "Bob"}
    assert responses.calls[0].request.url == "https://api.example.com/user/1"
$ pytest test_http.py -v
test_http.py::test_get_user PASSED

Библиотека перехватывает все запросы, выполняемые через requests, и возвращает заранее заданные ответы.


Тестирование исключений

Проверка, что код выбрасывает нужное исключение в определённой ситуации.

Пример
import pytest

def divide(a, b):
    if b == 0:
        raise ValueError("Деление на ноль")
    return a / b

def test_divide_zero():
    with pytest.raises(ValueError, match="Деление на ноль"):
        divide(10, 0)
$ pytest test_exc.py -v
test_exc.py::test_divide_zero PASSED

Использование контекстного менеджера pytest.raises делает проверку чистой и информативной.


Покрытие с игнорированием определённых строк

Иногда нужно исключить из отчёта покрытия некоторые участки, например, строки, которые никогда не должны выполняться в тестах (блоки if __name__ == '__main__').

Пример
# my_module.py
def main():
    """Основная функция"""
    print("Запуск")

if __name__ == '__main__':
    main()  # pragma: no cover
$ coverage run -m pytest && coverage report --skip-covered
Name                  Stmts   Miss  Cover
-----------------------------------------
my_module.py              3      0   100%

Комментарий # pragma: no cover исключает строку из подсчёта. Можно также использовать coverage run --omit=... для целых файлов.

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

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