This tutorial walks you through creating your first tox project from scratch. By the end, you will have a working tox configuration that runs tests and linting across multiple Python versions.
Before starting, make sure you have:
- Python 3.10 or later installed
- tox installed (see :doc:`../how-to/install`)
- A Python project you want to test (or follow along to create one)
Verify tox is available:
tox --versiontox needs a configuration file where you define what tools to run and how to set up environments for them. tox supports two configuration formats: TOML and INI. Use TOML -- it is more robust, has proper type support, and avoids ambiguities inherent in INI parsing. The INI format is deprecated: it keeps working for existing projects, but it is frozen and receives no new features.
Create a tox.toml at the root of your project:
.. tab:: TOML
.. code-block:: toml
env_list = ["3.13", "3.12", "lint"]
[env_run_base]
description = "run the test suite with pytest"
deps = [
"pytest>=8",
]
commands = [["pytest", { replace = "posargs", default = ["tests"], extend = true }]]
[env.lint]
description = "run linters"
skip_install = true
deps = ["ruff"]
commands = [["ruff", "check", { replace = "posargs", default = ["."], extend = true }]]
.. tab:: INI (deprecated)
.. code-block:: ini
[tox]
env_list = 3.13, 3.12, lint
[testenv]
description = run the test suite with pytest
deps =
pytest>=8
commands =
pytest {posargs:tests}
[testenv:lint]
description = run linters
skip_install = true
deps =
ruff
commands = ruff check {posargs:.}
The configuration has two parts: core settings and environment settings.
Core settings affect all environments or configure how tox itself behaves. They live at the root level in tox.toml
(or under the [tox] section in tox.ini).
.. tab:: TOML
.. code-block:: toml
env_list = ["3.13", "3.12", "lint"]
.. tab:: INI (deprecated)
.. code-block:: ini
[tox]
env_list = 3.13, 3.12, lint
The :ref:`env_list` setting defines which environments run by default when you invoke tox without specifying any.
Both formats support generating environment matrices from factor combinations — INI uses curly-brace expansion
(3.{10-}), while TOML composes from structured dicts. A single axis is a bare range dict ({ prefix = "3.", start =
10 }); multi-dimensional matrices use a product dict. See :ref:`generative-environment-list` for details. For the
full list of core options, see :ref:`conf-core`.
Each tox environment has its own configuration. Settings defined at the base level (env_run_base in TOML,
testenv in INI) are inherited by all environments unless overridden. Individual environments are configured under
env.<name> in TOML or testenv:<name> in INI.
.. tab:: TOML
.. code-block:: toml
[env_run_base]
description = "run the test suite with pytest"
deps = ["pytest>=8"]
commands = [["pytest", { replace = "posargs", default = ["tests"], extend = true }]]
[env.lint]
description = "run linters"
skip_install = true
deps = ["ruff"]
commands = [["ruff", "check", "."]]
.. tab:: INI (deprecated)
.. code-block:: ini
[testenv]
description = run the test suite with pytest
deps =
pytest>=8
commands =
pytest {posargs:tests}
[testenv:lint]
description = run linters
skip_install = true
deps =
ruff
commands = ruff check .
Here the lint environment overrides the base settings entirely, while 3.13 and 3.12 inherit from the base.
Tip
Options must go in the correct section — placing a core option in an environment section (or vice versa) silently
has no effect. Run tox run -v or tox config to check for misplaced keys.
Environment names can consist of alphanumeric characters, dashes, and dots. Names are split on dashes into factors
-- for example py311-django42 splits into factors py311 and django42. Additionally, the current platform
(like linux, darwin, win32) is automatically available as an implicit factor for conditional configuration.
tox recognizes certain naming patterns and automatically sets the Python interpreter:
N.M: CPython N.M (e.g.3.13) -- preferredpyNMorpyN.M: CPython N.M (e.g.py313orpy3.13) -- legacy, still supportedpypyNM: PyPy N.McpythonNM: CPython N.MgraalpyNM: GraalPy N.M
Prefer the N.M form (e.g. 3.14) over pyNMM (e.g. py314). The dotted form is unambiguous, reads more
naturally in environment lists and CI output, and avoids confusion for Python versions >= 3.10 where the concatenated
digits become three characters.
Note
Version-number factors like 3.14 can be checked in conditional expressions using subscript syntax:
factor['3.14'] (the dot-syntax factor.3.14 would be a syntax error). See :ref:`conditional-value-reference`
for details.
If the name doesn't match any pattern, tox uses the same Python as the one tox is installed into (this is the case for
lint in our example). To override this fallback, set :ref:`default_base_python`:
[env_run_base]
default_base_python = ["3.14", "3.13"]This pins a default Python version for environments without a Python factor, improving reproducibility across machines with different system Pythons.
Tip
If your project uses PEP 751 lock files (pylock.toml), you can install locked dependencies via :ref:`pylock`
instead of listing packages in deps.
For the full list of environment options, see :ref:`conf-testenv`.
When your test matrix grows beyond a few environments, use env_base sections to define named templates that generate
environments from factor combinations:
[env_base.test]
factors = [["3.13", "3.14"]]
deps = ["pytest>=8"]
commands = [["pytest"]]This generates test-3.13 and test-3.14, each inheriting deps and commands from the template. The template itself
inherits from env_run_base, so global defaults still apply.
Ranges save spelling out every version, and nesting one under a name lets the rest of the section refer back to whichever value the environment picked:
[env_base.test]
factors = [{ py_version = { prefix = "3.", start = 13, stop = 14 } }]
deps = ["pytest>=8"]
commands = [["pytest"]]
description = "run the tests under Python {factor:py_version}"See :ref:`env-base-templates` for details.
Run all default environments (those listed in :ref:`env_list`):
toxRun a specific environment:
tox run -e lintRun multiple environments:
tox run -e 3.13,lintPass extra arguments to the underlying tool using --:
# Run pytest in verbose mode
tox run -e 3.13 -- -v
# Run ruff on a specific file
tox run -e lint -- src/mymodule.pyThe { replace = "posargs" } in TOML (or {posargs} in INI) is a placeholder that gets replaced by whatever you
pass after --.
On the first run, tox creates virtual environments and installs dependencies. The environments land in a .tox
directory next to your configuration file; add it to your version control ignore file, or relocate it entirely as shown
in :ref:`howto_out_of_tree_envs`. Subsequent runs reuse existing environments unless dependencies change:
$ tox run -e 3.13,lint
3.13: install_deps> python -m pip install 'pytest>=8'
3.13: commands[0]> pytest tests
========================= 3 passed in 0.12s =========================
3.13: OK ✔ in 5.43s
lint: install_deps> python -m pip install ruff
lint: commands[0]> ruff check .
All checks passed!
lint: OK ✔ in 2.11s
3.13: OK (5.43=setup[3.21]+cmd[2.22] seconds)
lint: OK (2.11=setup[1.05]+cmd[1.06] seconds)
congratulations :)tox will automatically detect changes to your dependencies and recreate environments when needed. You can force a full
recreation with the -r flag:
tox run -e 3.13 -rIf tools inside the environment maintain their own caches (e.g. pre-commit), you can use :ref:`recreate_commands` to clean them before the environment directory is removed. See :ref:`howto_clean_caches` for details.
If you want to rerun tests without reinstalling dependencies or the package (e.g. when working offline or when nothing
has changed), use --skip-env-install:
tox run -e 3.13 --skip-env-installSee all configured environments and their descriptions:
$ tox list
default environments:
3.13 -> run the test suite with pytest
3.12 -> run the test suite with pytest
lint -> run lintersView the resolved configuration for an environment:
tox config -e 3.13 -k deps commandsThe output format can be changed with --format and written to a file with -o:
tox config -e 3.13 --format json -o config.json
tox config -e 3.13 --format toml -o config.tomlThis is useful for debugging configuration issues and for programmatic consumption.
Now that you have a working tox setup, explore these topics:
- :doc:`../explanation` -- understand how tox works (parallel mode, packaging, auto-provisioning)
- :doc:`../how-to/usage` -- practical recipes for common tasks (including testing across old and new Python versions with :ref:`virtualenv_spec`)
- :ref:`configuration` -- full configuration reference
- :ref:`cli` -- complete CLI reference
- :ref:`howto_pep723` -- run standalone scripts with inline dependencies (PEP 723)