Automatic regression testing in Django
Monday, January 19th, 2009Django and python make it a breeze to build and run automatic regression tests. In 1-2 days you can easily build 50 test cases which cover the basic functionality of your application, and make sure no view or model has been broken. The test cases can be divided into three groups:
- Tests for models
- Tests for views
- Tests for various utility functions
Here’s how to do it. You start by adding a tests.py file in your application directory. The Django default test runner will execute the runTest() method for every TestCase derived class when you run manage.py test. Here’s an example tests.py. Django has specific test functions to log you in and out of the standard authentication framework, but this example is using it’s own authentication functionality.
from django.test import *
import doctest
class ViewTests(TestCase):
def runTest(self):
c = Client()
# Try to get private content prior to login
response = c.get('/projects/')
self.assertRedirects(response, '/login/?next=/projects/')
# Attempt login using the wrong password
response = c.post('/login/',
{'login': 'eki', 'pass': 'wrongpassword'})
self.assertContains(response,
'Please check your user ID and password')
# Use proper credentials
response = c.post('/login/?next=/projects/',
{'login': 'eki', 'pass': 'helloworld'})
self.assertRedirects(response, '/projects/')
# Now fetch each of the basic pages
response = c.get('/projects/')
self.assertContains(response, 'Example project')
response = c.get('/users/')
self.assertContains(response, 'Erkki Tapola')
This is the mechanism for testing views. Next, you can add doctests to your models.py. Doctest is a very handy notation for test cases to be executed from the command line python. Essentially you just run a test case on the command line and cut and paste the command and it’s output to your class or function documentation. For example:
def get_base_dir(path):
"""
>>> get_base_dir('/first/second')
'first'
>>> get_base_dir('first/second')
'second'
"""
items = string.split(path, '/')
return items[1]
The Django test runner runs doctests from models.py automatically, but if you want it to execute doctest cases from other files than models.py as well, you can add a TestCase class to tests.py for that purpose:
class ModuleTests(TestCase):
def runTest(self):
def mytestmod(m):
result = doctest.testmod(m)
print str(result[1]) + ' test cases ran in ' + str(m)
self.assertEqual(result[0], 0)
print "\nRunning test cases for individual modules\n"
print 80*'*'
for x in [tracker.main.util, tracker.main.projects]:
mytestmod(x)
print 80*'*' + "\n"
When you’re finished with your test cases and able to run them all using manage.py test, you can connect your cool new test suite to svn using the pre-commit hook. This way you can make sure that the code changes are verified prior to commit. Here’s what you do on Linux:
- Go to hooks/ which is under your SVN repository directory and rename the start-commit.tmpl to start-commit
- Edit the file, and before the last exit 0, add the line:
-
for x in `find . -iname manage.py -exec dirname {} \;`; do cd $x; if ! ./manage.py test; then exit 1; fi; done
I should probably write this in python to make it platform independent. This unix shell command will find any manage.py from the subdirectories of your current location and use it for running tests. If any manage.py returns an error, the commit will be cancelled. This addition will not influence projects which doesn’t contain a manage.py, but if there is one, then the tests must pass.
A similar hooking mechanism exists in cvs and probably most other version control systems.
You can check out the article on Testing Django
applications for a comprehensive tutorial.