Home » Odeon Blogs » Tudor, UI/UX Maestro »

Test Driven Development - The Django Way

Test Driven Development - The Django Way

Last week I had to setup a contest platform for one of our projects. As many of the things we do in Odeon, it was something new. This project is one of our most important and the specs weren't clear from the start. All the features evolve trough short ( sometimes intense, othertimes silly ) discussions. AGILE baby! Changes can break already existing code, so I took advantage of this opportunity to evolve my unit-test writing skills and bend some Django rules.

Big Picture

Main types of tests were:

  • unit-tests that lead to the final algorithm
  • regression tests
  • integration testing ( black-box style )

Unit tests

Unit testing in Django is close to trivial, but takes time, nonetheless. These tests helped me define an lean way to score points in the contest and organize the all the methods under the proper classes.

Example: A user can score a point only if he is logged in, there is a contest running, he is not the contest owner and the players limit hasn't been reached. In the first iteration, I wasn't even aware of all these requirements so I had one 'if' for each check. After thinking a about some real-life scenarios, my code ended up as only one line that actually called the .score() method, while all these checks are cleanly inside the appropriate class. The coder that might change or maintain my code would just use the .score() method wherever needed.

Regression tests

"Regression testing focuses on finding defects after a major code change has occurred. Specifically, it seeks to uncover software regressions, or old bugs that have come back. Such regressions occur whenever software functionality that was previously working correctly stops working as intended. Typically, regressions occur as an unintended consequence of program changes, when the newly developed part of the software collides with the previously existing code." Wikipedia.

These tests include: creation of models, creation of users and user profiles, checking various actions a user can do ( score different types of points ). It's important to check what a user can do AND what a user can't do in as many scenarios as possible.

Every time I added new fields to the models, I wrote tests to cover both new and old scenarios. Is there a need to say that it's important that all tests pass ?

Integration tests ( black-box style )

This was by far the funnest part. Given the fact that all tests until now passed, I can actually make some requests to see what the results should be. The previous tests were pretty low level, testing object creation and logic.

Who cares about low level ? The client or user surely doesn't. That's why the integrations tests needed to be high-level, black-box style, without knowing internal implementations.

Django's Test Client is a powerful tool, which acts like a dummy web browser. It can make GET and POST requests to your URLs and returns status codes, templates and rendered HTML.

I've setup a few URLs, some of them should be accessed trough AJAX. By making POST requests with POST data, the tests checked the forms for contest creation. I emphasize again the need to check when a form is submitted successfully and when not.

Tip: Django has a problem when reporting possible template or view errors, which might be misleading. If the URL tests doesn't explain the error, try a test which calls the view associated to the URL.

Example:
View test:

  1. from userprofile.views import ajax_login_status
  2. email = "jim.morrison@is.awesome.com"
  3. u, p, password = Profile.objects.create_user_and_profile(email)
  4. # create a fake request object, because it needs the .user property
  5. class ReqObj:
  6. user = u
  7. request = ReqObj()
  8. output = ajax_login_status( request )
  9. self.assertEquals( output, "{'is_authenticated': false}" )

URL test:

  1. client.login(username = u.username, password = password)
  2. response = client.get( '/users/ajax/check/login_status/', follow = True )
  3. self.assertEquals( response.status_code, 200 )

I strongly advise to write URL tests for every URL defined within the project.

Voila

Contest system is up. The next coder who wants to check if it's working properly or if he broke some code only needs to run ./manage.py test contest, wait for 3.7s and rest assured that no bugs crawled in.

Did I mention that during this time my Firefox was closed? After all the tests passed I browsed the app trough Firefox and it magically worked.
What no, pretty pages? Selenium to the rescue, but I'll cover that on the next occasion.

Resources

http://en.wikipedia.org/wiki/Software_testing
http://docs.djangoproject.com/en/dev/topics/testing/


Discussion

  1. I've been doing TDD with django for about a year now, and I've started writing a tutorial on the topic - I'd love some feedback from ppl with more/different experience! see:

    http://harry.pythonanywhere.com

    https://github.com/hjwp/Test-Driven-Django-Tutorial




Leave a Comment :

(required)


(required)




(required)








Page generated in: 0.20s