tox – тестирование на разных версиях Python

онлайн тренажер по питону
Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение

Когда вы разрабатываете библиотеки или приложения на 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 состоит из следующих этапов:

  1. Анализ конфигурации — чтение файла tox.ini и определение списка окружений
  2. Создание виртуальных окружений — для каждого указанного Python-интерпретатора
  3. Установка пакета — установка тестируемого пакета в каждое окружение
  4. Установка зависимостей — установка зависимостей, указанных в секции deps
  5. Выполнение команд — запуск команд, определенных в секции commands
  6. Сбор результатов — агрегация результатов тестирования

Подробная конфигурация 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 помогает обеспечить высокое качество кода, совместимость и надежность ваших проектов. Следуя рекомендациям из этой статьи, вы сможете эффективно использовать все возможности этого инструмента в своих проектах.

Новости