Как проверять код на Python: основные методы и подходы
Методы проверки кода Python
Проверка кода включает в себя не только тестирование, но и статический анализ, отладку и логирование. В этой статье рассматриваются наиболее популярные подходы с акцентом на практические примеры.
Какой инструмент обеспечивает максимальную гибкость и удобство при написании тестов?
Наиболее эффективным решением для тестирования Python-кода считается pytest. Он позволяет писать тесты в минималистичном стиле, используя обычные assert, поддерживает параметризацию, фикстуры и множество плагинов.
# Пример простого теста с pytest
# файл test_sample.py
def test_addition():
assert 1 + 1 == 2
def test_subtraction():
assert 3 - 2 == 1проверить python (проверка установки python)
Запуск тестов выполняется командой pytest test_sample.py в терминале. Результат - зеленая точка за каждый пройденный тест.
Типичные проблемы:
- Непонимание механизма фикстур (scope, autouse). Решение: изучить документацию, начать с простых
@pytest.fixture. - Сложность настройки плагинов (например, coverage). Решение: использовать
pytest-covс минимальной конфигурацией. - Ошибки импорта при запуске. Решение: запускать pytest из корня проекта или использовать
PYTHONPATH.
Как провести тестирование без установки сторонних библиотек?
Модуль unittest является частью стандартной библиотеки Python. Он предоставляет класс TestCase, методы assertEqual, setUp, tearDown и позволяет создавать наборы тестов.
import unittest
class TestMath(unittest.TestCase):
def test_add(self):
self.assertEqual(1 + 1, 2)
if __name__ == '__main__':
unittest.main()Python проверка (проверка кода python)
Запуск: python test_unittest.py.
Распространенные ошибки:
- Игнорирование
if __name__ == '__main__'. Без него тесты не запускаются при импорте. - Путаница между
setUpиsetUpClass. Первый выполняется перед каждым тестом, второй - один раз для класса. - Забытие сброса состояния между тестами. Решение: использовать
tearDown.
Как автоматически проверить примеры кода в документации?
Модуль doctest извлекает фрагменты кода из docstring и выполняет их, сравнивая вывод с ожидаемым. Это удобно для небольших модулей и документации.
def factorial(n):
"""
Вычисляет факториал n.
>>> factorial(5)
120
>>> factorial(0)
1
"""
if n == 0:
return 1
return n * factorial(n-1)
if __name__ == '__main__':
import doctest
doctest.testmod()тест кодов python (тестирование кода python)
Запуск: python doctest_example.py -v для подробного вывода.
Проблемы:
- Чувствительность к пробелам в ожидаемом выводе. Решение: использовать
# doctest: +NORMALIZE_WHITESPACE. - Не подходит для тестирования сложной логики с побочными эффектами.
- Ошибки в docstring могут быть не замечены, если запускать без флага
-v.
Как обнаружить ошибки типов до выполнения программы?
Статический анализатор типов mypy проверяет аннотации типов и выявляет несоответствия. Это помогает ловить баги на этапе написания кода.
# file: type_example.py
def greet(name: str) -> str:
return 'Hello, ' + name
result = greet(42) # Ошибка: int вместо strпроверить программу python (проверить программу на python)
Запуск: mypy type_example.py выдаст ошибку несовместимости типов.
Частые сложности:
- Отсутствие аннотаций в сторонних библиотеках. Решение: установить
types-*пакеты или использовать--ignore-missing-imports. - Излишняя строгость для динамического кода. Решение: настроить
mypy.iniс параметромstrict = false. - Путаница с типами Union, Optional. Рекомендуется изучить модуль
typing.
Как пошагово выполнить код для поиска логических ошибок?
Встроенный отладчик pdb позволяет устанавливать точки останова, просматривать переменные, выполнять код по шагам. Это незаменимо при сложных алгоритмах.
import pdb
def compute(x):
y = x * 2
pdb.set_trace() # точка останова
z = y + 10
return z
result = compute(5)исправить ошибки в кодах python (исправление ошибок в коде python)
Запуск: python pdb_example.py. В консоли pdb доступны команды: n (next), c (continue), p variable (печать).
Типичные ошибки:
- Оставление
pdb.set_trace()в продакшн-коде. Решение: использовать условную отладку или удалять перед коммитом. - Незнание команд pdb (например,
lдля листинга,qдля выхода). Рекомендуется держать шпаргалку. - Проблемы с многопоточностью - pdb не работает надежно с потоками.
Как отлаживать программу с помощью логов, не засоряя код print?
Модуль logging предоставляет гибкую систему записи сообщений с разными уровнями (DEBUG, INFO, WARNING, ERROR). Логи можно выводить в консоль, файл, сеть.
import logging
logging.basicConfig(level=logging.DEBUG)
def divide(a, b):
logging.debug(f'Деление {a} на {b}')
if b == 0:
logging.error('Попытка деления на ноль')
return None
return a / b
print(divide(10, 2))
print(divide(10, 0))Результат: в консоль выводятся строки с уровнем DEBUG и ERROR.
Проблемы:
- Конфигурация логгеров может быть запутанной (корневой логгер, форматтеры, хендлеры). Решение: использовать
basicConfigдля простых случаев, а для сложных - отдельный файл конфигурации. - Избыточность логов в продакшне. Решение: установить уровень
WARNINGилиERROR. - Неверное использование уровней (например, DEBUG для обычных сообщений). Рекомендуется придерживаться общепринятой семантики.
Расширенные примеры проверки кода
Ниже приведены подробные примеры с кодом и ожидаемым результатом для каждого из описанных методов.
1. Параметризованные тесты в pytest с фикстурами
import pytest
@pytest.fixture
def sample_data():
return {'a': 10, 'b': 20}
@pytest.mark.parametrize('key, expected', [
('a', 10),
('b', 20),
('c', KeyError)
])
def test_fixture_values(sample_data, key, expected):
if key == 'c':
with pytest.raises(KeyError):
_ = sample_data[key]
else:
assert sample_data[key] == expected$ pytest test_param.py -v ============================= test session starts ============================== collected 3 items test_param.py::test_fixture_values[a-10] PASSED test_param.py::test_fixture_values[b-20] PASSED test_param.py::test_fixture_values[c-KeyError] PASSED ============================== 3 passed in 0.02s ===============================
2. Тестирование с unittest и mock
import unittest
from unittest.mock import patch
from math import sqrt
def fetch_value(calculator, x):
return calculator.sqrt(x)
class TestWithMock(unittest.TestCase):
@patch('math.sqrt')
def test_fetch(self, mock_sqrt):
mock_sqrt.return_value = 25
result = fetch_value(sqrt, 100) # передаем реальную функцию, но она заменена mock
self.assertEqual(result, 25)
mock_sqrt.assert_called_once_with(100)
if __name__ == '__main__':
unittest.main()$ python test_mock.py . ---------------------------------------------------------------------- Ran 1 test in 0.001s OK
3. Доктест с обработкой исключений
def safe_divide(a, b):
"""
Делит a на b, возвращает None при ошибке.
>>> safe_divide(10, 2)
5.0
>>> safe_divide(10, 0) is None
True
>>> safe_divide('a', 2) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
TypeError: unsupported operand type(s) for /: 'str' and 'int'
"""
try:
return a / b
except ZeroDivisionError:
return None
if __name__ == '__main__':
import doctest
doctest.testmod(verbose=True)$ python doctest_adv.py
Trying:
safe_divide(10, 2)
Expecting:
5.0
ok
Trying:
safe_divide(10, 0) is None
Expecting:
True
ok
Trying:
safe_divide('a', 2)
Expecting:
Traceback (most recent call last):
TypeError: unsupported operand type(s) for /: 'str' and 'int'
ok
2 items had no tests:
__main__
__main__.safe_divide
1 items passed all tests:
3 tests in __main__.safe_divide
3 tests in 3 items.
3 passed and 0 failed.
Test passed.4. Статический анализ с mypy для класса
from typing import List, Optional
class Person:
def __init__(self, name: str, age: Optional[int] = None) -> None:
self.name = name
self.age = age
def greet(self) -> str:
if self.age:
return f'Привет, меня зовут {self.name}, мне {self.age} лет'
return f'Привет, я {self.name}'
def introduce(people: List[Person]) -> None:
for person in people:
print(person.greet())
# Ошибка: передаётся число вместо списка
introduce(123)$ mypy class_example.py class_example.py:18: error: Argument 1 to "introduce" has incompatible type "int"; expected "List[Person]" Found 1 error in 1 file (checked 1 source file)
5. Пошаговая отладка с pdb и условными брейкпоинтами
def complex_loop(n):
total = 0
for i in range(n):
total += i
if i == 3:
import pdb
pdb.set_trace() # остановка только при i == 3
total *= 1
return total
complex_loop(10)$ python pdb_conditional.py > /Users/user/pdb_conditional.py(7)complex_loop() -> total *= 1 (Pdb) p i 3 (Pdb) p total 6 (Pdb) c
6. Логирование с ротацией файлов и разными форматтерами
import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger('myapp')
logger.setLevel(logging.DEBUG)
# Консольный вывод
console = logging.StreamHandler()
console.setLevel(logging.INFO)
formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
console.setFormatter(formatter)
logger.addHandler(console)
# Файл с ротацией
file_handler = RotatingFileHandler('app.log', maxBytes=100, backupCount=2)
file_handler.setLevel(logging.DEBUG)
file_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(file_formatter)
logger.addHandler(file_handler)
logger.debug('Отладочное сообщение')
logger.info('Информация')
logger.warning('Предупреждение')
logger.error('Ошибка')Консоль: myapp - INFO - Информация myapp - WARNING - Предупреждение myapp - ERROR - Ошибка Файл app.log: 2025-03-27 20:15:30,123 - myapp - DEBUG - Отладочное сообщение 2025-03-27 20:15:30,123 - myapp - INFO - Информация 2025-03-27 20:15:30,123 - myapp - WARNING - Предупреждение 2025-03-27 20:15:30,123 - myapp - ERROR - Ошибка