Replacing tox with uv and Poe the Poet¶
This guide demonstrates how Poe the Poet and uv can replace tox for running tests across multiple python versions and environments.
Why replace tox?¶
While tox is a powerful tool for test automation, using Poe the Poet with uv offers comparable expressiveness for many use cases, and several compelling advantages:
Faster execution: uv is significantly faster than traditional virtualenv tools
Unified configuration: everything in
pyproject.tomlBetter integration: works seamlessly with your existing uv workflow
More flexible: leverage all of Poe the Poet’s task composition features
Simpler syntax: more intuitive task oriented configuration
Prerequisites¶
Install both uv and Poe the Poet:
# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install Poe the Poet
uv tool install poethepoet
See other installation methods in the uv installation guide and Poe the Poet installation guide.
Basic migration¶
A typical tox.ini file might look like this:
[tox]
envlist = py310,py311,py312,py313
[testenv]
deps = pytest
pytest-cov
commands = pytest tests --cov=mypackage
Here’s the equivalent configuration using Poe the Poet with the uv executor:
[tool.poe.executor]
type = "uv" # Use the uv executor for all tasks by default
[tool.poe.tasks.test-py39]
cmd = "pytest tests --cov=mypackage"
executor = {isolated = true, python = "3.9"} # Specify python version, and that we want an isolated env
[tool.poe.tasks.test-py310]
cmd = "pytest tests --cov=mypackage"
executor = {isolated = true, python = "3.10"}
[tool.poe.tasks.test-py311]
cmd = "pytest tests --cov=mypackage"
executor = {isolated = true, python = "3.11"}
[tool.poe.tasks.test-py312]
cmd = "pytest tests --cov=mypackage"
executor = {isolated = true, python = "3.12"}
[tool.poe.tasks.test-all]
sequence = ["test-py39", "test-py310", "test-py311", "test-py312"]
Run all tests with:
poe test-all
Or run tests for a specific version:
poe test-py311
Tip
See the uv executor documentation for a full explanation of these and other available executor options.
Advanced configurations¶
Testing with different dependency sets¶
If you need to test against different versions of dependencies (like tox’s factors):
[tool.poe.tasks.test-django32]
cmd = "pytest tests"
executor = { isolated = true, python = "3.11", group = "dev", with = "django==3.2.*" }
[tool.poe.tasks.test-django42]
cmd = "pytest tests"
executor = { isolated = true, python = "3.11", group = "dev", with = "django==4.2.*" }
Testing with optional dependencies¶
Pick and choose optional or dev dependency groups defined in your pyproject.toml:
[tool.poe.tasks.test-with-extras]
cmd = "pytest tests"
executor = { extra = "all", group = ["ci", "debug"], no-group = "docs" }
Compose testing with other tasks¶
Use sequence tasks to run commands before and after tests:
[tool.poe.tasks.lint]
cmd = "ruff check ."
[tool.poe.tasks.test-py311]
cmd = "pytest tests --cov=mypackage"
executor = { isolated = true, python = "3.11" }
[tool.poe.tasks.coverage-report]
cmd = "coverage report"
[tool.poe.tasks.ci]
sequence = ["lint", "test-py311", "coverage-report"
Or define a DAG of tasks with task deps:
[tool.poe.tasks.lint]
cmd = "ruff check ."
[tool.poe.tasks.test-py311]
cmd = "pytest tests --cov=mypackage"
executor = { isolated = true, python = "3.11" }
deps = ["lint"]
See the guide on task composition for more examples.
Parallel execution¶
tox supports running tests in parallel using the -p option. Poe the Poet lets you define a parallel task that runs all your test variants concurrently:
[tool.poe.tasks.test-parallel]
parallel = ["test-py39", "test-py310", "test-py311", "test-py312"]
Environment variables¶
tox allows setting environment variables for test runs using the setenv option. You can achieve the same with Poe the Poet using the env or envfile options.
Set environment variables directly:
[tool.poe.tasks.test-integration]
cmd = "pytest tests/integration"
executor = { isolated = true, python = "3.11" }
env = { DATABASE_URL = "postgresql://localhost/test_db" }
Or load an envfile for the task:
[tool.poe.tasks.test-integration]
cmd = "pytest tests/integration"
executor = { isolated = true, python = "3.11" }
envfile = ".env.test"