Skip to Content
Course content

305: Testing Across Python Versions with tox

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was maintaining a data processing library used by a handful of different teams in my company. I spent a whole afternoon refactoring a core module to use the | operator for type unions (introduced in Python 3.10), and everything worked perfectly on my machine. I pushed the code, feeling great, only to have the CI pipeline scream at me five minutes later. It turned out one of our legacy services was still running on Python 3.8, and my "clean" update had just introduced a SyntaxError for an entire department. I spent the next hour manually creating three different virtual environments just to verify which versions were actually safe. It was tedious, error-prone, and frankly, a waste of my time.

That is exactly the problem tox solves. Instead of you manually switching versions or relying solely on a remote CI server to tell you that you broke something, tox allows you to run a "test matrix" locally. It automates the process of creating a virtual environment for every Python version you support, installing your package into each one, and running your tests. If it passes on 3.8, 3.9, 3.10, and 3.11, you can push your code with actual confidence.

Defining Your Version Matrix in tox.ini

To get tox working, you don't write a script; you write a configuration file called tox.ini. This file lives in your project root. The most important part of this file is the envlist, which tells tox exactly which versions of Python it needs to spin up.

[tox]
envlist = py38, py39, py310, py311

[testenv]
deps = pytest
commands = pytest

In this snippet, envlist tells tox to create four distinct environments. The [testenv] section is the blueprint for each of those environments. I've specified pytest as a dependency, meaning tox will pip install pytest inside every single one of those virtualenvs before it even thinks about running your tests. Finally, the commands line is where you put the actual test execution. You'll notice I didn't have to specify the Python path—tox handles that internally by executing the command within the context of the active environment.

Orchestrating the Test Loop

Once your tox.ini is set up, you just run the command tox in your terminal. The first time you run it, it'll be a bit slow because it has to build those environments from scratch. But after that, it's incredibly fast.

One thing I've found helpful is the ability to target a specific version if you're debugging a failure. If the py38 environment fails but the others pass, you don't need to run the whole matrix again. You can just run tox -e py38. This saves you from waiting on the versions that you already know are working. I also highly recommend adding .tox/ to your .gitignore file; you definitely don't want to commit those generated virtual environments to your version control.

It is worth noting that tox assumes you actually have those Python versions installed on your system. If you tell tox to run py38 but you only have Python 3.11 installed, it'll throw an error. On macOS or Linux, I usually pair tox with pyenv to manage the actual installation of multiple Python binaries, making the whole workflow seamless.




📋 Practical Task

Cross-Version Validation of the Timezone Utility

You are tasked with verifying a small utility library that uses the zoneinfo module. This module was introduced in Python 3.9, so any environment running Python 3.8 or older should fail during the test phase.

Your goal: Create a project structure that uses tox to prove the code is incompatible with Python 3.8 but compatible with 3.9 and 3.10.

  • Create a file named timezone_utils.py that imports zoneinfo and contains a simple function to get the current time in "America/New_York".
  • Create a test_timezone.py file using pytest that calls this function and asserts the output is a string.
  • Create a tox.ini file that defines a test matrix for py38, py39, and py310.
  • Configure tox.ini to install pytest as a dependency and run pytest as the command.
  • Run tox and verify that the py38 environment fails with an ImportError while the others pass.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.