Easier Testing of django models and forms

In this post lets talk about, making lives easier while testing django models and forms. This is the fourth article in our Test Driven Development Series. Until now, we have already talked about intro to TDD, basic testing django views and models and functional testing with selenium.

Testing Django Models

By default when django runs against sqlite backend it creates a new in memory database for a test, based on the fixtures which you have kept. Application fixtures are a great way to test, but these are limited to the cases where you have a constant data model. But the projects we deal with are much more complicated than just having simple models, and manually managed fixtures are an inflexible solution for providing the test case data. Test fixtures are hard to maintain, when trying to reproduce complex referential associations. Doing it manually or monkey patching gets us into more trouble as we fail, more often than not in maintaining the integrity of the schema.

While writing tests, a large portion of our test code, mainly prepares the data that we provide to our tests. When this system becomes even slightly flawed then reading, writing or maintaining tests becomes a cumbersome task. The trouble escalates when data mutations lead to bigger failures across the system, and then you have to take an extra amount of pain to systematically refactor the entire stuff.

The above problem leads us to think about managing our fixtures. Utilizing APIs to create data in concise readable format which makes it much easier to handle schema association and updation and use them inside our tests. They help us to avoid code repetition and streamline the process of creating and managing test data. Amongst the most famous ones are factory-boy, few more can be found here. Personally, for me managing the factories becomes tiring, and I rely on django-dynamic-fixture. We will be working with it, in this post.

The library has got a decent documentation. Why it should be used, what led to the creation, its features, and comparison with other fixture tools. To easily get started you can install it with pip install django-dynamic-fixture. This article is not about choosing fixture APIs, rather testing models so lets get back to our polls model, and see how we can easily test them. The description to our polls model can be found on our last post. Writing test for them would be something as follows:

from django.test import TestCase
from django_dynamic_fixture import G

class PollModelTest(TestCase):

    def test_poll_creation(self):
        "Test the creation and saving of a new poll"
        poll1 = G(Poll)
        poll2 = G(Poll)
        polls_in_db = Poll.objects.all()
        self.assertEquals(polls_in_db.count(), 2)

        # check the values stored
        poll_zero = polls_in_db[0]
        self.assertEquals(poll_zero.question, poll1.question)
        self.assertEquals(poll_zero.pub_date, poll1.pub_date)

Please compare this, with the previous test case we wrote, hope you will appreciate the differences. In a single line we can create fixture and get rid of maintaining the separate json file with proper schema and changing it every time as per changing requirements. Having seen

Testing Django Forms

The first thing to keep in mind when testing django form is again that the django form handling should not be tested. Meaning that the core behavior should not be tested. What needs to be verified is if the application produces correct responses when a form is submitted/edited. Superficially, a form is just a class with few member functions. A form goes through the following three stages:

  1. Instantiating an instance.

  2. Populating some data.

  3. Clean the form and validate the data.

Lets do it with an example. Assume we have to test a basic registeration form. The steps would be as follows:

1. create a user

    User.objects.create('bob', '[email protected]', 'letmein')

2. create invalid data dictionaries, one easy example being that the

two data passwords did not match.

    invalid_data_dict = [
        {'data': {'username':'foobar',
                  'email': '[email protected]',
                  'password1': 'foo',
                  'password2':'foo'},
         'error': ('password', [u"The two password fields didn't match."])
    ]

This is one example of an invalid data, there can be other cases, with an invalid username, an already existing username, desired password strength etc.

3. assert that the form fails for all invalid data, correct error message is displayed.

for invalid_data in invalid_data_dict: form = forms.RegisterationForm(data=invalid_dict['data']) self.failIf(form.is_valid()) self.assertEqual(form.errors[invalid_dict]['error'][0], invalid_dict['error'][1])

The above was a very basic but an apt example of testing a django form. For further cases when you have custom validations or save method things can be made handier with the use of django-webtest. To install webtest all that needs to be done is pip install django-webtest A simple example of testing with webtest is as follows:

from djagno_webtest import WebTest

class DemoTestCase(WebTest):

def test_my_form(self)
        form = self.app.get(reverse('url-name')).form
        self.assertEqual(form[form['fieldname1'].value, 'some value'])
        form['field2'] = "some other value"
        response = form.submit().follow()
        self.assertEqual(response.context['field2'].value, 'some
    other value')

Having taken a look at webtest again forces us to choose. Do we chuck selenium/twill and use only django-webtest. Well there is again no answer to it. Having said that the thoughts that come are as follows:

Selenium is primarily built to be a server agnostic testing framework, whereas django-webtest is built to work specifically on django websites and is actually a WSGI only framework.

Pros for WebTest:

  1. Being Python only WebTest can interact deeply with and with a higher level of awareness of how things are built inside. It provides access to django internals and native API stuff as self.assertFormError, response.templates etc. are supported.

  2. WebTest is faster, since it hooks to the WSGI, this also means a smaller memory footprint.

  3. No server is required for running WebTest.

Cons for WebTest:

  1. WebTest can't test JavaScript and reveal browser issues.

  2. Macros can't be recorded and run as with Selenium.

  3. No browser extensions exists for reasons obvious, in case you rely on browser extensions Selenium might be handier.

Bottomline is choose one and stick, depending on your needs. Hope the article helped!

Comments

blog comments powered by Disqus