Основы тестирования на языке 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) == 5Python тестирование (тестирование на 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() == 14. Параметризация с несколькими аргументами и понятными именами
Можно задавать идентификаторы для наглядности отчёта.
@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 == resultPytest отобразит 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'Фикстура доступна без дополнительной установки.