Проверка функций в Python с помощью модульных тестов

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

Основные варианты тестирования функций

Как эффективно тестировать функции с помощью pytest?

Наиболее распространённый и удобный способ - использование фреймворка pytest. Он позволяет писать простые тесты с обычными assert, поддерживает параметризацию, фикстуры и автоматическое обнаружение тестовых файлов.


# Файл test_math_func.py
def add(a, b):
    return a + b

def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0

A b test python (a/b тестирование в python)

Для запуска тестов достаточно выполнить в терминале: pytest test_math_func.py. Система сама найдёт все функции, начинающиеся на test_.

Типичная проблема: тест может не запуститься, если файл не начинается с test_ или функция не содержит префикса test. Решение - следовать соглашениям pytest: имена файлов test_*.py, функции test_*.

Для проверки исключений используется pytest.raises:


import pytest

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

def test_divide_zero():
    with pytest.raises(ZeroDivisionError, match="деление на ноль"):
        divide(10, 0)

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

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


@pytest.mark.parametrize("a,b,expected", [
    (1, 2, 3),
    (0, 0, 0),
    (-5, 5, 0),
])
def test_add_parametrized(a, b, expected):
    assert add(a, b) == expected

Py test python (написание тестов на python (pytest))

Ошибка: несоответствие количества параметров в декораторе и сигнатуре теста. Все имена должны совпадать, иначе pytest выдаст ошибку Incorrect number of arguments.

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

Модуль doctest извлекает примеры из docstring и проверяет их выполнение. Это удобно для простых функций с очевидным поведением.


def multiply(a, b):
    """
    Возвращает произведение двух чисел.

    >>> multiply(3, 4)
    12
    >>> multiply(0, 100)
    0
    >>> multiply(-2, 5)
    -10
    """
    return a * b

if __name__ == "__main__":
    import doctest
    doctest.testmod()

тесты алгоритмы и программирование python (тестирование алгоритмов и программ на python)

Запуск: python mymodule.py - если тесты пройдены, вывода не будет. При ошибке показывается различие.

Проблема: при изменении реализации тесты в docstring перестают соответствовать. Решение - поддерживать документацию в актуальном состоянии или использовать doctest только для простых примеров.

Как использовать классический unittest из стандартной библиотеки?

Модуль unittest предоставляет объектно-ориентированный подход с классами наследуемыми от TestCase.


import unittest

def square(n):
    return n * n

class TestSquareFunction(unittest.TestCase):
    def test_positive(self):
        self.assertEqual(square(5), 25)

    def test_negative(self):
        self.assertEqual(square(-3), 9)

    def test_zero(self):
        self.assertEqual(square(0), 0)

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

тестирование функций python (тестирование функций в python)

Запуск: python test_file.py. Методы assertEqual, assertTrue, assertRaises - основные инструменты.

Типичная ошибка: забыть унаследоваться от unittest.TestCase - тогда тесты не будут распознаны как тестовые методы. Решение - всегда указывать class MyTest(unittest.TestCase).

Как изолировать функцию от внешних зависимостей с помощью unittest.mock?

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


from unittest.mock import patch
import requests

def get_user_name(user_id):
    response = requests.get(f"https://api.example.com/users/{user_id}")
    return response.json()["name"]

def test_get_user_name():
    with patch('requests.get') as mock_get:
        mock_get.return_value.json.return_value = {"name": "Alice"}
        result = get_user_name(1)
        assert result == "Alice"
        mock_get.assert_called_once_with("https://api.example.com/users/1")

В pytest можно использовать плагин pytest-mock и фикстуру mocker.

Проблема: если путь к моку указан не точно, подмена не сработает. Нужно передавать строку с путём, где объект используется (в модуле, где определена тестируемая функция). Ошибка ModuleNotFoundError или AssertionError при проверке вызова.

Расширенные примеры тестирования функций

Тестирование асинхронных функций

Для асинхронных функций (async/await) требуется pytest-asyncio. Установка: pip install pytest-asyncio.

Пример

import pytest

async def fetch_data(url):
    # имитация запроса
    return {"status": 200, "data": "OK"}

@pytest.mark.asyncio
async def test_fetch_data():
    result = await fetch_data("http://example.com")
    assert result["status"] == 200

Использование временных файлов (tmpdir)

Фикстура tmpdir в pytest создаёт временную директорию для каждого теста, которая удаляется после завершения.

Пример

import pytest

def write_to_file(path, content):
    with open(path, 'w') as f:
        f.write(content)

def test_write_to_file(tmpdir):
    file_path = tmpdir.join("output.txt")
    write_to_file(str(file_path), "Hello, pytest!")
    with open(str(file_path)) as f:
        assert f.read() == "Hello, pytest!"
Тест проходит успешно, файл создаётся и удаляется.

Тестирование функций с побочными эффектами

Функция может изменять глобальное состояние или записывать в лог. Для проверки логов используют caplog из pytest.

Пример

import logging
import pytest

def log_message(logger, msg):
    logger.info(msg)

def test_log_message(caplog):
    logger = logging.getLogger("test_logger")
    with caplog.at_level(logging.INFO):
        log_message(logger, "Тестовое сообщение")
    assert "Тестовое сообщение" in caplog.text

Параметризация с несколькими аргументами и сложными типами

В pytest можно передавать списки, словари и другие объекты как параметры.

Пример

import pytest

def merge_dictionaries(a, b):
    merged = a.copy()
    merged.update(b)
    return merged

@pytest.mark.parametrize("a,b,expected", [
    ({"x": 1}, {"y": 2}, {"x": 1, "y": 2}),
    ({"a": 10}, {"a": 20}, {"a": 20}),
])
def test_merge(a, b, expected):
    assert merge_dictionaries(a, b) == expected
Оба теста проходят.

Тестирование встроенных функций с помощью pytest и hypothesis

Библиотека hypothesis генерирует случайные тестовые данные для поиска граничных случаев.

Пример

from hypothesis import given, strategies as st

def factorial(n):
    if n < 0:
        raise ValueError("Факториал отрицательного числа не определён")
    if n == 0:
        return 1
    return n * factorial(n - 1)

@given(st.integers(min_value=0, max_value=20))
def test_factorial_positive(n):
    result = factorial(n)
    assert result >= 1
    if n > 0:
        assert result % n == 0
Hypothesis генерирует множество значений, тесты могут выявить ошибки для необычных входных данных.

Проверка покрытия кода (coverage)

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

Пример

# simple_func.py
def is_even(x):
    return x % 2 == 0

# test_simple.py
def test_is_even():
    assert is_even(4) is True
    assert is_even(3) is False
Имя              Stmts   Miss  Cover
-------------------------------------
simple_func.py       2      0   100%

Тестирование функций в Python - comments

En
тестирование функций python (python)