close forgot it?
close
Login with password or OpenID

What to test in django?

I’m continuously trying to get used to test driven development, as I agree with its many advantages cited around the web. Still, I often find it hard to come up with a necessary (and hopefully sufficient) list of tests. Moreover, as I often code for fun only, I don’t pay much attention to the question. This has changed now as I got a paying project. Here are my first insights, I hope it will help other django enthusiasts as well. :)

The bottom line is test every method you write, including output variables and inside logic. Moreover, write tests that pass as they mimick normal usage, and then start to think about tests that fail if someone tries abnormal usage (like setting a start date after an end date).

Testing models

The model contains your business logic and data. Something no user should be able to avoid or misuse. As django itself is thoroughly tested there is not much reason to test whether a NullBooleanField accepts None as a value, and in general I see no reason in testing a model’s attributes.

On the other hand every defined method should be tested! For example, if you have a model to represent Comments with approved and published attributes, in the save method you’ll likely avoid saving if approved==True and published==False. This is a logic to be tested. You should similarly check your get_absolute_url (and similar) methods whether they provide proper output.

class ComentModel(models.Model):
    author = models.ForeignKey(User)
    comment = models.CharField
    published = models.BooleanField
    approved = models.NullBooleanField

    def save(self, force_insert=False, force_update=False):
        if approved==True and published==False:
            return
        super(Submission, self).save(force_insert, force_update)

Testing forms

My feeling is that many people misunderstand the use of forms. Basically forms are to interact with non-trusted users (and sometimes to extend the admin functionality as well, but that’s a different story).

This being said, it might happen that you have the same functionality on the admin and on your main site, like adding a comment for something. The difference being that on the admin site the user can pretend to be any other user for example, or can immediately approve his comment. While on the main site the user can comment only under his own name, and every comment might need approval. Despite this difference, both comments will be stored in the same Model.

Moreover, every logic related to a bit complex data that will be stored in the database should be included in your form definition, instead of using your views. This for example allows you to easily reuse the same form processing in different views (e.g. APIs might need different views than simple HTML, but they will still process the same data).

As this is the point many posts miss, I’ll repeat it: when you would like to save data coming form users in a model rather keep your views simple, and make your forms complex, than having complex views and simple forms.

So, as we will have some hard-working form classes, what should we test? The same principle applies as before: test every method you define! If there is any reason to define your own __init__ method, then you should test the modified form properties as well.

For example, take a conference application. Every conference has many Topics, and if you want to present a paper you might need to select a Topic when you submit your idea. But the form should show only the topics for the conference you are interested in. This can be achieved by overwriting the __init__ method like this

class SubmissionForm(RegistrationForm):
    topic = forms.ModelChoiceField(label=_('Select a topic'), 
           queryset=models.Topic.objects.all(), empty_label=None,
           help_text=_('Topics help our reviewers to coordinate their work. \
            Please find a topic that best matches your paper'))

    def __init__(self, *args, **kwargs):
        super(SubmissionForm, self).__init__(*args, **kwargs)
        self.fields['topic'].queryset=\
            models.Conference.objects.get(pk=kwargs['initial']['conference']).topic_set.all()

In this case we should test that form.fields['topic'].queryset contains all the topics of the given conference, and only those.

Testing views

As you already know, you should check every method you define. Moreover, you should check that the methods you want to be restricted (e.g. by the @login_required decorator) are really restricted, but are available for authorised users. For more subtle restrictions, like showing only your own posts further testing is necessary, as this assumes that the user should be logged in, but still can’t see every post.

Moreover, if you are using forms like above that should be initialised, then you should check that they are indeed properly initialised. In the above case this would mean to check the template context variable for the form object, and see the value of response.context['myform'].initial.

Testing templates

As I am not a designer guy, I’ve found this is the easy part! Basically, I just test for the variables that the designers might need to put together the content of the page. Like this:

class TemplateTests(MyTestCase):

    def check_template_var(self, rsp, var):
        '''
        Check for template variable ``var`` in response rsp
        '''
        if isinstance(rsp.context, dict):
            contexts = [rsp.context,]
        else:
            contexts = rsp.context

        for c in contexts: 
            if c.has_key(var):
                return True
        return False        

    def test_conference_list_html(self):
        rsp = self.client.get(reverse('conference_list'))
        self.assertTrue(self.check_template_var(rsp, 'conference_list'),
                        "conference_list is not set for conference_list.html")

Other tests

It might happen that you write some custom template tags, filter, or some strange admin functionality. I haven’t thought much about testing these yet, but for template tags at least there are some posts on the web

Conclusion

The bottom line is to test every method you write, including output variables and inside logic.

Add post to: Delicious Reddit Slashdot Digg Technorati Google
Subscribe via RSS | Comment

Comments

24.05.2009 16:21 Marc Fargas

About the thing you do in save()… You should never “drop” data this way.

The thing is you should never be reaching save() on that condition. This should have been stoped earlier. i.e: In form validation and in a near future, in model validation.

The right thing to do, IMHO, in save() would be: raise ProgrammingError(“You should NEVER have reached this point”)

Obsiously, the test would be a self.assertRaises ;)

Anyway, dropping data this way (not saving it) silently can produce lots of headaches and is not a very good practice. Again IMHO.

25.05.2009 10:53 V

I was thinking about this, and actually I do what you say as well. I check for these conditions at form validation too, but you are right that raising an exception would be a better solution. Thanks.

1.07.2009 10:57 casinos online

I was a bit annoyed at the lack of WYSIWYG fields for Django’s admin interface. I was originally planning to use FCKEditor but couldn’t find a polished Django implementation (although I’m sure there is one out there). Instead I opted to use TinyMCE due to this nice Django widget implementation. After running setup.py, installation is a cinch — I just added from tinymce import models as tinymce_models, and replaced instances of models.TextField() with tinymce_models.HTMLField(). To customize the options available to TinyMCE, I was able to add this to my settings.py:

16.10.2009 21:31 chairs

I do what you say as well. I check for these conditions at form validation too, but you are right that raising an exception would be a better solution. Thanks.

5.01.2010 17:36 cialis

class TemplateTests(MyTestCase):

def check_template_var(self, rsp, var):
    '''
    Check for template variable ``var`` in response rsp
    '''
    if isinstance(rsp.context, dict):
        contexts = [rsp.context,]
    else:
        contexts = rsp.context
  • Ebben a code error!
8.01.2010 16:47 cialis online

def test_conference_list_html(self): rsp = self.client.get(reverse(‘conference_list’)) self.assertTrue(self.check_template_var(rsp, ‘conference_list’), “conference_list is not set for conference_list.html”)

26.01.2010 19:57 ppo plans

Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design.

28.01.2010 11:12 Ed Hardy

I do what you say as well. I check for these conditions at form validation too, but you are right that raising an exception would be a better solution. Thanks.

4.02.2010 14:13 Adult sex toys

Wonderful photos.Thanks for posting such an exciting photos.Thanks again!!!

9.02.2010 6:41 evening dresses

I don’t pay much attention to the question. This has changed now as I got a paying project. Here are my first insights, I hope it will help other django enthusiasts as well. :)

16.02.2010 1:53 stop snoring

Great information. Thanks for sharing.

20.02.2010 10:33 evening dresses

A skac meg ült a padon, mint egy tök, a kis sérójával, aztán elkullogott. Azt hiszem van még tennivaló. Mennyi férfit kell még legyártani, mire ismét kialakul egy közfelfogás, hogy mit is jelent a férfi. Van munka.

11.03.2010 14:33 cheap vps

This is my first time i visit here. I found so many interesting stuff in your blog especially its discussion. From the tons of comments on your articles, I guess I am not the only one having all the enjoyment here! keep up the good work.

Comment form for «What to test in django?»

Required. 30 chars of fewer.

Required.

captcha image Please, enter symbols, which you see on the image