Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
305: Testing Across Python Versions with tox
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.pythat importszoneinfoand contains a simple function to get the current time in "America/New_York". - Create a
test_timezone.pyfile usingpytestthat calls this function and asserts the output is a string. - Create a
tox.inifile that defines a test matrix forpy38,py39, andpy310. - Configure
tox.inito installpytestas a dependency and runpytestas the command. - Run
toxand verify that thepy38environment fails with anImportErrorwhile the others pass.
There are no comments for now.