Эффективные методики тестирования программ на 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 (отчет без учета отмеченных строк)