Основы тестирования на языке Python

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

Основы тестирования на Python

Тестирование кода позволяет убедиться в его корректности и предотвратить регрессии. В Python существует несколько инструментов: unittest, pytest, doctest. Рассмотрим наиболее эффективные подходы.

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

Pytest - это современный фреймворк, требующий минимального кода. Достаточно написать функцию с именем, начинающимся на test_, и использовать оператор assert.

def add(a, b):
    return a + b

def test_add_positive():
    assert add(2, 3) == 5

def test_add_zero():
    assert add(0, 5) == 5

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

Затем выполнить команду pytest в терминале. Pytest автоматически найдёт все файлы вида test_*.py и запустит тесты.

Типичная ошибка:

Если модуль pytest не установлен, возникает ModuleNotFoundError. Установка: pip install pytest. Также распространена ошибка пропуска ключевого слова assert или использование assertEqual из unittest, что не нужно в pytest.

Как использовать фикстуры для подготовки данных?

Фикстуры позволяют создавать общие объекты или состояния перед тестами. В pytest фикстура объявляется декоратором @pytest.fixture.

import pytest

@pytest.fixture
def sample_data():
    return {'a': 1, 'b': 2}

def test_sum(sample_data):
    result = sample_data['a'] + sample_data['b']
    assert result == 3

Фикстура передаётся в тестовую функцию как аргумент. Она может также использовать yield для освобождения ресурсов.

Ошибка:

Если забыть добавить имя фикстуры в список аргументов теста, возникнет TypeError. Фикстура должна быть явно запрошена.

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

Параметризация позволяет запустить один тест с разными наборами данных без дублирования кода. Используется декоратор @pytest.mark.parametrize.

import pytest

@pytest.mark.parametrize('a, b, expected', [
    (1, 2, 3),
    (10, -5, 5),
    (0, 0, 0)
])
def test_add(a, b, expected):
    assert a + b == expected

Все три набора будут проверены отдельно, и pytest покажет, какой из них не прошёл.

Ошибка:

Количество аргументов в декораторе должно совпадать с параметрами функции. Несоответствие приведёт к ошибке при запуске.

Как проверить, что функция выбрасывает исключение?

Для тестирования исключений используется контекстный менеджер pytest.raises.

import pytest

def divide(a, b):
    if b == 0:
        raise ValueError('На ноль делить нельзя')
    return a / b

def test_divide_by_zero():
    with pytest.raises(ValueError, match='На ноль делить нельзя'):
        divide(10, 0)

Если исключение не возникнет, тест упадёт.

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

Unittest - встроенный модуль, основанный на JUnit. Тест оформляется как класс, наследующий unittest.TestCase.

import unittest

class TestMath(unittest.TestCase):
    def setUp(self):
        self.a = 5
        self.b = 3

    def test_add(self):
        result = self.a + self.b
        self.assertEqual(result, 8)

    def tearDown(self):
        pass

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

Запуск через python test_file.py. Плюс - не требует установки, минус - более многословный.

Проблема:

Разработчики часто забывают вызывать super().setUp() при переопределении. Это может привести к некорректной инициализации.

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

Doctest позволяет писать примеры внутри docstring в виде диалога интерпретатора. Они автоматически проверяются.

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

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

Запуск python -m doctest module.py.

Ошибка:

Проблемы с отступами: примеры должны быть с отступом 4 пробела или одной табуляцией. Также ожидаемый вывод должен совпадать с фактическим вплоть до пробелов.

В большинстве проектов pytest является предпочтительным из-за простоты и богатых возможностей. Unittest и doctest полезны в специфических сценариях.

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

1. Мокирование внешних вызовов с unittest.mock

Когда функция обращается к внешнему API или базе данных, моки позволяют изолировать тест.

Пример
from unittest.mock import Mock, patch
import requests

def get_user_name(user_id):
    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_response = Mock()
    mock_response.json.return_value = {'name': 'Alice'}
    mock_get.return_value = mock_response
    
    result = get_user_name(1)
    assert result == 'Alice'
    mock_get.assert_called_once_with('https://api.example.com/users/1')
Тест проходит успешно, проверяя вызов URL.

2. Тестирование асинхронного кода с pytest-asyncio

Для async/await функций используется фикстура event_loop или маркер asyncio.

Пример
import pytest
import asyncio

async def fetch_data():
    await asyncio.sleep(0.1)
    return 42

@pytest.mark.asyncio
async def test_fetch_data():
    result = await fetch_data()
    assert result == 42

Необходимо установить pytest-asyncio.

3. Общие фикстуры в conftest.py

Файл conftest.py автоматически считывается pytest и содержит фикстуры, доступные всем тестам в папке.

Пример
# conftest.py
import pytest

@pytest.fixture
def db_connection():
    conn = create_connection()
    yield conn
    conn.close()
Пример
# test_database.py
def test_insert(db_connection):
    db_connection.insert('data')
    assert db_connection.count() == 1

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

Можно задавать идентификаторы для наглядности отчёта.

Пример
@pytest.mark.parametrize('x, y, result', [
    pytest.param(1, 2, 3, id='positive'),
    pytest.param(-1, 1, 0, id='cancel')
])
def test_add(x, y, result):
    assert x + y == result
Pytest отобразит id в отчёте: test_add[positive], test_add[cancel].

5. Тестирование вывода в stdout с capsys

Встроенная фикстура capsys перехватывает вывод.

Пример
def greet(name):
    print(f'Привет, {name}!')

def test_greet(capsys):
    greet('Мир')
    captured = capsys.readouterr()
    assert captured.out == 'Привет, Мир!\n'

Фикстура доступна без дополнительной установки.

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

En
Python тестирование (python)