Тестирование кода на Python: от doctest до coverage с примерами
Основные подходы к тестированию 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=... для целых файлов.