Django Testing

Avatar for vbabiy@howsthe.com

Django Testing

Published Jan. 4, 2010 by Vitaly Babiy

Test driven development will help in a few aspects when developing an application. One aspect is you get to eat your own dog food even before you make it. You are able to use your API's before they are implemented, this will display any short comings in the design. Also by forcing yourself to think through a problem and get a better understanding, which in turn helps make better design decisions. We all make mistakes, without testing our code base there is no way to be sure that everything is still working, otherwise you are crossing your fingers and knocking on wood.

Django and Python Test Tools

If you've been using Django for some time you have heard the phrase "Django is just Python" and this holds true in the realm of testing your Django Application. You can use other testing frameworks with Django like nose. Django comes with a good testing framework that is built off the Python unittest module. The default Django testcase is a subclass of unittest.TestCase this class provides with some additional assertions and also a client that will helps you test your views ( more details to come ).

Environment setup and Running Tests.

If you are using Django 1.1 or newer when creating an application by running ./manage.py startapp app_name Django will create a test.py file. To run the the test suite for you application you need to make sure it is included in the INSTALLED_APPS in your setting.py. By calling python manage.py test will run all your tests, if you want to run your tests for a single application run python manage.py app_name ( refer to the docs for more information on how to limit even further http://docs.djangoproject.com/en/1.1/topics/testing/#id1 ). Having a single module to store your test is great, if you are building a simple application. I find that when an application has a few views, models, managers, forms, or template tags it is hard to maintain all your tests in one module. Since the Django test runner is looking for a tests module, we can create a tests package and import all tests into the __init__.py of the package so the test runner can find the tests.

This is your standard layout:

|-- __init__.py
|-- manage.py
|-- settings.py
|-- testapp
|   |-- __init__.py
|   |-- models.py
|   |-- tests.py
|   `-- views.py
`-- urls.py

You will notice there is a single tests.py file, it will be replaced with a tests package as follows:

|-- __init__.py
|-- manage.py
|-- settings.py
|-- testapp
|   |-- __init__.py
|   |-- models.py
|   |-- tests 
|   |   |-- __init__.py
|   |   |-- forms.py
|   |   |-- managers.py
|   |   |-- models.py
|   |   |-- template_tags.py
|   |   `-- views.py
|   `-- views.

We created a tests package and we split out the tests. If you try to run the test again Django's test runner won't be albe to locate your tests yet. We still need to add a few imports in to our __init__.py.

# tests/__init__.py
from forms import *
from managers import *
from models import *
from template_tags import *
from views import *

Once completed Django should be able to locate your test from each module, one gotcha is if you have two test cases that share a name, which ever one is last imported will be run since in the __init__.py we are importing all test cases in the tests package name space.

Unit Tests, Functional Tests, Integration Tests.

If you have done any kind of testing or even researched testing, I am sure you have heard of these terms. Here we will only talk about unit and functional tests, Django allows you to write both pretty easily. We will start off with some functional test to test our views, the reason I classified these test under functional test is they are not just testing one section of the code like a unit test, they are testing your view is correct and the routing of your urls is also correct.

from django.test import TestCase

class HelloViewTestCase(TestCase):
    urls = 'test_app.urls'

    def setUp(self):
        self.res = self.client.get('/hello/')

    def test_display_hello(self):
        self.assertEquals(self.res.status_code, 200)
        self.assertTemplateUsed(self.res, 'test_app/hello.html')

Since this is our first code snippet some explaining is due, first we import the Django TestCase and we subclass it to create our own test case. We specify the root url to be used by this test case, this is done so we don't have to know the entire url for the view in order to test it.

Then we have a setUp function which will run before every test, there is also a tearDown which will be run after every test, we don't have a need for it here. In the setUp we are using the Django client to call the view and storing the result in self.res instance variable so other tests can use it.

test_display_hello test to make sure that we got a HTTP status code of 200 which means we didn't get any errors render the views, we also make sure that the correct template is used for rendering.

In this example we are only using a couple assertions, Django/Python provides you with many more, follow these links for a complete list:
Django Specific: http://docs.djangoproject.com/en/dev/topics/testing/#assertions
Python Unit test: http://docs.python.org/library/unittest.html#testcase-objects

Functional tests like the one above will get you a long way, they verify that your views are being called and rendering without error. Once you get into more complicated applications you will start writing both unit and functional tests for your views. For instance if you have helper methods in your views those can be tested with unit tests, or if you have class based views these can also be test with unit tests.

Models and managers are prefect place to write unit tests, we can test thes function in a isolated environment. Here is a example of a model unit test.

# models.py
from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=40)
    last_name = models.CharField(max_length=40)
    age = models.SmallIntegerField()

    def get_age_in_seconds(self):
        return self.age * 365 * 86400

    def get_full_name(self):
        return "%s %s" % ( self.first_name, self.last_name)

# tests/models.py
from django.test import TestCase
from test_app.models import Person

class PersonModelTestCase(TestCase):

    def setUp(self):
        self.model = Person()
        self.model.first_name = "John"
        self.model.last_name = "Doe"
        self.model.age = 22

    def test_age_in_seconds(self):
        self.assertEquals(self.model.get_age_in_seconds(), 693792000)

    def test_full_name(self):
        self.assertEquals(self.model.get_full_name(), "John Doe")

This is a simple example to show you how to setup a model unit test, but this can be scaled into larger models and more complex. In models.py we define a person model with first_name, last_name, and age, we have two functions that calculate a value from the model.

In tests/models.py we create a create a PersonModelTestCase and define a setUp function to handle creating a person model that we can be used in our test cases.

test_age_in_seconds will make sure this function returns the correct value using the assertEquals. We verify that a person instance with an age of 22 would would return 693792000, and the similar concept goes for the next test.

Models, forms, managers and template tags are well suited to write unit tests, because it's easy to isolate the sections of code and test it. If there are requests for more details about testing these aspects of a Django application I will write a follow up post.

Mocking

As you start working on more complex applications, you will start to relying on many outside resources for your application to work properly. I always try to write tests that can be run without being connected to the Internet. This is where mocking comes in to play, I am not going to describe it here but there is going to be a follow up post that will dive deeper into one of the mocking frameworks available for python.

Wrap up

I hope this helped. If you have any question, feedback, or requests please leave them in the comments below.

Written By Vitaly Babiy

Avatar for vbabiy@howsthe.com

Vitaly Babiy is the creator of Howsthe.com (Yes, you can contact him about the service). He is a software engineer at heart, loves working with great technologies like Django and Jquery. Vitaly spends most of his days in python and loves it. Another passion of Vitaly's is learning the business side of things, one of the reason why he started Howsthe.com monitoring service. You can follow him on Twitter

blog comments powered by Disqus

A blog about development, marketing, and design.