Test your Django app with Travis CI

    Make sure your code works with different versions of Python, Django and whatever else

    Los Angeles Times photographic archive, UCLA Library
    1965: William Gunnell, left, a senior at the University of California's Berkeley campus, learns how to monitor computer ouput from Art Nelson, manager of Security First National Bank in Los Angeles.

    Writing tests is the right thing to do. It strengthens your code. It saves you time in the long run. It stream­lines col­lab­or­a­tion. It pro­tects you from your­self. Yet, like most things that are good for you, it is a pain.

    That’s why Trav­is CI has me ex­cited. It’s a free and simple tool for con­tinu­ous in­teg­ra­tion, which is a fancy way of say­ing “a sys­tem to make sure your code will work on oth­er people’s com­puters.”

    Run­ning veri­fic­a­tion tests to make sure code up­dates work is great. But if you run the tests on your laptop you prob­ably have no idea if the changes will work on my com­puter, es­pe­cially if I have dif­fer­ent ver­sions of any un­der­ly­ing soft­ware your code de­pends on.

    Con­tinu­ous in­teg­ra­tion soft­ware like Trav­is CI helps solve that prob­lem. It can spin up a fleet of ma­chines, each with a slightly dif­fer­ent con­fig­ur­a­tion, and check each one to see if your code crashes.

    If you don’t use Django, get­ting star­ted is easy. Es­pe­cially if you use Git­Hub to host your re­pos­it­ory. Every time you push a new com­mit to Git­Hub, it pulls in the code and runs tests across whatever mix of ma­chines you ask for. Go fol­low the great doc­u­ment­a­tion that already ex­ists.

    If you do use Django, it’s still pretty easy. But there are a couple tricks you have to get down.

    Test­ing plug­gable apps

    One of great things about Django is its mod­u­lar design. If you start a new Django pro­ject, there is already a world of premade fea­tures you can plug in and run with. A great ex­ample is the Pin­ax pro­ject, which of­fers dozens of re­usable apps that, taken to­geth­er, of­fer most of the fea­tures ne­ces­sary to build a so­cial net­work.

    The Data Desk has re­leased a num­ber of plug­gable Django apps, each tack­ling a simple and spe­cif­ic fea­ture. One ex­ample is django-softhy­phen, which auto­mates the hy­phen­a­tion of text in HTML. It’s used to hy­phen­ate the text you’re read­ing right now.

    Test­ing django-softhy­phen re­quires Django be in­stalled and con­figured. And Django’s con­fig­ur­a­tion can vary de­pend­ing on what ver­sion you’re us­ing, which is where the trouble comes in. So if you want to write a test that Trav­is CI can pick up and run against every ver­sion of Django back to ver­sion 1.0, the tests will need to be smart enough to fig­ure it out.

    Be­low is my hacky at­tempt to get something work­ing. If you have a plug­gable app, with a tests.py du­ti­fully writ­ten in­side, copy this script to the dir­ect­ory 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 run­ning a single com­mand should be able to boot­strap Django and run the tests in­side your app without hav­ing to cre­ate a Django pro­ject, fill out a settings.py file or any oth­er pre­par­a­tion.

    # Substitute your app name at the end if you try this at home
    $ python quicktest.py softhyphen
    

    Talk­ing to Trav­is

    Not only is this handy for run­ning tests loc­ally, but it’s a script that Trav­is CI can pick up and use to test your app across dif­fer­ent ver­sions of Django, Py­thon and any oth­er lib­rary you’d like.

    Be­fore you can use Trav­is CI, you have to go there and link your Git­Hub ac­count and the re­pos­it­ory you’re work­ing on. It must be a pub­lic, open-source re­pos­it­ory.

    Once that’s done, you need to cre­ate a .travis.yml file in the dir­ect­ory above your app next to quicktest.py, in the same place you might nor­mally put setup.py or a README file. There you need to in­struct Trav­is CI on what type of tests to run. Be­low is the con­fig­ur­a­tion for django-softhy­phen, which tests the app against five dif­fer­ent ver­sions of Django with three dif­fer­ent ver­sions of Py­thon.

    # 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 com­mit your changes and push the code to Git­Hub. With­in a mat­ter of minutes you should re­cieve an email telling you how the tests went, and you’ll be able to closely ex­am­ine the res­ults in each en­vir­on­ment on the Trav­is 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 re­spect due

    The code above is an ex­pan­sion of an idea pos­ted to Stack­Over­flow by Lukasz Dziedzia. He should get any cred­it, and any er­rors are al­most cer­tainly my fault. If you see any­thing that could be im­proved, please let me know in the com­ments be­low.

    Readers: What’s your take? Share it here.

    Advertisement

    Latest work

      About The Data Desk

      This page was created by the Data Desk, a team of reporters and Web developers in downtown L.A.