Testing with Django and Nose
I’ve never been terribly happy with the testing options in Django, so like a lot of people, I ended up rolling together my own solution.
Quite a while ago, I rolled together a plugin to enable testing of Django apps using Jason Pellerin’s fantastic nose testing tool.
You can grab the code from Assembla here : nose-django.
My main gripes with Django’s testing model are (in order):
- Doctests – evil in disguise. Consider these Decepticons.
- Python code tests need to subclass unittest.TestCase
- Test code goes into either models.py or into tests.py
- Lack of coverage analysis
- nose rocks – I really really want to use nose.
Now, I’ve got one more reason – I never notice until recently that pdb just goes off into Neverneverland with doctests.
So let’s stick a pdb.set_trace() into my testcase in doctest and see what happens:
Here’s the doctest:
24 __test__ = {‘API_TESTS’: “”"
25 >>> image_data = open(os.path.join(os.path.dirname(__file__), “test.png”), ‘rb’).read()
26 >>> p = Person(name=”Joe”)
27 >>> p.mugshot.save(“mug”, ContentFile(image_data))
28 >>> print p.mugshot.width
29 >>> import pdb
30 >>> pdb.set_trace()
31 >>> shutil.rmtree(temp_storage_dir)
32 “”"}
and here’s the output
Creating test database…
Creating table file_storage_person
Creating table django_admin_log
Creating table auth_permission
Creating table auth_group
Creating table auth_user
Creating table auth_message
Creating table django_content_type
Creating table django_session
Creating table django_site
Installing index for admin.LogEntry model
Installing index for auth.Permission model
Installing index for auth.Message model
(Pdb) ?
(Pdb) u
(Pdb) dir()
['FileSystemStorage', 'Image', 'Person', '__builtins__', '__doc__', '__file__', '__name__', '__test__', '_imaging', 'models', 'p', 'pdb', 'shutil', 'temp_storage', 'temp_storage_dir', 'tempfile']
(Pdb) n
(Pdb) n
(Pdb) dir()
['BOOM', 'FAILURE', 'SUCCESS', 'check', 'compileflags', 'example', 'examplenum', 'exc_info', 'exc_msg', 'exception', 'failures', 'filename', 'got', 'original_optionflags', 'out', 'outcome', 'quiet', 'self', 'test', 'tries']
(Pdb)
Well that was unsatisfying. I can’t see where in the call stack I’m at in any reasonably easy way – no line numbers, no filenames to help out. I can’t seem to ask for help.
Heck – I actually have to step up a call frame to actually get to where the set_trace() was called.
Hitting next doesn’t seem to do anything based on the pdb output, but it actually is stepping through code. This ain’t obvious to poor dumb little me.
This is lousy.
Fixing doctest isn’t the right solution – we’re only using doctest because it alleviates us from the pain of having to subclass unittest.TestCase and write all the scaffolding code that that entails.
What if we use nose?
We get this for our test code:
8 def test_fs():
9 image_data = open(os.path.join(os.path.dirname(__file__), “test.png”), ‘rb’).read()
10 p = Person(name=”Joe”)
11 p.mugshot.save(“mug”, ContentFile(image_data))
12 print p.mugshot.width
13 import pdb
14 pdb.set_trace()
15 shutil.rmtree(temp_storage_dir)
and this is the output
Creating test database…
Creating table file_storage_person
Creating table django_admin_log
Creating table auth_permission
Creating table auth_group
Creating table auth_user
Creating table auth_message
Creating table django_content_type
Creating table django_session
Creating table django_site
Installing index for admin.LogEntry model
Installing index for auth.Permission model
Installing index for auth.Message model
16
/> /Users/victorng/dev/djangotest/app/file_storage/tests.py(15)test_fs()
-> shutil.rmtree(temp_storage_dir)
(Pdb)
So, we have the filename, the line number – and we can start doing stuff in pdb right away. Great!
Not only that, but our nose test code is shorter than the doctest code because we don’t have pesky “>>>” line prefixes to deal with.
nose will also automatically detect all your test code – whether that’s unittest, a class that looks like a Testcase class, module top level functions that start with test …
heck – pretty much anything that looks like a test can be automatically detected and run with nose. Even those nasty doctests.
I haven’t yet updated nose-django to use the new faster database rollback patches for fast testing that were merged into Django in changeset 9756, but that’ll come this weekend.
If all goes according to plan, I should be able to post up the SQL Server 2000 backend into a public repository for Django/Jython, as well as a backport of fastdb testing to Django 1.0.2.
Hi!
Do you have any example? How it works with django 1.0
Need step-by-step tutorial
Have you encountered and solved the problem that myapp/admin.py gets imported by nose first and then by Django, at which point re-registration of admin classes raises an exception?
Hi,
I’ve done a patch to the nosedjango which enables custom settings-files (ie. other than the default settings.py). If you’re interested in it, feel free to contact me via e-mail.