Введение
Когда вы разрабатываете библиотеки или приложения на Python, крайне важно убедиться в их совместимости с различными версиями интерпретатора. Особенно актуально это становится при публикации пакетов на PyPI, где пользователи могут использовать разные версии Python. Ручная установка Python 3.7, 3.8, 3.9, 3.10 и последующее тестирование каждой версии отнимает много времени и сил.
Здесь на помощь приходит tox — мощный инструмент автоматизации тестирования, который позволяет запускать тесты в изолированных виртуальных окружениях с различными версиями Python. Tox обеспечивает автоматическую установку зависимостей, создание чистых окружений и гарантирует воспроизводимость результатов тестирования.
Что такое tox и зачем он нужен
Tox (сокращение от "testing out of the box") — это инструмент командной строки, написанный на Python, который автоматизирует процесс тестирования Python-проектов в различных окружениях. Он создает изолированные виртуальные окружения, устанавливает в них зависимости и выполняет тесты, что позволяет проверить совместимость кода с разными версиями Python и различными наборами зависимостей.
Основные преимущества tox:
Изоляция окружений — каждый тест выполняется в отдельном виртуальном окружении, что исключает конфликты зависимостей.
Автоматизация — полностью автоматический процесс создания окружений, установки зависимостей и запуска тестов.
Многоверсионность — возможность одновременного тестирования на разных версиях Python.
Воспроизводимость — гарантия получения одинаковых результатов в разных окружениях.
Интеграция с CI/CD — простая настройка для автоматического тестирования в системах непрерывной интеграции.
Установка и первоначальная настройка
Установка tox
Установить tox можно несколькими способами:
# Через pip
pip install tox
# Через conda
conda install -c conda-forge tox
# Через pipx (рекомендуемый способ)
pipx install tox
Создание базового файла конфигурации
Основным файлом конфигурации tox является tox.ini, который должен располагаться в корневой директории проекта:
[tox]
envlist = py38, py39, py310, py311
[testenv]
deps =
pytest
pytest-cov
commands =
pytest tests/ --cov=src/
Структура проекта
Для корректной работы tox рекомендуется следующая структура проекта:
my_project/
├── src/
│ └── my_package/
│ ├── __init__.py
│ └── module.py
├── tests/
│ ├── __init__.py
│ └── test_module.py
├── tox.ini
├── setup.py (или pyproject.toml)
└── requirements.txt
Основы работы с tox
Базовые команды
Tox предоставляет простой интерфейс командной строки:
# Запуск всех окружений из envlist
tox
# Запуск конкретного окружения
tox -e py39
# Запуск нескольких окружений
tox -e py38,py39,lint
# Принудительное пересоздание окружений
tox -r
# Запуск с подробным выводом
tox -v
# Очень подробный вывод (для отладки)
tox -vv
Как работает tox
Процесс работы tox состоит из следующих этапов:
- Анализ конфигурации — чтение файла
tox.iniи определение списка окружений - Создание виртуальных окружений — для каждого указанного Python-интерпретатора
- Установка пакета — установка тестируемого пакета в каждое окружение
- Установка зависимостей — установка зависимостей, указанных в секции
deps - Выполнение команд — запуск команд, определенных в секции
commands - Сбор результатов — агрегация результатов тестирования
Подробная конфигурация tox.ini
Основные секции
Файл tox.ini состоит из нескольких основных секций:
Секция [tox]
Содержит глобальные настройки:
[tox]
# Список окружений по умолчанию
envlist = py38, py39, py310, py311, pypy3
# Минимальная версия tox
minversion = 3.8.0
# Пропуск отсутствующих интерпретаторов
skip_missing_interpreters = true
# Изоляция сборки
isolated_build = true
Секция [testenv]
Базовая конфигурация для всех окружений:
[testenv]
# Зависимости для тестирования
deps =
pytest>=6.0
pytest-cov
pytest-mock
requests
# Команды для выполнения
commands =
pytest {posargs:tests/} --cov=src/ --cov-report=term-missing
# Передача переменных окружения
passenv =
CI
GITHUB_*
PYTEST_*
# Установка переменных окружения
setenv =
PYTHONPATH = {toxinidir}
COVERAGE_FILE = {envtmpdir}/.coverage
# Дополнительные аргументы pip
pip_pre = false
download = true
Специализированные окружения
Можно создавать специализированные окружения для различных задач:
[testenv:lint]
description = Проверка стиля кода
deps =
flake8
black
isort
pylint
commands =
black --check --diff src/ tests/
isort --check-only --diff src/ tests/
flake8 src/ tests/
pylint src/
[testenv:typecheck]
description = Проверка типов
deps =
mypy
types-requests
commands =
mypy src/ --strict
[testenv:security]
description = Проверка безопасности
deps =
bandit[toml]
safety
commands =
bandit -r src/ -f json
safety check
[testenv:docs]
description = Сборка документации
deps =
sphinx
sphinx-rtd-theme
commands =
sphinx-build -b html docs/ docs/_build/
Тестирование на разных версиях Python
Указание версий Python
В секции envlist можно указывать различные версии Python:
[tox]
envlist =
py37, py38, py39, py310, py311 # CPython
pypy3 # PyPy
jython # Jython (если установлен)
Установка нескольких версий Python
Для работы с несколькими версиями Python рекомендуется использовать:
pyenv (Linux/macOS)
# Установка pyenv
curl https://pyenv.run | bash
# Установка нужных версий Python
pyenv install 3.8.10
pyenv install 3.9.16
pyenv install 3.10.11
pyenv install 3.11.3
# Установка всех версий как доступных
pyenv global 3.11.3 3.10.11 3.9.16 3.8.10
Chocolatey (Windows)
# Установка через chocolatey
choco install python38 python39 python310 python311
Проверка доступных интерпретаторов
# Показать все доступные окружения
tox --listenvs-all
# Показать только сконфигурированные окружения
tox --listenvs
Управление зависимостями
Способы указания зависимостей
Прямое указание в deps
[testenv]
deps =
pytest>=6.0
requests>=2.25.0
numpy==1.21.0
Использование requirements.txt
[testenv]
deps =
-r{toxinidir}/requirements.txt
-r{toxinidir}/requirements-test.txt
Условные зависимости
[testenv]
deps =
pytest
py38: importlib-metadata # только для Python 3.8
py{37,38}: dataclasses # для Python 3.7 и 3.8
Работа с pyproject.toml
Начиная с tox 4.0, поддерживается чтение зависимостей из pyproject.toml:
[testenv]
deps =
extras =
test
dev
Соответствующий pyproject.toml:
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "my-package"
dependencies = ["requests", "click"]
[project.optional-dependencies]
test = ["pytest", "pytest-cov"]
dev = ["black", "flake8", "mypy"]
Расширенные возможности и параллелизм
Параллельное выполнение
Tox поддерживает параллельное выполнение окружений:
# Автоматическое определение количества процессов
tox -p auto
# Указание конкретного количества процессов
tox -p 4
# Параллельный запуск с живым выводом
tox --parallel-live
Передача аргументов в команды
# Передача дополнительных аргументов в pytest
tox -- --verbose --tb=short
# Для конкретного окружения
tox -e py39 -- --maxfail=1
Использование факторов
Факторы позволяют создавать комбинации окружений:
[tox]
envlist = py{38,39,310}-django{32,40,41}
[testenv]
deps =
django32: Django>=3.2,<3.3
django40: Django>=4.0,<4.1
django41: Django>=4.1,<4.2
pytest
commands = pytest
Интеграция с системами непрерывной интеграции
GitHub Actions
Создайте файл .github/workflows/test.yml:
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox tox-gh-actions
- name: Run tests with tox
run: tox
Соответствующая конфигурация tox:
[tox]
envlist = py38, py39, py310, py311
[gh-actions]
python =
3.8: py38
3.9: py39
3.10: py310
3.11: py311
[testenv]
deps = pytest
commands = pytest
GitLab CI
Файл .gitlab-ci.yml:
image: python:3.11
stages:
- test
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
cache:
paths:
- .cache/pip/
- .tox/
before_script:
- python -V
- pip install tox
test:
stage: test
script:
- tox
parallel:
matrix:
- PYTHON_VERSION: ['3.8', '3.9', '3.10', '3.11']
image: python:$PYTHON_VERSION
Jenkins
pipeline {
agent any
stages {
stage('Test') {
matrix {
axes {
axis {
name 'PYTHON_VERSION'
values '3.8', '3.9', '3.10', '3.11'
}
}
stages {
stage('Run Tests') {
steps {
sh """
python${PYTHON_VERSION} -m pip install tox
tox -e py${PYTHON_VERSION.replace('.', '')}
"""
}
}
}
}
}
}
post {
always {
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'htmlcov',
reportFiles: 'index.html',
reportName: 'Coverage Report'
])
}
}
}
Плагины и расширения tox
Популярные плагины
tox-gh-actions
Автоматическая интеграция с GitHub Actions:
pip install tox-gh-actions
[gh-actions]
python =
3.8: py38, lint
3.9: py39
3.10: py310
3.11: py311, typecheck
tox-docker
Запуск тестов в Docker-контейнерах:
pip install tox-docker
[testenv:integration]
docker =
postgres:13
redis:6
deps =
pytest
psycopg2
redis
commands = pytest tests/integration/
tox-conda
Использование conda для управления окружениями:
pip install tox-conda
[testenv]
conda_deps =
numpy
scipy
conda_channels =
conda-forge
tox-pyenv
Автоматический поиск Python через pyenv:
pip install tox-pyenv
Создание собственных плагинов
Tox поддерживает создание пользовательских плагинов через систему хуков:
# tox_custom_plugin.py
import tox
@tox.hookimpl
def tox_addoption(parser):
parser.add_argument(
"--custom-flag",
action="store_true",
help="Enable custom functionality"
)
@tox.hookimpl
def tox_configure(config):
if config.option.custom_flag:
# Кастомная логика
pass
Отладка и диагностика
Режимы отладки
# Подробный вывод
tox -v
# Очень подробный вывод
tox -vv
# Показ конфигурации без выполнения
tox --showconfig
# Только установка зависимостей без выполнения команд
tox --notest
Работа с кэшем
# Принудительное пересоздание окружений
tox -r
# Очистка кэша pip
tox -r --force-dep-version
# Показать расположение окружений
tox --showconfig | grep envdir
Частые проблемы и их решения
#### Интерпретатор Python не найден
Проблема: ERROR: InterpreterNotFound: python3.9
Решение:
# Установите нужную версию Python
pyenv install 3.9.16
pyenv global 3.11.3 3.10.11 3.9.16
# Или укажите путь явно
[testenv:py39]
basepython = /usr/local/bin/python3.9
#### Конфликты зависимостей
Проблема: Несовместимые версии пакетов
Решение:
[testenv]
deps =
pip-tools
commands =
pip-compile requirements.in
pip-sync requirements.txt
pytest
#### Медленная установка зависимостей
Проблема: Долгая установка пакетов при каждом запуске
Решение:
[testenv]
deps =
-c constraints.txt
pytest
install_command = pip install --find-links https://my-cache {opts} {packages}
Оптимизация производительности
Кэширование
[testenv]
# Использование кэша pip
download = true
# Кэширование установленных пакетов
sitepackages = false
# Переиспользование существующего окружения
recreate = false
Оптимизация CI/CD
# GitHub Actions с кэшированием
- name: Cache tox environments
uses: actions/cache@v3
with:
path: .tox
key: tox-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('tox.ini', 'requirements*.txt') }}
restore-keys: |
tox-${{ runner.os }}-${{ matrix.python-version }}-
tox-${{ runner.os }}-
Минимизация времени тестирования
[testenv]
# Параллельный запуск тестов внутри окружения
commands =
pytest -n auto --dist worksteal
# Быстрый выход при первой ошибке
commands =
pytest --maxfail=1 --tb=short
Продвинутые сценарии использования
Тестирование с различными версиями зависимостей
[tox]
envlist = py{38,39,310}-requests{20,25,28}
[testenv]
deps =
requests20: requests>=2.0,<2.1
requests25: requests>=2.5,<2.6
requests28: requests>=2.8,<2.9
pytest
commands = pytest
Создание окружений для разработки
[testenv:dev]
description = Development environment
basepython = python3.11
usedevelop = true
deps =
-e .
pytest
black
flake8
mypy
ipython
jupyter
commands = python -c "print('Development environment ready!')"
Интеграция с coverage
[testenv]
deps =
pytest
pytest-cov
commands =
pytest --cov=src --cov-report=term-missing --cov-report=xml
[testenv:coverage-report]
description = Generate coverage report
deps = coverage[toml]
skip_install = true
commands =
coverage combine
coverage report
coverage html
Сравнение с альтернативными инструментами
| Критерий | tox | nox | hatch | poetry |
|---|---|---|---|---|
| Мульти-Python | ✅ | ✅ | ✅ | ✅ |
| Простота настройки | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| Параллельность | ✅ | ✅ | ✅ | ❌ |
| Конфигурационный файл | INI | Python | TOML | TOML |
| Интеграция с CI | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| Экосистема плагинов | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| Скорость работы | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
Таблица основных методов и функций tox
| Команда/Функция | Описание | Пример использования |
|---|---|---|
tox |
Запуск всех окружений | tox |
tox -e <env> |
Запуск конкретного окружения | tox -e py39 |
tox -r |
Принудительное пересоздание окружений | tox -r |
tox -p auto |
Параллельный запуск | tox -p auto |
tox -v |
Подробный вывод | tox -v |
tox --listenvs |
Список доступных окружений | tox --listenvs |
tox --showconfig |
Показ конфигурации | tox --showconfig |
tox --help |
Справка по командам | tox --help |
tox -- <args> |
Передача аргументов в команды | tox -- --verbose |
tox -c <config> |
Использование альтернативного конфига | tox -c custom.ini |
Основные секции конфигурации
| Секция | Назначение | Основные параметры |
|---|---|---|
[tox] |
Глобальные настройки | envlist, minversion, skip_missing_interpreters |
[testenv] |
Базовое окружение | deps, commands, basepython |
[testenv:<name>] |
Специализированные окружения | description, deps, commands |
[gh-actions] |
Интеграция с GitHub Actions | Маппинг версий Python |
Переменные окружения tox
| Переменная | Описание | Пример значения |
|---|---|---|
{toxinidir} |
Корневая директория проекта | /home/user/project |
{envdir} |
Директория окружения | .tox/py39 |
{envtmpdir} |
Временная директория окружения | .tox/py39/tmp |
{envname} |
Имя окружения | py39 |
{posargs} |
Позиционные аргументы | Аргументы после -- |
Лучшие практики и рекомендации
Структурирование tox.ini
# Рекомендуемая структура конфигурации
[tox]
minversion = 4.0
envlist =
clean,
py{38,39,310,311},
lint,
typecheck,
security,
docs,
report
skip_missing_interpreters = true
isolated_build = true
[testenv]
description = Run tests
deps =
pytest>=6.0
pytest-cov
pytest-xdist
setenv =
COVERAGE_FILE = {env:COVERAGE_FILE:{envtmpdir}/.coverage.{envname}}
commands =
pytest {posargs:tests} --cov --cov-config=pyproject.toml --cov-report=term-missing:skip-covered --cov-report=xml:{envtmpdir}/coverage.xml --cov-report=html:{envtmpdir}/htmlcov -n auto
# Специализированные окружения
[testenv:clean]
description = Clean coverage files
deps = coverage[toml]
skip_install = true
commands = coverage erase
[testenv:report]
description = Generate coverage report
deps = coverage[toml]
skip_install = true
commands =
coverage combine
coverage report
coverage html
Оптимизация для больших проектов
[testenv]
# Использование развернутой установки для быстрой итерации
usedevelop = true
# Кэширование pip для ускорения установки
download = true
# Переиспользование существующих окружений
recreate = false
# Параллельная установка зависимостей
install_command = python -m pip install --upgrade --upgrade-strategy eager {opts} {packages}
Безопасность и изоляция
[testenv]
# Отключение доступа к системным пакетам
sitepackages = false
# Контроль переменных окружения
passenv =
CI
GITHUB_*
HOME
PATH
TERM
# Установка безопасных переменных
setenv =
PYTHONDONTWRITEBYTECODE = 1
PYTHONHASHSEED = 0
PYTHONUNBUFFERED = 1
Часто задаваемые вопросы
#### Что делать, если tox не может найти Python-интерпретатор?
Убедитесь, что нужная версия Python установлена и доступна в PATH. Используйте python3.9 --version для проверки. Если версия установлена в нестандартном месте, укажите путь явно:
[testenv:py39]
basepython = /usr/local/bin/python3.9
#### Как ускорить работу tox при частых запусках?
Используйте следующие оптимизации:
- Включите кэширование:
download = true - Избегайте пересоздания:
recreate = false - Используйте развернутую установку:
usedevelop = true - Запускайте только нужные окружения:
tox -e py39,lint
#### Можно ли использовать tox с Docker?
Да, существует плагин tox-docker для интеграции с Docker:
pip install tox-docker
[testenv:integration]
docker =
postgres:13
redis:alpine
deps = pytest
commands = pytest tests/integration/
#### Как настроить tox для работы с monorepo?
Для monorepo создайте отдельные tox.ini файлы в каждом подпроекте или используйте один общий:
[tox]
envlist = service1, service2, shared
[testenv:service1]
changedir = services/service1
deps = -r{toxinidir}/services/service1/requirements.txt
commands = pytest
[testenv:service2]
changedir = services/service2
deps = -r{toxinidir}/services/service2/requirements.txt
commands = pytest
#### Как интегрировать tox с pre-commit?
Создайте файл .pre-commit-config.yaml:
repos:
- repo: local
hooks:
- id: tox
name: tox
entry: tox
args: ["-e", "lint,typecheck"]
language: system
pass_filenames: false
#### Почему tox создает новые окружения при каждом запуске?
Это может происходить по нескольким причинам:
- Изменились зависимости в
deps - Изменился Python-интерпретатор
- Установлен флаг
recreate = true
Для отладки используйте tox -vv для подробного вывода.
Tox — это мощный инструмент, который значительно упрощает процесс тестирования Python-проектов на разных версиях интерпретатора. Правильная настройка и использование tox помогает обеспечить высокое качество кода, совместимость и надежность ваших проектов. Следуя рекомендациям из этой статьи, вы сможете эффективно использовать все возможности этого инструмента в своих проектах.
Настоящее и будущее развития ИИ: классической математики уже недостаточно
Эксперты предупредили о рисках фейковой благотворительности с помощью ИИ
В России разработали универсального ИИ-агента для роботов и индустриальных процессов