Отладка и тестирование Python приложений
Основные подходы к проверке Python программ
Как автоматизировать проверку функций с помощью pytest?
Библиотека pytest предоставляет простой синтаксис для написания тестов, обширные возможности фикстур и параметризации. Это основной инструмент для модульного тестирования в современном Python.
# test_math.py
import pytest
def add(a, b):
return a + b
def test_addition():
assert add(2, 3) == 5
assert add(-1, 1) == 0
# Запуск: pytest test_math.py -v
проверить python (проверка установки python)
Фикстуры позволяют подготовить данные:
import pytest
@pytest.fixture
def sample_data():
return {'value': 10}
def test_fixture(sample_data):
assert sample_data['value'] == 10
Python проверка (проверка кода python)
Параметризация для тестирования нескольких вариантов:
@pytest.mark.parametrize("a,b,expected", [
(1, 2, 3),
(0, 0, 0),
(-5, 10, 5)
])
def test_param(a, b, expected):
assert a + b == expected
тест кодов python (тестирование кода python)
Типичные ошибки:
- Забыть установить pytest (pip install pytest). Возникает ModuleNotFoundError.
- Не использовать префикс test_ для файлов и функций. pytest не найдёт тесты.
- Фикстуры не возвращают данные, а только подготавливают окружение.
Как использовать встроенный модуль unittest для тестирования?
Модуль unittest входит в стандартную библиотеку и подходит для написания тестов в объектно-ориентированном стиле. Не требует дополнительных установок.
import unittest
def multiply(x, y):
return x * y
class TestMathFunctions(unittest.TestCase):
def test_multiplication(self):
self.assertEqual(multiply(3, 4), 12)
if __name__ == '__main__':
unittest.main()
проверить программу python (проверить программу на python)
Методы setUp и tearDown для подготовки и очистки:
class TestWithSetup(unittest.TestCase):
def setUp(self):
self.data = [1, 2, 3]
def test_sum(self):
self.assertEqual(sum(self.data), 6)
def tearDown(self):
del self.data
исправить ошибки в кодах python (исправление ошибок в коде python)
Частые проблемы:
- Название метода теста не начинается с test. Метод не будет выполнен.
- Забыть вызвать super().setUp() при наследовании.
- Использовать assert вместо self.assert... сравнения.
Как пошагово отлаживать программу с pdb?
Отладчик pdb позволяет останавливать выполнение, просматривать переменные и выполнять код по шагам. Встраивается в программу одной строкой.
import pdb
def factorial(n):
result = 1
for i in range(1, n+1):
pdb.set_trace() # Вход в отладчик
result *= i
return result
print(factorial(3))
Команды внутри pdb: n (next), c (continue), p var (печать), l (список строк).
(Pdb) p i 1 (Pdb) p result 1 (Pdb) n --Return-- > ...
Возможные сложности:
- Множественные вызовы set_trace() в цикле приводят к частым остановкам.
- Запуск программы в фоне делает pdb неприменимым.
- Не установлен breakpoint (Python 3.7+ рекомендуется использовать встроенную функцию breakpoint()).
Как добавить диагностические сообщения для проверки состояния программы?
Модуль logging даёт гибкий механизм вывода сообщений разного уровня. Позволяет вести лог и не засорять код print.
import logging
logging.basicConfig(level=logging.DEBUG, format='%(levelname)s - %(message)s')
def divide(a, b):
logging.debug(f"Вызов divide с a={a}, b={b}")
if b == 0:
logging.error("Деление на ноль!")
return None
return a / b
divide(10, 2)
divide(5, 0)
DEBUG - Вызов divide с a=10, b=2 DEBUG - Вызов divide с a=5, b=0 ERROR - Деление на ноль!
Настройка вывода в файл:
logging.basicConfig(filename='app.log', level=logging.INFO)
Ошибки при работе с logging:
- Вызов basicConfig после первого логирования не применяется.
- Забыть задать уровень, сообщения не выводятся.
- Использовать print() вместо logging для отладки.
Как использовать assert для проверки условий?
Инструкция assert проверяет условие и выбрасывает AssertionError, если оно ложно. Подходит для быстрых проверок во время разработки.
def sqrt_approx(x):
assert x >= 0, "Число должно быть неотрицательным"
# реализация...
return x ** 0.5
sqrt_approx(9) # ОК
sqrt_approx(-1) # AssertionError
Можно использовать двоеточие для сообщения об ошибке.
Предостережения:
- assert отключается при запуске с флагом -O. Не подходит для проверок, которые обязательны в продакшене.
- Сообщение в assert не должно содержать конфиденциальные данные.
Как проверить типы и найти потенциальные ошибки с помощью статического анализа?
Инструменты вроде mypy анализируют код до выполнения, выявляя несоответствия типов. Это помогает предотвратить баги на ранних этапах.
# mypy_example.py
def greet(name: str) -> str:
return "Hello, " + name
greet(42) # mypy укажет ошибку: Argument 1 has incompatible type 'int'
$ mypy mypy_example.py mypy_example.py:4: error: Argument 1 to "greet" has incompatible type "int"; expected "str"
Аннотации типов не обязательны, но mypy использует их для проверки. Возможна интеграция с pytest через плагин pytest-mypy.
Трудности:
- Сторонние библиотеки без аннотаций типов могут выдавать ложные срабатывания.
- Строгая проверка требует времени на разметку кода.
- Некоторые динамические возможности Python (например, type() для создания классов) не анализируются корректно.
Как встроить тесты в документацию функции с помощью доктестов?
Модуль doctest извлекает примеры из строк документации и выполняет их как тесты. Идеально для небольших проверок и документации.
def add(a, b):
"""
Возвращает сумму двух чисел.
>>> add(2, 3)
5
>>> add(-1, 1)
0
"""
return a + b
if __name__ == '__main__':
import doctest
doctest.testmod()
$ python mymodule.py -v
Trying:
add(2, 3)
Expecting:
5
ok
Trying:
add(-1, 1)
Expecting:
0
ok
1 items passed all tests.
Проблемы:
- Строки вывода должны совпадать точно, включая пробелы.
- При сложной логике доктесты становятся громоздкими.
- Забыть импортировать doctest или добавить testmod.
Расширенные примеры проверки Python программ
Пример интеграционного теста с pytest и mock
# test_user.py
import pytest
from unittest.mock import Mock
def get_user(user_id):
# имитация внешнего API
response = requests.get(f"https://api.example.com/user/{user_id}")
return response.json()
def test_get_user_success(monkeypatch):
mock_response = Mock()
mock_response.json.return_value = {'id': 1, 'name': 'Alice'}
monkeypatch.setattr('requests.get', lambda url: mock_response)
result = get_user(1)
assert result == {'id': 1, 'name': 'Alice'}
Пример использует monkeypatch для замены вызова requests.get. Это позволяет тестировать код без реальных сетевых запросов.
$ pytest test_user.py -v ============================= test session starts ============================== ... test_user.py::test_get_user_success PASSED
Отладка рекурсии с pdb и точками останова по условию
import pdb
def fibonacci(n):
if n < 0:
return None
if n in (0, 1):
return n
pdb.set_trace() if n == 5 else None # остановка только при n=5
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(6))
(Pdb) n ...
Можно устанавливать условные точки останова через pdb.set_trace() или breakpoint() с условием. Это ускоряет отладку больших вызовов.
Логирование в файл с ротацией и разными уровнями
import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
handler = RotatingFileHandler('app.log', maxBytes=500, backupCount=3)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
def process_data(data):
logger.debug("Начало обработки")
if not data:
logger.warning("Пустые данные")
return
try:
result = 1 / data[0]
logger.info("Результат получен: %s", result)
except ZeroDivisionError:
logger.error("Деление на ноль в данных")
logger.debug("Конец обработки")
process_data([2])
process_data([0])
После выполнения в каталоге появится файл app.log и его ротационные копии. Ротация предотвращает бесконечное увеличение лога.
Проверка соответствия типов с mypy для сложного кода
# complex_types.py
from typing import List, Optional, Dict, Union
def parse_config(config: Dict[str, Union[str, int]]) -> Optional[str]:
if 'name' in config and isinstance(config['name'], str):
return config['name']
return None
def run(items: List[int]) -> List[int]:
return [x * 2 for x in items]
result = run([1,2,3])
print(result)
$ mypy complex_types.py --strict Success: no issues found in 1 source file
При нарушении типов mypy укажет строку и суть ошибки. Рекомендуется включать флаг --strict для максимальной проверки.
Комбинированный подход: unittest + logging для отладки
import unittest
import logging
logging.basicConfig(level=logging.DEBUG)
def calculate_discount(price, percent):
logging.debug("Расчёт скидки для цены %s и процента %s", price, percent)
if price < 0 or percent < 0 or percent > 100:
logging.error("Некорректные аргументы")
raise ValueError
discount = price * percent / 100
return price - discount
class TestDiscount(unittest.TestCase):
def test_discount_positive(self):
self.assertEqual(calculate_discount(100, 10), 90)
def test_discount_invalid_percent(self):
with self.assertRaises(ValueError):
calculate_discount(100, 110)
if __name__ == '__main__':
unittest.main()
Логирование помогает увидеть внутреннее состояние при выполнении тестов, не останавливая программу. Все сообщения выводятся в консоль вместе с результатами тестов.
Доктесты со сложными структурами данных
def sort_dict_by_value(d):
"""
Сортирует словарь по значениям и возвращает список кортежей.
Пример:
>>> sort_dict_by_value({'a': 3, 'b': 1, 'c': 2})
[('b', 1), ('c', 2), ('a', 3)]
>>> sort_dict_by_value({})
[]
"""
return sorted(d.items(), key=lambda item: item[1])
if __name__ == '__main__':
import doctest
doctest.testmod()
Вывод точно соответствует ожидаемому формату. Пробелы и порядок ключей имеют значение.
$ python doctest_example.py -v ... 1 items passed all tests.