Although I usually begin writing tests for models, if I had to pick only one component to test I would pick The View. That is because testing views will give you the best coverage for a typical Django application.
If your apps are not project specific you need to decouple tests from project particulars. While it is safe to expect a project will provide database access, assuming too much might lead to your tests failing undesirebly1. You may even end up losing data. Ideally, a reusable app should be testable under any project.
Your tests will probably assume a certain URL configuration and rendered bits of templates. If developers of the project change the URL configuration or install their own templates your tests will fail. They did nothing wrong though, it is called customization.
Specifying URLConf And Templates For Tests
The first step of isolating your view related tests is to supply a specific URL configuration for your tests. django.contrib.auth does this. It also uses a specific set of templates. This is important because an innocent {% url some_project_specific_view %} in your base template could prevent tests to run properly.
1 class AuthViewsTestCase(TestCase):
2 ...
3 urls = 'django.contrib.auth.urls'
4
5 def setUp(self):
6 ...
7 self.old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS
8 settings.TEMPLATE_DIRS = (
9 os.path.join(
10 os.path.dirname(__file__),
11 'templates'
12 )
13 ,)
14
15 def tearDown(self):
16 ...
17 settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS
How To Reset URLConf
If your URL configuration is generated dynamically and varies on a setting, you might need to re-evaluate it during testing. One of my apps generate some URLs (effectively overriding another apps URLs) only when a certain setting is True. When testing this specific feature I would need to first set this value then reset URLConf and finally test if everything is OK.
Resetting URLConf is actually as easy as reloading related modules and calling django.core.urlresolvers.clear_url_caches:
1 from django.core.urlresolvers import clear_url_caches
2 from django.utils.importlib import import_module
3 from test_settings import SettingsTestCase
4
5 class FooTestCase(SettingsTestCase):
6 urls = 'foo.test_urls'
7
8 def reset_urlconf(self):
9 reload(import_module('foo.test_urls'))
10 clear_url_caches()
11
12 def test_bar_true(self):
13 self.settings_manager.set(BAR=True)
14 self.reset_urlconf()
15 # Rest of the test
16 ...
I’m using TestSettingsManager by carljm here.
Room For Improvement
I am sure more can be done here. If you know other techniques to make tests2 more reliable and expressive please let me know.
1: OK, we never desire our tests to fail. But it is undesirable to have them failing when our code is actually valid.
2: For Django apps that is.
Related posts:
- Working with files in Django – Part 1
- Working with files in Django – Part 2
- Working with files in Django – Part 3
- My PyCon APAC 2011 Presentation: Optimizing Media Performance with django_compressor
- Drawing Gradients with PyGame
Although I usually begin writing tests for models, if I had to pick only one component to test I would pick The View. That is because testing views will give you the best [coverage](http://pypi.python.org/pypi/django-test-coverage/) for a typical Django application.
If your apps are not [project specific](/2009/03/django-where-should-my-app-live/) you need to decouple tests from project particulars. While it is safe to expect a project will provide database access, assuming too much might lead to your tests failing undesirebly1. You may even end up [losing data](/2009/07/django-testing-with-file-system-side-effects/). Ideally, a reusable app should be testable under any project.
Your tests will probably assume a certain URL configuration and rendered bits of templates. If developers of the project change the URL configuration or install their own templates your tests will fail. They did nothing wrong though, it is called **customization**.
### Specifying URLConf And Templates For Tests
The first step of isolating your view related tests is to supply a [specific URL configuration](http://docs.djangoproject.com/en/dev/topics/testing/#django.test.TestCase.urls) for your tests. [`django.contrib.auth`](http://code.djangoproject.com/browser/django/tags/releases/1.1/django/contrib/auth/tests/views.py) does this. It also uses a specific set of templates. This is important because an innocent `{% url some_project_specific_view %}` in your base template could prevent tests to run properly.
1 class AuthViewsTestCase(TestCase):
2 ...
3 urls = 'django.contrib.auth.urls'
4
5 def setUp(self):
6 ...
7 self.old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS
8 settings.TEMPLATE_DIRS = (
9 os.path.join(
10 os.path.dirname(__file__),
11 'templates'
12 )
13 ,)
14
15 def tearDown(self):
16 ...
17 settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS
### How To Reset URLConf
If your URL configuration is [generated dynamically](/2009/05/serving-static-media-in-django-development-server/) and varies on a setting, you might need to re-evaluate it during testing. One of my apps generate some URLs (effectively overriding another apps URLs) only when a certain setting is `True`. When testing this specific feature I would need to first set this value then reset URLConf and finally test if everything is OK.
Resetting URLConf is actually as easy as reloading related modules and calling `django.core.urlresolvers.clear_url_caches`:
1 from django.core.urlresolvers import clear_url_caches
2 from django.utils.importlib import import_module
3 from test_settings import SettingsTestCase
4
5 class FooTestCase(SettingsTestCase):
6 urls = 'foo.test_urls'
7
8 def reset_urlconf(self):
9 reload(import_module('foo.test_urls'))
10 clear_url_caches()
11
12 def test_bar_true(self):
13 self.settings_manager.set(BAR=True)
14 self.reset_urlconf()
15 # Rest of the test
16 ...
I'm using [TestSettingsManager](http://www.djangosnippets.org/snippets/1011/) by [carljm](http://www.djangosnippets.org/users/carljm/) here.
### Room For Improvement
I am sure more can be done here. If you know other techniques to make tests2 more reliable and expressive please let me know.
-----
**1**: OK, we never _desire_ our tests to fail. But it is undesirable to have them failing when our code is actually valid.
**2**: For Django apps that is.
Tags: django, python, testing
This entry was posted
on Wednesday, September 9th, 2009 at 16:43 and is filed under Programming.
You can follow any responses to this entry through the RSS 2.0 feed.
Both comments and pings are currently closed.