Testing is quite fun by itself, as it feels like benevolent magic to me. But it can even be more fun with some simple tools. Some of these already exist, but are sometimes a bit hard to find. Here are the ones that I use.
Most of the time I write Django tests. Moreover, I struggle to write proper browser-based tests too. The django tests are unit and integration tests. They are easy to implement with the tools django provides. Still, during the years, I’ve built up a quite rich toolset for writing tests, and from time to time, I even test the browsers with the django test runner.
My toolset
The very basis of all my testing activities is django-nose. Basically, its a simple wrapper around the nose test runner.
Why is django-nose that useful? Because of the nose plugins. My favourite one is the nose notification centre. It’s totally useless if you are obsessed with how your tests run, but it’s fun and fancy if you prefer to read your mails or just do something else to watching your tests run.
There is one caveat to django-nose tough, you should be aware of: your tests will always run with DEBUG=False. This is quite natural, as your production server will likely run as well with DEBUG=False
, but it might still be surprising for example if you would like to analyse your DB queries, or you’ve switched off some part of your code locally on purpose, and you use the debug variable to signal this.
For testing frontend code, I prefer to write QUnit tests, but their implementation is still tested best together with the django backend running. For this I prefer Selenium, as it was the easiest to get up and running.
To make my tests run as fast as possible, and to make them atomic, I try to use mock objects wherever I can. This is quite tricky, I’ve never managed to properly mock a related and required model in django. But I can still save a lot in test running time with my current mock. For this I use the well known Mock library.
To test my apps, I use all the above tools, moreover, I have a quite nice runtests.py file in my Python package to run all or just selected tests.
Nose plugins
Notification Centre
It’s installation is kinda tricky though, so here is a step-by-step description:
|
|
The tricky part is to install the terminal notifier first. :)
Once installed you can get it working by adding the following options to your Django settings file:
|
|
Enjoy!
How to set DEBUG=True
Easy-peasy you might think, but actually, if you don’t want to write your own test runner or a custom nose plugin, you can only set it on a per test basis.
My preferred solution for this is to use a nose plugin, namely nose-django-querycount. This plugin adds a simple summary of queries run in each of your tests, plus - for this to happen - it sets DEBUG
to True
.
Selenium with Django
Selenium can be used directly with Django. Even the docs have some examples. Still, the Selenium API might not be what you prefer. Thankfully, there are a few wrappers around it. The one I’ve selected is splinter.
Setting up a Splinter/Selenium test with Django is pretty simple. First you should install splinter with pip install splinter
, after you should write your tests as usual. Here is my preferred approach:
|
|
These few extra methods add a get
method to the browser instance, this makes it quite similar to self.client.get
. Moreover, I add a new assertion, and take care of quitting the browser at teardown. Now, you might ask why do I run my tests in Firefox, instead of something chrome-less, like phantomjs? Your question is 100% valid, I would like to run my tests in chrome, but unfortunately they fail.
Testing the backend code and timing
With selenium tests, I’m asserting that some of my django methods have been called as expected. Unfortunately, it often happens that the given server action did not finish by the time the test runner gets to my code.:
|
|
Using Firefox, I’ve managed to get around this by adding a 1 second wait time before my assertions.:
|
|
Mocking
I guess there aint much to be written in short about mocking. It’s a must, especially if you have several related apps or remote services used in your apps.
My preferred way to mock is by mocking single methods, and passing on an expected return value. This way I foresee my code to run properly. E.g.:
|
|
This way, the form never run its save method, but my code can still expect its result.
My runtests.py
file
I use this file to test my reusable Django apps.:
|
|
The fun part is the runtests function. This way I can call python runtests.py views
or even python runtests.py views:ViewTest.test_method
to run just the view specific tests or just a given test, respectively. This really makes testing of apps easy!