Django unit testing for form edit
Someone has probably already developed a technique for relieving the tedium for the following idiomatic unit test:
- GET a url with form data already populated
- POST a revised form with one or more fields edited
- Check response (profit!)
Step 2 is the most tedious, cycling through the form fields. Are there any ti开发者_StackOverflow中文版me-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.]
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 from the context.
If what you want to test is that the templates contain the proper HTML to make the form actually work (in order to catch errors like forgetting to include the management form for a formset in the template), I use WebTest and django-webtest, which parse your HTML and make it easy to fill in field values and submit the form like a browser would.
You can use response.context
and form.initial
to get the values you need to post:
update_url = reverse('myobject_update',args=(myobject.pk,))
# GET the form
r = self.client.get(update_url)
# retrieve form data as dict
form = r.context['form']
data = form.initial # form is unbound but contains data
# manipulate some data
data['field_to_be_changed'] = 'updated_value'
# POST to the form
r = self.client.post(update_url, data)
# retrieve again
r = self.client.get(update_url)
self.assertContains(r, 'updated_value') # or
self.assertEqual(r.context['form'].initial['field_to_be_changed'], 'updated_value')
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'].value, '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.
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.
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.
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)
I'd recommed you to take a look into acceptance testing level tools like robot test framework or letucce which are thought just for you want to do "I'm verifying that my application produces correct responses when a user makes changes to a form", that sounds more like acceptance (black-box) testing than unit-testing.
For instace, Robot let you to define your tests in tabular form, you define the workflow once and then you can define easily a bunch of tests that exercise the workflow with different data.
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.
精彩评论