Практическое тестирование Python-приложений: алгоритмы и модули
Тестирование алгоритмов и программ на Python - неотъемлемая часть разработки, позволяющая убедиться в корректности, производительности и устойчивости кода. В статье рассмотрены различные подходы к тестированию: от простых модульных тестов до property‑based тестирования и замеров производительности.
Основные подходы к тестированию
Как написать простой модульный тест для алгоритма сортировки с помощью pytest?
Решение: pytest - современный и гибкий фреймворк для тестирования. Установите его через pip install pytest, создайте файл test_sort.py и напишите тестовую функцию.
# test_sort.py
def sort_list(arr):
return sorted(arr)
def test_sort_list():
assert sort_list([3, 1, 2]) == [1, 2, 3]
assert sort_list([]) == []
assert sort_list([5]) == [5]
A b test python (a/b тестирование в python)
Запустите тесты командой pytest test_sort.py. Результат:
collected 1 item test_sort.py . [100%] 1 passed in 0.01s
тесты алгоритмы и программирование python (тестирование алгоритмов и программ на python)
Типичные ошибки: отсутствие проверки граничных случаев (пустой список, один элемент, дубликаты, отрицательные числа). Решение - добавлять соответствующие тесты. Также возможны ошибки импорта, если файл лежит не в корне.
Как использовать unittest для тестирования классов?
Решение: встроенный модуль unittest предоставляет класс TestCase. Создайте подкласс и определите методы test_*.
import unittest
def multiply(a, b):
return a * b
class TestMath(unittest.TestCase):
def test_multiply(self):
self.assertEqual(multiply(3, 4), 12)
self.assertEqual(multiply(-1, 5), -5)
if __name__ == "__main__":
unittest.main()
Python code tests (тестирование кода в python)
Типичные ошибки: забывание вызывать assertEqual вместо assert (хотя можно и assert, но теряется читаемость). Также необходимо запускать через python -m unittest или main().
Как встроить тесты в документацию функции с помощью doctest?
Решение: записывайте примеры использования в docstring в формате интерактивной сессии. Модуль doctest автоматически проверяет их.
def factorial(n):
"""
Вычисляет факториал числа n.
>>> factorial(5)
120
>>> factorial(0)
1
>>> factorial(-1)
Traceback (most recent call last):
...
ValueError: n must be non-negative
"""
if n < 0:
raise ValueError("n must be non-negative")
return 1 if n == 0 else n * factorial(n-1)
if __name__ == "__main__":
import doctest
doctest.testmod()
Py test python (написание тестов на python (pytest))
Типичные ошибки: несоответствие пробелов в ожидаемом выводе; забывание импортировать doctest. Для сложных тестов doctest неудобен из-за громоздких docstring.
Как автоматически генерировать тестовые данные для проверки свойств алгоритма?
Решение: библиотека hypothesis генерирует случайные тестовые случаи по стратегиям. Тестируются не конкретные значения, а свойства (property).
from hypothesis import given, strategies as st
from reverse import reverse # reverse(s) возвращает перевёрнутую строку
@given(st.text())
def test_reverse_property(s):
assert reverse(reverse(s)) == s
Test data python (создание тестовых данных в python)
Falsifying example: test_reverse_property(s='\x00')
Типичные ошибки: слишком общие стратегии могут генерировать неинтересные данные (пустая строка). Нужно настраивать стратегии. Также возможны бесконечные циклы, если свойство нарушено.
Как измерить время выполнения алгоритма и убедиться в его эффективности?
Решение: используйте модуль timeit или pytest‑benchmark. Сравните несколько реализаций.
import timeit
def bubble_sort(arr):
for i in range(len(arr)):
for j in range(len(arr)-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[0]
left = [x for x in arr[1:] if x <= pivot]
right = [x for x in arr[1:] if x > pivot]
return quick_sort(left) + [pivot] + quick_sort(right)
# Замеры
t_bubble = timeit.timeit(lambda: bubble_sort([5,3,1,4,2]), number=10000)
t_quick = timeit.timeit(lambda: quick_sort([5,3,1,4,2]), number=10000)
print(f"Bubble: {t_bubble:.4f}s, Quick: {t_quick:.4f}s")
Bubble: 0.4523s, Quick: 0.0876s
Типичные ошибки: однократный замер не показателен; нужно многократное повторение. Разные списки могут давать разное время.
Как тестировать функции, обращающиеся к внешним API или базе данных?
Решение: используйте моки (unittest.mock) для подмены внешних вызовов.
from unittest.mock import patch
import requests
def fetch_data(url):
response = requests.get(url)
return response.json()
def test_fetch_data():
with patch('requests.get') as mock_get:
mock_get.return_value.json.return_value = {'key': 'value'}
result = fetch_data('http://fake.url')
assert result == {'key': 'value'}
Типичные ошибки: неправильный путь для patch (используйте полное имя импортируемого объекта). Также забывают проверить, что вызов действительно произошёл.
Как проверить, что функция выбрасывает правильное исключение?
Решение: в pytest используйте контекстный менеджер pytest.raises.
import pytest
def divide(a, b):
if b == 0:
raise ZeroDivisionError("division by zero")
return a / b
def test_divide_by_zero():
with pytest.raises(ZeroDivisionError, match="division by zero"):
divide(10, 0)
Типичные ошибки: не указан match или указан неверный; использование простого try/except вместо менеджера.
Расширенные примеры тестирования
Приведённые ниже примеры демонстрируют более сложные сценарии, часто встречающиеся в реальной разработке.
Параметризованные тесты с pytest
Параметризация позволяет запустить один тест с разными входными данными без дублирования кода.
import pytest
def is_even(n):
return n % 2 == 0
@pytest.mark.parametrize("n,expected", [
(2, True),
(3, False),
(0, True),
(-2, True),
(-3, False),
])
def test_is_even(n, expected):
assert is_even(n) == expected
collected 5 items test_param.py ..... [100%] 5 passed in 0.01s
Тестирование асинхронного кода с pytest-asyncio
Для асинхронных функций используйте плагин pytest-asyncio.
import pytest
import asyncio
async def fetch_data_async():
await asyncio.sleep(0.1)
return "data"
@pytest.mark.asyncio
async def test_fetch_data_async():
result = await fetch_data_async()
assert result == "data"
collected 1 item test_async.py . [100%] 1 passed in 0.11s
Мокирование внешнего сервиса с unittest.mock
Подмена сложного внешнего вызова, возвращающего итератор или контекстный менеджер.
from unittest.mock import Mock, patch
class Database:
def connect(self):
return "real connection"
def get_data(db):
conn = db.connect()
return f"data from {conn}"
def test_get_data():
mock_db = Mock()
mock_db.connect.return_value = "mock connection"
result = get_data(mock_db)
assert result == "data from mock connection"
Тестирование с hypothesis для сортировки
С помощью hypothesis можно проверить, что функция сортировки возвращает перестановку входного списка и является неубывающей.
from hypothesis import given, strategies as st
def sort_list(arr):
return sorted(arr)
@given(st.lists(st.integers()))
def test_sort_is_sorted_and_permutation(arr):
result = sort_list(arr)
# Проверка, что результат отсортирован
assert all(result[i] <= result[i+1] for i in range(len(result)-1))
# Проверка, что результат является перестановкой (одинаковое количество элементов)
assert sorted(result) == result # нестрого, но для сортировки верно
# Более точная проверка: сравнение мультимножеств со входом
from collections import Counter
assert Counter(result) == Counter(arr)
collected 1 item test_hypothesis_sort.py . [100%] 1 passed in 0.05s