Django class based views and testing them

Posted by Drakonen on October 5, 2010 in Uncategorized | Short Link

One of the problems described well by Honza Král’s presentation on Djangocon.eu 2010, is how to test properly and make it easier. In his presentation he touches on using class based views instead of using normal functions (around 14:14 in the link video). This is not as straight forwards as it seems at first. There are some problems with thead-safety for one.

To have fast tests you should avoid hitting the database as much as you can, and after using this method I have to agree.

Making a class based view

We have this simple view:

from django.shortcuts import render_to_response, get_object_or_404
 
from mysite.polls.models import Poll
 
def simple_view(request, poll_id):
    """ A simple view, which takes an id from a url, shows it in a template. """
    poll = get_object_or_404(Poll, pk=poll_id)
    poll.access_counter += 1
    poll.save()
    return render_to_response('polls/detail.html', {'poll': poll})

And now as a Class based view:

from django.template import Context, loader
from django.http import HttpResponse
 
from mysite.polls.models import Poll
 
class simple_view(object):
    def get_objects(self, poll_id):
        """ Return poll object for given poll_id. """
        return get_object_or_404(Poll, pk=poll_id)
 
    def process(self, poll):
        """ Manipulate data and return the manipulated, unsaved object. """
        poll.access_counter += 1
        return poll
 
    def render(self, poll):
        """ Return HTML. """
        template = loader.get_template('polls/detail.html')
        context = Context({'poll': poll})
        return template.render(context)
 
    def __call__(request, poll_id):
        """ Show a Poll, increment the access_counter each time. """
        poll = self.get_objects(poll_id)
        poll = self.process(poll)
        poll.save()
 
        output = self.render(poll)
        return HttpResponse(output)

What did we do here? We separated the functionality into methods. We have four functions now:

  • get_objects which gets and returns the data, not more, not less. If you have more then one piece of data to get, return a tuple.
  • process which manipulates the data, but does not save it.
  • render transform the manipulated data into HTML.
  • __call__ the mighty function which ties it all together

Each of these functions do one thing, and try to do it well and isolated. Because this is so isolated, testing becomes quite easy since you can test the output of every step and it does not have side effects of the other parts. You still need to run __call__ at least once to check if it is all nicely tied together.

Testing

Testing a part of this view is simple:

class SimpleviewTest(TestCase):
    def test_simpleview_process_increment(self):
        view = mysite.view.simple_view()
        poll = Poll(question="sample")
 
        poll = view.process(poll)
 
        self.assertEquals(poll.access_counter, 1)

Create an instance of the view, create a poll object and feed it to the function. Check afterwards if the value has increased. This is fast, there is no database interaction!

Wiring it into urls.py

I found a little lambda helper to call the class based view:

# this wraps a class based view in a lambda, so it is thread safe (and I do not need to use __new__)
# yes this returns a lambda
class_view = lambda x: lambda *args, **kwargs: x()(*args, **kwargs)
 
urlpatterns = patterns('',
    (r'showpoll', class_view(mysite.views.simple_view)),
)

Just put the class_view function around the view reference.

This might seem quite a lot of code for this example, and it is, but this will give you some benefits when testing this way. Because this is a lot longer then before, you might not want to use it for every view, just for the more complicated ones.

Facebook Twitter Email

One Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">

Copyright © 2005-2012 Draakwired All rights reserved.
The Shades theme, version 1.7, is a BuyNowShop.com creation.

Social links powered by Ecreative Internet Marketing