Erik Derohanian
Using Tox to Unit Test Python AWS Lambdas
Contents
Overview
If you are writing a backend using AWS lambdas and have multiple lambdas, you may run into the issue of: “how do I run all my unit tests, but in environments isolated per lambda?”. Tox
https://tox.wiki
was originally designed to run tests for a packages in multiple environments (“I want to test my library against every version of Python between 3.6 and 3.10”), but we can abuse it to instead create isolated environments for each of our lambdas, and run tests with only the dependencies specified per package. We will use pytest
https://docs.pytest.org/
and also add coverage reporting.
Code Layout
Our code layout is as follows:
./
└── src/
├── api/
│ ├── api1/
│ │ ├── stuff...
│ │ ├── requirements.txt
│ │ └── tests/
│ ├── api2/
│ │ ├── stuff...
│ │ ├── requirements.txt
│ │ └── tests/
│ └── api3/
│ ├── stuff...
│ ├── requirements.txt
│ └── tests/
└── libs/
├── stuff...
├── requirements.txt
└── tests/
The api<N>/
folders are the lambdas and a shared resource layer lives in libs/
. We want to create a new virtual env for each of these folders and test them in isolation, since that’s how they’ll be running in production.
tox.ini
To begin, create a tox.ini
file in the root of your project. Add the following, updating path names with your folder names:
|
|
Running it all
Install tox
pip install tox
. Everything should be set up now and fully configured, all you need to do is run:
$ tox
The first run will be slow as it needs to create all the environments, but subsequent runs will reuse them and be much faster.
If you want to force recreation of all the virtual environments, you can:
$ tox -r
What’s Happening
The [tox]
Block
We’re setting a minimum version of tox and asking it not to create a source dist of our package, we don’t need one. We’re also creating a list of environments:
- clean, This will clean out old coverage reports
- src/libs, This is our shared resource layer folder that we want to test
- src/api/{api1,api2,api3}, These are the 3 api lambdas, we want to test all of them
- report, This is an additional environment that will turn the pytest coverage into both an XML report for your IDE as well as an HTML report for human consumption
[base]
This block is just setting up a way to import requirements.txt
files or any other set up we want shared between all the test environments (right now it’s just set up to give us a requirements file). It’s now clear here why we named our environments after the folder we want to test - the “environment name” is going to be injected into a bunch of commands to only test parts of the repo.
[testenv]
This is the meat of the testing logic. We’re defining a directory to run, python version to use, and allowing tox to run sh
commands (You need to tell it to allow running commands outside the virtual environment otherwise it’ll let you know that you’re reaching outside of the isolated sandbox it’s trying to maintain for you). We then set up our testing dependencies. Line 20 has all the pytest
and coverage
-related dependencies we want for our testing, as well as pulling in the requirements.txt
file specified in the base
block. This will run per environment.
We also set the PYTHONPATH
env var, and COVERAGE_FILE
so that all the tests write append coverage to the same file. We set how we want our files installed, as well as our test command. The --cov-append
flag is critical, otherwise it will overwrite previous coverage
[testenv:clean]
This block is subclassing the [testenv]
block and overwriting some settings with clean
-step specific configuration. The only package we need here is coverage
, and we don’t need the clean
package installed (because it doesn’t exist) so we add skip_install = true
.
This environment clears out existing coverage and deletes the htmlcov
folder of html coverage reports, if it exists. This is the reason we had to allow sh
to run earlier.
[testenv:report]
This is set up similar to testenv:clean
. It will:
- Print off the coverage, not counting empty or test files towards your code coverage
- Generate an HTML report in the
htmlcov
folder - Generate an XML report in
coverage.xml
- Delete the empty
report/
andclean/
folders that tox will generate, I haven’t figured out how to stop this.
Additional Resources
For information on making the test environments run in parallel, see these pytest-cov docs.
To integrate the coverage report into VSCode, we’ve been using Coverage Gutters