Test your Django app with Travis CI
Make sure your code works with different versions of Python, Django and whatever else
Writing tests is the right thing to do. It strengthens your code. It saves you time in the long run. It streamlines collaboration. It protects you from yourself. Yet, like most things that are good for you, it is a pain.
That’s why Travis CI has me excited. It’s a free and simple tool for continuous integration, which is a fancy way of saying “a system to make sure your code will work on other people’s computers.”
Running verification tests to make sure code updates work is great. But if you run the tests on your laptop you probably have no idea if the changes will work on my computer, especially if I have different versions of any underlying software your code depends on.
Continuous integration software like Travis CI helps solve that problem. It can spin up a fleet of machines, each with a slightly different configuration, and check each one to see if your code crashes.
If you don’t use Django, getting started is easy. Especially if you use GitHub to host your repository. Every time you push a new commit to GitHub, it pulls in the code and runs tests across whatever mix of machines you ask for. Go follow the great documentation that already exists.
If you do use Django, it’s still pretty easy. But there are a couple tricks you have to get down.
Testing pluggable apps
One of great things about Django is its modular design. If you start a new Django project, there is already a world of premade features you can plug in and run with. A great example is the Pinax project, which offers dozens of reusable apps that, taken together, offer most of the features necessary to build a social network.
The Data Desk has released a number of pluggable Django apps, each tackling a simple and specific feature. One example is django-softhyphen, which automates the hyphenation of text in HTML. It’s used to hyphenate the text you’re reading right now.
Testing django-softhyphen requires Django be installed and configured. And Django’s configuration can vary depending on what version you’re using, which is where the trouble comes in. So if you want to write a test that Travis CI can pick up and run against every version of Django back to version 1.0, the tests will need to be smart enough to figure it out.
Below is my hacky attempt to get something working. If you have a pluggable app, with a tests.py dutifully written inside, copy this script to the directory above your app and name it quicktest.py
.
import os import sys import argparse from django.conf import settings class QuickDjangoTest(object): """ A quick way to run the Django test suite without a fully-configured project. Example usage: >>> QuickDjangoTest('app1', 'app2') Based on a script published by Lukasz Dziedzia at: http://stackoverflow.com/questions/3841725/how-to-launch-tests-for-django-reusable-app """ DIRNAME = os.path.dirname(__file__) INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.admin', ) def __init__(self, *args, **kwargs): self.apps = args # Get the version of the test suite self.version = self.get_test_version() # Call the appropriate one if self.version == 'new': self._new_tests() else: self._old_tests() def get_test_version(self): """ Figure out which version of Django's test suite we have to play with. """ from django import VERSION if VERSION[0] == 1 and VERSION[1] >= 2: return 'new' else: return 'old' def _old_tests(self): """ Fire up the Django test suite from before version 1.2 """ settings.configure(DEBUG = True, DATABASE_ENGINE = 'sqlite3', DATABASE_NAME = os.path.join(self.DIRNAME, 'database.db'), INSTALLED_APPS = self.INSTALLED_APPS + self.apps ) from django.test.simple import run_tests failures = run_tests(self.apps, verbosity=1) if failures: sys.exit(failures) def _new_tests(self): """ Fire up the Django test suite developed for version 1.2 """ settings.configure( DEBUG = True, DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(self.DIRNAME, 'database.db'), 'USER': '', 'PASSWORD': '', 'HOST': '', 'PORT': '', } }, INSTALLED_APPS = self.INSTALLED_APPS + self.apps ) from django.test.simple import DjangoTestSuiteRunner failures = DjangoTestSuiteRunner().run_tests(self.apps, verbosity=1) if failures: sys.exit(failures) if __name__ == '__main__': """ What do when the user hits this file from the shell. Example usage: $ python quicktest.py app1 app2 """ parser = argparse.ArgumentParser( usage="[args]", description="Run Django tests on the provided applications." ) parser.add_argument('apps', nargs='+', type=str) args = parser.parse_args() QuickDjangoTest(*args.apps)
Now running a single command should be able to bootstrap Django and run the tests inside your app without having to create a Django project, fill out a settings.py
file or any other preparation.
# Substitute your app name at the end if you try this at home $ python quicktest.py softhyphen
Talking to Travis
Not only is this handy for running tests locally, but it’s a script that Travis CI can pick up and use to test your app across different versions of Django, Python and any other library you’d like.
Before you can use Travis CI, you have to go there and link your GitHub account and the repository you’re working on. It must be a public, open-source repository.
Once that’s done, you need to create a .travis.yml
file in the directory above your app next to quicktest.py
, in the same place you might normally put setup.py
or a README
file. There you need to instruct Travis CI on what type of tests to run. Below is the configuration for django-softhyphen, which tests the app against five different versions of Django with three different versions of Python.
# Tell Travis you want a Python environment to test in language: python # List the versions of Python you'd like to test against python: - "2.5" - "2.6" - "2.7" # List the versions of Django you'd like to test against env: - DJANGO_VERSION=1.0.4 - DJANGO_VERSION=1.1.4 - DJANGO_VERSION=1.2.7 - DJANGO_VERSION=1.3.1 - DJANGO_VERSION=1.4 # Tell it the things it will need to install when it boots install: # This is a dependency of our Django test script - pip install argparse --use-mirrors # Install the dependencies the app itself has # which in this case I choose to keep in a requirements file - pip install -r requirements.txt --use-mirrors # Install whatever version of Django that's listed above # Travis is currently working on - pip install -q Django==$DJANGO_VERSION --use-mirrors # Tell Travis how to run the test script itself script: python quicktest.py softhyphen
Now all that’s left to do is commit your changes and push the code to GitHub. Within a matter of minutes you should recieve an email telling you how the tests went, and you’ll be able to closely examine the results in each environment on the Travis CI site.
# Committing the code I've added $ git add . $ git commit -am "Set up tests to work with Travis CL" $ git push origin master
Much respect due
The code above is an expansion of an idea posted to StackOverflow by Lukasz Dziedzia. He should get any credit, and any errors are almost certainly my fault. If you see anything that could be improved, please let me know in the comments below.