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.

    im­port os
    im­port sys
    im­port ar­g­parse
    from django.conf im­port set­tings
    
    class Quick­Djan­goTest(ob­ject):
        """
        A quick way to run the Django test suite without a fully-con­figured pro­ject.
    
        Ex­ample us­age:
    
            >>> Quick­Djan­goTest('ap­p1', 'ap­p2')
    
        Based on a script pub­lished by Lukasz Dziedzia at: 
        ht­tp://stack­over­flow.com/ques­tions/3841725/how-to-launch-tests-for-django-re­usable-app
        """
        DIR­NAME = os.path.dir­name(__­file__)
        IN­STALLED_APPS = (
            'django.con­trib.au­th',
            'django.con­trib.con­tent­types',
            'django.con­trib.ses­sions',
            'django.con­trib.ad­min',
        )
    
        def __in­it__(self, *args, **kwargs):
            self.apps = args
            # Get the ver­sion of the test suite
            self.ver­sion = self.get_test_ver­sion()
            # Call the ap­pro­pri­ate one
            if self.ver­sion == 'new':
                self._new_tests()
            else:
                self._old_tests()
    
        def get_test_ver­sion(self):
            """
            Fig­ure out which ver­sion of Django's test suite we have to play with.
            """
            from django im­port VER­SION
            if VER­SION[0] == 1 and VER­SION[1] >= 2:
                re­turn 'new'
            else:
                re­turn 'old'
    
        def _old_tests(self):
            """
            Fire up the Django test suite from be­fore ver­sion 1.2
            """
            set­tings.con­fig­ure(DE­BUG = True,
               DATA­BASE_EN­GINE = 'sql­ite3',
               DATA­BASE_­NAME = os.path.join(self.DIR­NAME, 'data­base.db'),
               IN­STALLED_APPS = self.IN­STALLED_APPS + self.apps
            )
            from django.test.simple im­port run­_tests
            fail­ures = run­_tests(self.apps, verb­os­ity=1)
            if fail­ures:
                sys.exit(fail­ures)
    
        def _new_tests(self):
            """
            Fire up the Django test suite de­veloped for ver­sion 1.2
            """
            set­tings.con­fig­ure(
                DE­BUG = True,
                DATA­BASES = {
                    'de­fault': {
                        'EN­GINE': 'django.db.backends.sql­ite3',
                        'NAME': os.path.join(self.DIR­NAME, 'data­base.db'),
                        'USER': '',
                        'PASS­WORD': '',
                        'HOST': '',
                        'PORT': '',
                    }
                },
                IN­STALLED_APPS = self.IN­STALLED_APPS + self.apps
            )
            from django.test.simple im­port Djan­goTest­Suit­eRun­ner
            fail­ures = Djan­goTest­Suit­eRun­ner().run­_tests(self.apps, verb­os­ity=1)
            if fail­ures:
                sys.exit(fail­ures)
    
    if __­name__ == '__­main__':
        """
        What do when the user hits this file from the shell.
    
        Ex­ample us­age:
    
            $ py­thon quicktest.py ap­p1 ap­p2
    
        """
        pars­er = ar­g­parse.Ar­gu­ment­Pars­er(
            us­age="[args]",
            de­scrip­tion="Run Django tests on the provided ap­plic­a­tions."
        )
        pars­er.ad­d_ar­gu­ment('apps', nargs='+', type=str)
        args = pars­er.parse_args()
        Quick­Djan­goTest(*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.

    # Sub­sti­tute 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 Trav­is you want a Py­thon en­vir­on­ment to test in
    lan­guage: py­thon
    # List the ver­sions of Py­thon you'd like to test against
    py­thon:
      - "2.5"
      - "2.6"
      - "2.7"
    # List the ver­sions of Django you'd like to test against
    env:
      - DJAN­GO_VER­SION=1.0.4
      - DJAN­GO_VER­SION=1.1.4
      - DJAN­GO_VER­SION=1.2.7
      - DJAN­GO_VER­SION=1.3.1
      - DJAN­GO_VER­SION=1.4
    # Tell it the things it will need to in­stall when it boots
    in­stall: 
       # This is a de­pend­ency of our Django test script
     - pip in­stall ar­g­parse --use-mir­rors
       # In­stall the de­pend­en­cies the app it­self has
       # which in this case I choose to keep in a re­quire­ments file
     - pip in­stall -r re­quire­ments.txt --use-mir­rors
       # In­stall whatever ver­sion of Django that's lis­ted above
       # Trav­is is cur­rently work­ing on
     - pip in­stall -q Django==$DJAN­GO_VER­SION --use-mir­rors
    # Tell Trav­is how to run the test script it­self
    script: py­thon quicktest.py softhy­phen
    

    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.

    # Com­mit­ting the code I've ad­ded
    $ git add .
    $ git commit -am "Set up tests to work with Trav­is 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.