Эффективные методики тестирования программ на Python

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

Тестирование кода в Python: обзор инструментов и подходов

Как запускать тесты с помощью pytest?

Pytest является современным и популярным инструментом для тестирования Python кода. Для начала работы устанавливается библиотека командой pip install pytest. Далее создается файл с тестами, который должен начинаться с test_ или заканчиваться на _test.py. Внутри файла пишутся функции, имена которых также начинаются на test_. Внутри функции используется оператор assert для проверки условий.


# test_example.py
def test_sum():
    assert sum([1, 2, 3]) == 6
  

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

Запуск тестов выполняется из командной строки: pytest. Pytest автоматически находит все тестовые файлы и функции.

Проблема: тест не найден. Причина: имя файла или функции не соответствует шаблону. Решение: переименовать файл или функцию, добавив префикс test_.

Проблема: сообщение об ошибке pytest неинформативно. Решение: добавить сообщение в assert, например assert result == expected, f'ожидалось {expected}, получено {result}'.

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

Doctest позволяет включить тесты непосредственно в строки документации. Тесты записываются в виде примеров кода и ожидаемого вывода. Для запуска используется команда python -m doctest -v module.py. Пример:


def multiply(a, b):
    '''
    Функция перемножает два числа.
    >>> multiply(2, 3)
    6
    >>> multiply(0, 5)
    0
    '''
    return a * b
  

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

Проблема: пробелы в ожидаемом выводе. Решение: точное копирование вывода, включая пробелы. При наличии неопределенностей используется # doctest: +ELLIPSIS.

Как организовать тесты с использованием класса unittest?

Unittest входит в стандартную библиотеку. Тесты оформляются в виде класса, наследующего unittest.TestCase. Методы тестирования должны начинаться с test_. Для проверок используются методы assertEqual, assertTrue и другие. Пример:


import unittest

def divide(a, b):
    return a / b

class TestDivide(unittest.TestCase):
    def test_divide(self):
        self.assertEqual(divide(10, 2), 5)
    def test_divide_by_zero(self):
        with self.assertRaises(ZeroDivisionError):
            divide(1, 0)
  

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

Запуск выполняется через python -m unittest test_module.py.

Проблема: метод setUp вызывается для каждого теста, но забыт вызов super(). Решение: в setUp класса TestCase вызывать super().setUp() при переопределении.

Как тестировать функцию с множеством входных данных с помощью параметризации pytest?

Pytest поддерживает параметризацию через декоратор @pytest.mark.parametrize. Передаются имена аргументов и список кортежей со значениями. Пример:


import pytest

def is_even(n):
    return n % 2 == 0

@pytest.mark.parametrize('input, expected', [
    (2, True),
    (3, False),
    (0, True),
    (-2, True)
])
def test_is_even(input, expected):
    assert is_even(input) == expected
  

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

Проблема: большое количество параметров усложняет чтение. Решение: использовать фикстуры и комбинировать параметризацию с фикстурами.

Как подготовить окружение для тестов с помощью фикстур pytest?

Фикстуры определяются с декоратором @pytest.fixture. Они могут возвращать данные или выполнять действия до и после теста с помощью yield. Пример:


import pytest

@pytest.fixture
def sample_data():
    data = {'key': 'value'}
    yield data
    # очистка не требуется

def test_data(sample_data):
    assert sample_data['key'] == 'value'
  

Test data python (создание тестовых данных в python)

Фикстуры можно настраивать с параметром scope (function, class, module, session).

Проблема: фикстура создается для каждого теста, что замедляет выполнение. Решение: установить scope='module' или scope='session'.

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

Mock из библиотеки unittest.mock позволяет подменять вызовы функций или атрибутов объектов. Используется декоратор @patch или контекстный менеджер. Пример:


from unittest.mock import patch

def get_user_name(user_id):
    # обращение к внешнему API
    import requests
    response = requests.get(f'https://api.example.com/users/{user_id}')
    return response.json()['name']

@patch('requests.get')
def test_get_user_name(mock_get):
    mock_get.return_value.json.return_value = {'name': 'Alice'}
    result = get_user_name(1)
    assert result == 'Alice'
  

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

Как оценить покрытие кода тестами с помощью coverage?

Инструмент coverage позволяет измерить, какие строки кода были выполнены во время тестов. Устанавливается командой pip install pytest-cov. Запуск: pytest --cov=my_module tests/. Генерируется отчет в терминале. Пример конфигурации в .coveragerc


[run]
source = my_module
omit = */test_*
  

Проблема: отчет включает сторонние библиотеки. Решение: настроить source и omit в конфигурации.

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

Пример 1: фикстура pytest с областью видимости session и autouse

Фикстура может быть автоматически применена ко всем тестам в модуле с параметром autouse=True. Полезна для подготовки глобальных ресурсов.

Пример

import pytest

@pytest.fixture(scope='session', autouse=True)
def setup_database():
    print('\\nПодключение к БД')
    yield
    print('\\nОтключение от БД')

def test_one():
    print('Тест 1')

def test_two():
    print('Тест 2')
$ pytest -s test_example.py
Подключение к БД
Тест 1
.Тест 2
.
Отключение от БД

Пример 2: параметризация с несколькими аргументами и идентификаторами

Использование параметра ids для понятных имен тестов.

Пример

import pytest

def concat(a, b, sep):
    return f'{a}{sep}{b}'

@pytest.mark.parametrize('a,b,sep,expected', [
    ('hello', 'world', ' ', 'hello world'),
    ('foo', 'bar', '-', 'foo-bar')
], ids=['space', 'hyphen'])
def test_concat(a, b, sep, expected):
    assert concat(a, b, sep) == expected
$ pytest -v test_example.py
test_example.py::test_concat[space] PASSED
test_example.py::test_concat[hyphen] PASSED

Пример 3: mock для имитации API запроса с side_effect

Использование side_effect для возврата разных значений при последовательных вызовах.

Пример

from unittest.mock import patch

def get_users(api):
    return [api.get_user(i) for i in range(3)]

class FakeAPI:
    def get_user(self, user_id):
        pass

@patch.object(FakeAPI, 'get_user')
def test_get_users(mock_get_user):
    mock_get_user.side_effect = ['Alice', 'Bob', 'Charlie']
    api = FakeAPI()
    result = get_users(api)
    assert result == ['Alice', 'Bob', 'Charlie']
$ pytest -v test_example.py
test_example.py::test_get_users PASSED

Пример 4: doctest с обработкой исключений и настройками

В doctest можно проверять исключения с помощью Traceback (most recent call last):.

Пример

def sqrt(x):
    '''
    Вычисляет квадратный корень.
    >>> sqrt(4)
    2.0
    >>> sqrt(-1)
    Traceback (most recent call last):
        ...
    ValueError: отрицательное число
    '''
    if x < 0:
        raise ValueError('отрицательное число')
    return x ** 0.5
$ python -m doctest -v example.py
Trying:
    sqrt(4)
Expecting:
    2.0
ok
Trying:
    sqrt(-1)
Expecting:
    Traceback (most recent call last):
        ...
    ValueError: отрицательное число
ok

Пример 5: unittest с подтестами (subTest)

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

Пример

import unittest

class TestLists(unittest.TestCase):
    def test_append(self):
        lst = []
        for i in range(3):
            with self.subTest(i=i):
                lst.append(i)
                self.assertEqual(len(lst), i+1)
Ran 1 test in 0.001s
OK

Пример 6: настройка coverage для игнорирования строк

Для исключения строк из отчета используется комментарий # pragma: no cover.

Пример

def legacy_function(x):
    # pragma: no cover
    return x * 2

В файле .coveragerc можно также исключить целые модули.

$ pytest --cov=my_module --cov-report=html
(отчет без учета отмеченных строк)

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

En
Python code tests (python)