views:

487

answers:

6

Someone has probably already developed a technique for relieving the tedium for the following idiomatic unit test:

  1. GET a url with form data already populated
  2. POST a revised form with one or more fields edited
  3. Check response (profit!)

Step 2 is the most tedious, cycling through the form fields. Are there any time-saving hacks for testing Django forms?

[Update: I'm not testing Django forms handling. I'm verifying that my application produces correct responses when a user makes changes to a form. This is an application which processes clinical information, hence a lot of possible responses to test.]

+1  A: 

Think carefully about why you need to unit-test this. Forms are part of the core Django functionality, and as such are very well covered by Django's own unit tests. If all you're doing is basic create/update, which from your question it sounds like is the case, I don't see any reason to write unit tests for that.

Daniel Roseman
Maybe he has custom validation.
Felix Kling
Yes, lots of custom validation. Believe me, I'm not looking to create more work for myself. ;-)
Jeff Bauer
A: 

You are possibly looking for tools that do front end testing like twill or selenium

Both of these generate python code, that can be included within the django tests, so when you run tests, it opens the urls, posts the data and inspects whatever you want!

It should help you to see these tests written for selenium, for an open source reusable django app.

Lakshman Prasad
A: 

I don't see how or why you need unit tests for this. Sounds to me like you're testing for (and correcting) possible user input, which is covered very simply with Django's form validation (and model validation in 1.2)

jonwd7
+1  A: 

It depends what you are trying to test. I would target your tests a bit more finely than it sounds like you are doing.

If the code you need to test is the form validation logic, then I would simply instantiate the form class directly in your tests, pass it various data dictionaries and call .is_valid(), check for the proper errors or lack thereof. No need to involve HTML or HTTP requests.

If it's view logic (which IMO should be minimized) that you are testing, you will probably want to use the test client, but you shouldn't need to do multi-stage tests or very many tests at this level. In testing view logic I wouldn't scrape HTML (that's testing templates), I'd use response.context to pull out the form object

If what you want to test is that the templates contain the proper HTML, I don't usually do this at the Django test level; I go straight to something like Selenium which is designed for UX testing.

Carl Meyer
"I'd use response.context to pull out the form object" <-- Doing this now. Not really testing HTML, just the outcomes of a process. Thanks for your post.
Jeff Bauer
+1  A: 

It's not clear but one guess is that you have tests like this.

class TestSomething( TestCase ):
    fixtures = [ "..." ]
    def test_field1_should_work( self ):
        response= self.client.get( "url with form data already populated" )
        form_data = func_to_get_field( response )
        form_data['field1']= new value
        response= self.client.post( "url", form_data )
        self.assert()
    def test_field2_should_work( self ):
        response= self.client.get( "url with form data already populated" )
        form_data = func_to_get_field( response )
        form_data['fields']= new value
        response= self.client.post( "url", form_data )
        self.assert()

First, you're doing too much. Simplify.

class TestFormDefaults( TestCase ):
    fixtures = [ "some", "known", "database" ]
    def test_get_should_provide_defaults( self ):
        response= self.client.get( "url with form data already populated" )
        self.assert(...)

The above proves that the defaults populate the forms.

class TestPost( TestCase ):
    fixtures = [ "some", "known", "database" ]
    def test_field1_should_work( self ):
        # No need to GET URL, TestFormDefaults proved that it workd.
        form_data= { expected form content based on fixture and previous test }
        form_data['field1']= new value
        response= self.client.post( "url", form_data )
        self.assert()

Don't waste time doing a "get" for each "post". You can prove -- separately -- that the GET operations work. Once you have that proof, simply do the POSTs.

If you POSTS are highly session-specific and stateful, you can still do a GET, but don't bother parsing the response. You can prove (separately) that it has exactly the right fields.

To optimize your resting, consider this.

class TestPost( TestCase ):
    fixtures = [ "some", "known", "database" ]
    def test_many_changes_should_work( self ):
        changes = [
            ( 'field1', 'someValue', 'some expected response' ),
            ( 'field2', 'someValue' ),
            ...
        ]
        for field, value, expected in changes:
            self.client.get( "url" ) # doesn't matter what it responds, we've already proven that it works.
            form_data= { expected form content based on fixture and previous test }
            form_data[field]= value
            response self.client.post( "url", form_data )
            self.assertEquas( expected, who knows what )

The above will obviously work, but it makes the number of tests appear small.

S.Lott
Upvoted because it's the most useful response so far. I'm basically doing your optimization now. GET is already in a separate test, but I'm using it here to replace line: "form_data={ expected form content based on fixture and previous test }", as I'm iterating through auto-generated fixtures and it saves the work of manually typing out the details.
Jeff Bauer
@Jeff Bauer: "it saves the work of manually typing out the details"? What? So does refactoring the default data into `setUp` or a superclass or a separate function. There are a million programming techniques to reduce redundancy, all of which can be used in unit testing. Why aren't you simply refactoring the tedious parts? I don't understand what's so hard about designing a test class that's somehow optimal.
S.Lott
+1  A: 

django-webtest is perfect for such tests:

from django_webtest import WebTest

class MyTestCase(WebTest):
    def test_my_view(self)
        form = self.app.get('/my-url/').form
        self.assertEqual(form['my_field_10'], 'initial value')
        form['field_25'] = 'foo'
        response = form.submit() # all form fields are submitted

In my opinion it is better than twill for django testing because it provides access to django internals so native django's response.context, response.templates, self.assertTemplateUsed and self.assertFormError API is supported.

On other hand it is better than native django test client because it has much more powerful and easy API.

I'm a bit biased ;) but I believe that django-webtest is now the best way to write django tests.

Mike Korobov