Wellfire Interactive // Expertise for established Django SaaS applications

Testing Django and Python variations (This Old Pony #61)

For that app that you’re moving to Django 2, or that app you’re finally migrating off of Python 2.7…

Such an upgrade probably isn’t one you want to try in just one fell swoop. Even if the migration is a hard migration, it’d be nice to have some confidence first in how the transition will work. In fact, that’s strategic guidance - don’t make a major language or dependency version change without testing it and smoothing the jump - and further there’s a fantastic tool you’ve probably seen that can help immensely.

That tool is tox[0], and it can help make Django and Python version migrations much less stressful.

Meet tox

Broadly speaking, tox is a testing automation tool. It lets you run a single test suite against a matrix of dependency variations, including Python interpreters and dependency versions.

If you’ve already seen it in action it’s probably in the context of installable third party Python packages. It is, in fact, how I test my own published Python libraries and Django apps. As a tool it creates a unique Python virtual environment for each specified environment (usually a combination of Python interpreter and major dependency versions) and runs your test suite sequentially in each environment. This ensures it will run in each supported environment.

The testing strategy

Now with a third-party Django app, for example, you might want to ensure that it runs on every currently supported version of Django and every version of Python that those versions of Django should run on[1]. That’s a fantastic degree of confidence but if you’re concerned about a single Django project you’re probably not worried about it running on several different Django versions and a set of Python versions for each Django version…

And in most cases you shouldn’t! No, what you do you want to do is close the gap between different versions - Python, Django, etc - so that when you do make the migration itself you need to change as little as possible. So the goal is to get your test suite passing - or as close to passing - in both your current iteration and the next.

Example: if you’re currently running on Django 1.11 with Python 2.7 and wanted to migrate to Python 3.7 and Django 2.1, you could run tests against both environments and try to make changes that pass in both to minimize the changes needed for the big jump itself. Although to be clear, if that’s your migration goal you should find intermediate steps first - a Python version that both Django versions support, then the Django version jump, and then the update to the latest version of Python.

A few tactical tips

Tox is primarily designed for installable packages and as a consequence tox will try to install your package by default. For most Django projects this is undesireable, so ensure that you use the “skip_install”[2] setting in whichever configuration file you use[3].

In most cases you want test combinations of Django versions and Python versions. If you’re using a requirements.txt file, this is an opportunity to break out Django from everything else, including Django in the main requirements.txt and all other dependencies in something like a requirements/base.txt file. This makes the mechanics of modifying the Django version from tox environment to tox environment slightly simpler.

If you’re using a different requirements strategy, including Pipenv[4], my untested suggestion would be to install your base requirements first and then install the environment specific version of Django.

What you’re looking for

Ideally what you’re looking for is a test suite with significant coverage that passes in both your current version combination and your target combination. You’ll have to decide to what degree you ensure test passing parity versus cutting ties with a version, burning the ships, and going forward with the new version.

However if you can get to the point where regardless of Django and Python version your project runs smoothly then you can deploy the upgrade without any other changes to your code base. Anywhere you can minimize the changes deployed is a nice risk reducer.

And if not, then what you want to look for is the smallest number of changes that are absolutely required for the migration so that you’re deploying the smallest change set possible.

Dependently yours,

[0] tox: https://tox.readthedocs.io/en/latest/
[1] the Django/Python matrix: https://docs.djangoproject.com/en/2.1/faq/install/#faq-python-version-support
[2] tox: skip_install https://tox.readthedocs.io/en/latest/config.html#confval-skip_install=BOOL
[3] I’ve found just using tox.ini is the best place
[4] pipenv https://pipenv.readthedocs.io/en/latest/

Learn from more articles like this how to make the most out of your existing Django site.