Django class based views and testing them
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_objectswhich gets and returns the data, not more, not less. If you have more then one piece of data to get, return a tuple.processwhich manipulates the data, but does not save it.rendertransform 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.
[...] This post was mentioned on Twitter by Imaginary Landscape , Django Ireland. Django Ireland said: Draakwired : Django class based views and testing them http://bit.ly/9PYVoX [...]