Эта статья является продолжением статьи Пишем простую программу на python и входит в цикл статей Пишем свою библиотеку с нуля и публикуем её в PyPI (GitVerse)
В прошлой статье был написан код программы, но программа не тестировалась и не проверялась на линтеры и прочие чекеры.
В этой статье мы быстро пробежимся по тестам, которые можно взять из git по ссылке. А после сделаем проверки тестов, покрытия, mypy, isort, pylint, vulture и black.
Используемые технологии
ОС: Windows 10
Язык: Python 3.14
Тесты
Вообще тесты надо писать во время кодинга и даже до кодинга основной программы. Но в рамках статьи я отошел от этого стандарта, чтоб все проверки разместить в одной статье.
Структура тестов: что и зачем
Все тесты размещаем рядом с нашей программой в папке \bnkc\tests.
conftest.py — Конфигурация pytest
- Единая точка конфигурации
- Загружается до выполнения тестов
- Тут можно добавить фикстуры, действия до/после тестов, конфигурировать плагины, менять настройки pytest
test_calculator.py — Тесты бизнес-логики
- Используем фикстуры, так каждый тест получает чистый объект, не зависящий от других тестов.
- Проверяем каждый метод отдельно
- Используем маркеры, для группировки тестов
test_cli.py — Тесты CLI
- Отдельный файл для тестирования при работе с консолью
Чтоб не усложнять проект, у нас только 3 файла. Конечно, можно было бы добавить ещё тестов, добавить папки и раскидать по ним тесты, но решил не усложнять.
Устанавливаем pytest
Заходим в виртуальное окружение и устанавливаем нужные библиотеки
cd bnkc
venv\Scripts\activate
py -m pip install pytest
py -m pip install pytest-cov
pip freeze > requirements.txt
deactivate
Дополним pyproject.toml
Добавляем блок опциональных зависимостей и создаем в нем раздел test.
[project.optional-dependencies]
test = [
"pytest>=9.0.2",
"pytest-cov>=7.0.0"
]
За счет этой строки можно будет производить установку зависимостей для теста командой
pip install -e ".[test]"
Добавляем блок [tool.pytest.ini_options]
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
Указываем в блоке где искать тесты, как они должны именоваться.
В этом же блоке указываем дополнительные опции запуска для большей детализации вывода в консоль.
addopts = [
"--tb=short",
"-v",
]
В этом же блоке указываем маркеры, которыми мы декорировали скрипты в тестах.
markers = [
"unit: unit tests",
"integration: integration tests",
"credit: tests for Credit class",
"schedule: tests for PaymentSchedule",
"cli: tests for command line interface",
"calculation: calculation tests",
"statistics: statistics tests",
"validation: validation tests",
"parsing: parsing tests",
"edge_case: edge case tests",
"error_handling: error handling tests",
"smoke: smoke tests",
]
Добавляем блок [tool.coverage.run]
В нем указываем, что будем тестировать на покрытие, а что не будем.
[tool.coverage.run]
source = ["bnkc"]
omit = [
"*/tests/*",
"*/__pycache__/*",
"*/test_*.py",
]
Добавляем блок [tool.coverage.report]
Добавляем для теста покрытия исключения на уровне кода.
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if self.debug:",
"raise AssertionError",
"raise NotImplementedError",
]
Запускаем тесты
Запускаем просто тесты из корня проекта
pytest
Как видим, есть ошибки
Одна ошибка в файле \bnkc\utils\__init__.py. Переменных MONEY_PRECISION и ROUNDING не существует. Их надо удалить.
Другая ошибка в самом тесте. Такое может происходить, когда бизнес логика меняется, а тесты забывают исправить.
В файле \bnkc\tests\test_credit.py строку 77 заменить на
with pytest.raises(ValueError, match="1000"):
Запустим еще раз.
pytest
Тесты пройдены. Теперь запустим тесты покрытия.
pytest --cov=bnkc --cov-report=term-missing
Тесты прошли, но покрытие тестами всего 69%. Считается, что покрывать код 100% тестами не имеет смысла, достаточно 80-90%. У нас сейчас меньше, но это учебный проект, пока достаточно.
Исходники после тестов
Работаем с mypy
Устанавливаем mypy в проект
Заходим в виртуальное окружение и устанавливаем нужную библиотеку
cd bnkc
venv\Scripts\activate
py -m pip install mypy
pip freeze > requirements.txt
deactivate
Запускаем проверку mypy
В корне проекта набираем команду
mypy bnkc
Получаем множество ошибок.
Из ошибок узнаем, что нужна еще одна библиотека.
Заходим в виртуальное окружение и устанавливаем нужную библиотеку
cd bnkc
venv\Scripts\activate
py -m pip install types-python-dateutil
pip freeze > requirements.txt
deactivate
Также надо в файле \bnkc\core\schedule.py заменить строки
total_payment = sum(p.payment for p in self.payments)
total_interest = sum(p.interest for p in self.payments)
total_principal = sum(p.principal for p in self.payments)
на
total_payment = Decimal(sum(p.payment for p in self.payments))
total_interest = Decimal(sum(p.interest for p in self.payments))
total_principal = Decimal(sum(p.principal for p in self.payments))
Набираем команду
mypy bnkc
Ошибок нет
Дополним pyproject.toml
В блок опциональных зависимостей добавляем раздел dev.
dev = [
"mypy>=1.19.1",
"types-python-dateutil>=2.9.0",
]
Создадим раздел с конфигом для mypy. И обновим перечень проверок.
[tool.mypy]
python_version = "3.14"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
disallow_untyped_decorators = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
Добавим блок с исключениями
[[tool.mypy.overrides]]
module = [
"tests.*"
]
disallow_untyped_defs = false
Повторно проверяем mypy
Набираем команду
mypy bnkc
Новые ошибки.
Необходимо в файле \bnkc\core\credit.py заменить строку
def from_string(cls, credit_string: str, **kwargs):
на
def from_string(cls, credit_string: str, start_date: str | None = None) -> Credit:
и строку
return cls(amount, rate, term, **kwargs)
на
return cls(amount, rate, term, start_date)
И в файле \bnkc\__main__.py заменить строку
def main():
на
def main() -> None:
Набираем команду
mypy bnkc
Ошибок нет
Исходники после mypy
Прочие линтеры
Устанавливаем нужные зависимости в проект
Заходим в виртуальное окружение и устанавливаем нужные библиотеки
cd bnkc
venv\Scripts\activate
py -m pip install isort pylint vulture black
pip freeze > requirements.txt
deactivate
Дополним pyproject.toml
Не будем запускать проверки, давайте сразу укажем нужные настройки и потом запустим.
В блоке опциональных зависимостей обновляем раздел dev.
dev = [
"mypy>=1.19.1",
"types-python-dateutil>=2.9.0",
"black>=26.1.0",
"isort>=7.0.0",
"pylint>=4.0.4",
"vulture>=2.14.0"
]
Добавляем конфиги для pylint
[tool.pylint.master]
fail-under = 8.0
max-line-length = 88
[tool.pylint.messages_control]
disable = [
"missing-module-docstring",
"too-few-public-methods",
"missing-class-docstring",
"missing-function-docstring",
"import-error",
"wrong-import-order",
"broad-exception-caught",
]
[tool.pylint.format]
max-line-length = 88
single-line-if-stmt = true
[tool.pylint.design]
max-attributes = 15
min-public-methods = 1
[tool.pylint.basic]
good-names = ["i", "j", "k", "ex", "run", "a", "b", "x", "y", "args"]
Добавляем конфиги для vulture
[tool.vulture]
exclude = [
"*/tests/*",
"*__pycache__/*",
"*/test_*.py",
"build/*",
"dist/*",
"venv/*",
]
min_confidence = 80
Добавляем конфиги для black
[tool.black]
line-length = 88
target-version = ['py314']
Добавляем конфиги для isort
[tool.isort]
profile = "black"
multi_line_output = 3
line_length = 88
Я расписывать, что означают настройки, не буду. Каждый в своем проекте сам определяет, что ему важно и нужно. Поэтому тут набор настроек может варьироваться.
Запускаем проверки линтеров
Правим стилистику кода
py -m black .
Правим импорты
py -m isort .
Находим ошибки качества кода
py -m pylint bnkc/
Ошибки есть. Надо править. Тут правки описывать не буду. Ниже будет ссылка на репозиторий с правками.
Проверка мёртвого кода
py -m vulture bnkc/
Еще раз прогоняем mypy и тесты
mypy bnkc
pytest
Надеюсь, у вас, как и у меня, всё без ошибок)
Сохраняем проект в репозитории
Отправляем данные в удаленный репозиторий
git add .
git commit -m "пройдена проверка линтерами"
git push -u origin dev
В следующей статье мы настроим CI в GitVerse
Исходники проекта
Подписывайтесь на Дзен, а также приглашаю в мой телеграмм канал, там публикую другой, но не менее интересный контент.