Тестирование Python программ: выбор инструмента и шаблоны

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

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

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

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

pytest – современный фреймворк, который требует минимум кода и предоставляет мощные возможности: автоматическое обнаружение тестов, фикстуры, параметризацию, плагины.

Установка:

pip install pytest

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

Пример теста для функции вычисления факториала. Создайте файл test_math.py:

# test_math.py
def factorial(n):
    if n == 0:
        return 1
    return n * factorial(n-1)

def test_factorial_positive():
    assert factorial(5) == 120

def test_factorial_zero():
    assert factorial(0) == 1

Запуск:

pytest test_math.py

Результат:

============================= test session starts ==============================
collected 2 items

test_math.py ..                                                          [100%]

============================== 2 passed in 0.02s ===============================

Для параметризации тестов используйте декоратор @pytest.mark.parametrize:

import pytest

def factorial(n):
    if n < 0:
        raise ValueError("Отрицательное значение")
    if n == 0:
        return 1
    return n * factorial(n-1)

@pytest.mark.parametrize("n,expected", [
    (0, 1),
    (1, 1),
    (5, 120),
    (10, 3628800),
])
def test_factorial(n, expected):
    assert factorial(n) == expected

@pytest.mark.parametrize("n", [-1, -10])
def test_factorial_negative(n):
    with pytest.raises(ValueError):
        factorial(n)

Фикстуры позволяют подготовить общие данные. Пример с временным файлом:

import pytest

@pytest.fixture
def temp_file(tmp_path):
    file = tmp_path / "data.txt"
    file.write_text("test content")
    return file

def test_read_temp_file(temp_file):
    data = temp_file.read_text()
    assert data == "test content"

Типичная ошибка: забыть установить pytest в окружение. Используйте виртуальное окружение или pip install pytest. Также тесты должны начинаться с test_ или оканчиваться на _test для обнаружения.

Проблема с областью видимости фикстуры – по умолчанию function, что приводит к повторному созданию для каждого теста. Для ресурсоёмких объектов используйте scope="module" или scope="session".

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

Модуль unittest встроен в Python, основан на JUnit. Он требует создания классов-наследников 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), -2)

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

Запуск:

python -m unittest test_math.py

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

Как протестировать код с помощью doctest?

Doctest позволяет встроить тесты прямо в документацию функции. Примеры копируются из docstring и проверяются.

def multiply(a, b):
    """
    Умножает два числа.

    >>> multiply(2, 3)
    6
    >>> multiply(10, 0)
    0
    >>> multiply(-1, 5)
    -5
    """
    return a * b

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

Запуск:

python -m doctest test_doctest.py -v

Подходит для простых проверок, но не для сложной логики и внешних зависимостей.

Ошибка: doctest чувствителен к пробелам и ожидает точное совпадение вывода. Лучше не использовать для больших проектов.

При использовании unittest частая проблема – забыть вызвать super().setUp() в переопределённом методе setUp, что может нарушить работу тестов.

Как выполнить временное тестирование (перехват вывода)?

pytest предоставляет фикстуру capsys для захвата stdout и stderr. Пример:

def greet(name):
    print(f"Hello, {name}!")

def test_greet(capsys):
    greet("Alice")
    captured = capsys.readouterr()
    assert captured.out == "Hello, Alice!\n"

Как mock внешние вызовы?

Использование unittest.mock или pytest-mock. Пример с запросом HTTP:

import requests

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

# test with mock
def test_get_user_data(mocker):
    mock_response = mocker.Mock()
    mock_response.json.return_value = {"id": 1, "name": "Alice"}
    mocker.patch('requests.get', return_value=mock_response)
    
    result = get_user_data(1)
    assert result == {"id": 1, "name": "Alice"}

Проблема: неправильный путь для patching – нужно указывать полный путь в импорте модуля, где используется объект.

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

Тестирование свойств с помощью Hypothesis

Hypothesis генерирует множество случайных входных данных для поиска граничных случаев. Установка:

Пример
pip install hypothesis

Пример
from hypothesis import given, strategies as st
from mymodule import encode, decode

@given(st.text())
def test_encode_decode(text):
    encoded = encode(text)
    decoded = decode(encoded)
    assert decoded == text

Запуск:

hypothesis test_example.py::test_encode_decode
... found no counterexample (seems valid)

Важно: тесты могут быть медленными, ограничивайте стратегии параметрами max_examples.

Интеграционное тестирование Flask приложения

Используйте клиент тестирования Flask:

Пример
# app.py
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello, World!'

# test_app.py
import pytest
from app import app

@pytest.fixture
def client():
    with app.test_client() as client:
        yield client

def test_index(client):
    response = client.get('/')
    assert response.status_code == 200
    assert b'Hello, World!' in response.data

Результат:

pytest test_app.py -v
================== test session starts ==================
test_app.py::test_index PASSED

Ошибка: не забыть импортировать приложение или использовать фабрику.

Тестирование с покрытием кода (coverage)

Установка:

Пример
pip install pytest-cov

Пример
# запуск с отчётом
pytest --cov=myproject --cov-report=html tests/

Результат – папка htmlcov с детальным отчётом. Позволяет найти не покрытые тестами строки.

Параметризация с внешними данными (JSON, CSV)

Пример
import json
import pytest

@pytest.fixture
def test_data():
    with open('tests/test_data.json') as f:
        return json.load(f)

def test_from_json(test_data):
    for entry in test_data:
        assert entry['expected'] == myfunc(entry['input'])

Можно использовать @pytest.mark.parametrize с загрузкой списка.

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

Пример
import pytest

def set_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative")
    return age

def test_set_age_negative():
    with pytest.raises(ValueError, match="Age cannot be negative"):
        set_age(-1)

def test_set_age_valid():
    assert set_age(25) == 25

Проверка точного сообщения об ошибке важна для документации.

Тестирование программ Python - comments

En
Python тест программ (python)