Posts Tagged ‘testing’

Django Fixtures

Thursday, September 24th, 2009

A fixture is basically a data dump in a specific format. There is no restriction of which models or how much data a fixture can contain. Fixtures are portable. They work the same with all the database backends and operating systems supported by Django. This means that you can use fixtures as a simple data migration tool1. Django supports a number of formats by default. JSON format is widely used in the community. But you can easily create serializers for other formats. Although it is not a requirement, all built-in serializers produce human-readable output. Therefore fixtures are an ideal way to keep your data and code together without tightly coupling them.

One limitation you should be aware of is that fixtures are deserialized with the absolute values of primary keys. This makes it difficult to store permissions in fixtures. Also, say, you have two different fixtures for the same model with overlapping pk values. If you load both, instances with overlapping pk‘s will be overwritten by the second load.

Test Fixtures

Fixtures are most useful during testing. They are an easy, reliable way to test your app with some data. If you prefer unittests like I do, here’s how to attach fixtures to your TestCase:

   1 from django.test import TestCase
   2 
   3 
   4 class MyTestCase(TestCase):
   5     fixtures = ['test_users', 'test_foos.json', 'test_bars.xml']
   6 
   7     # rest of your class

Here is a tip for preparing fixtures quickly:

  1. Enable admin for your app. Just registering your models should be enough.
  2. Create and/or edit test data via admin.
  3. You can also use Django shell2 for test data creation.
  4. Dump your data as intermediary fixtures3.
  5. Manually perform any final editing if necessary and save.

This method might look like a lot of work. But it is actually very practical to prepare your test fixtures, or fixtures of any kind this way. Most of the work is removing unneeded data and tweaking the primary keys in the final step. But it is still faster than manually writing the whole thing.

Initial Data

If you want to load some data right after creating your database tables, you provide initial_data fixtures. At the end of syncdb (and flush), Django automatically loads fixtures in your installed apps4 named initial_data regardless of their format.

This is good if your app require some data to function correctly. But there is a pitfall; as stated in Django documentation your initial_data fixture will be loaded every time you run syncdb. And if you edit these entries they will be overwritten next time you run syncdb. Therefore initial_data is only good for data immutable in nature, such as units of length.

If you want to supply initial data for your project it is better to create one or more bootstrap fixtures and load them manually. Here is my minimal list of things I put in my bootstrap fixture:

  • An auth.User as my superuser. It is much easier to load a superuser from fixtures than to create it interactively. I always run syncdb with --noinput and just change my superuser’s password once when I deploy.
  • A profile for my superuser.
  • A sites.site object.

And of course if all of the following fixtures will be loaded when you issue manage.py loaddata bootstrap command:

  • project_dir/myapp1/fixtures/bootstrap.json
  • project_dir/myapp1/fixtures/bootstrap.xml
  • project_dir/myapp2/fixtures/bootstrap.json
  • project_dir/myapp3/fixtures/bootstrap.yml

I think it would be nice if re-usable app authors agreed upon a convention like naming initial data fixtures bootstrap.


1: For very little amount of data this works fine. But serialization/deserialization becomes unreasonably slow for larger data sets. Then it’s better to use native tools for your database, modifying the input if necessarily.

2: Use manage.py shell command to enter Django shell.

3: Use manage.py dumpdata <app_name> > <out_file> command to dump your app data as a fixture. By default Django uses JSON format. If you add --indent=2 it will make the output much easier to read and edit.

4: Apps that are in your settings.INSTALLED_APPS.

Bookmark and Share

Django: Testing Views

Wednesday, September 9th, 2009

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.

Bookmark and Share

Django: Testing With File System Side Effects

Thursday, July 30th, 2009

Django uses unittest for testing. django.test.TestCase is a special base class for tests that adds various features1. Django testing framework takes care of creating a test database, resetting it to its initial state after each test and finally destroying it. So you don’t have to worry tests overwriting anything in your main database.

But if your tests need to create, modify or delete files you need to make sure these side effects are contained. For instance if one of your applications is generating or uploading files within your MEDIA_PATH you would rather not let it mess with files already there.

Here is a little example of using a temporary folder for such operations:

   1 import shutil
   2 import tempfile
   3 from django.test import TestCase
   4 
   5 
   6 class FooTestCase(TestCase):
   7     def setUp(self):
   8         self.__old_foo_dir = settings.FOO_DIR
   9         settings.FOO_DIR = tempfile.mkdtemp(suffix='foo')
  10 
  11     def test_foo(self):
  12         # some tests with filesystem side effects
  13         pass
  14 
  15     def tearDown(self):
  16         shutil.rmtree(settings.FOO_DIR)
  17         settings.FOO_DIR = self.__old_foo_dir

Here tempfile.mkdtemp() creates a temporary directory for us and returns its path. When we are finished we remove this temporary directory with shutil.rmtree() and restore our setting FOO_DIR back to its original value. It is always best to clean up thoroughly. We don’t want our tests to fail because of themselves.

Assume settings.FOO_DIR was a directory under MEDIA_PATH. So when you diverted FOO_DIR to the temporary directory, files within couldn’t be served with media server or your development server (if you have configured it to serve static files). If for some reason you need these files accessible over HTTP you need to re-configure your media server. In case of development server it is relatively easy; just divert whole MEDIA_PATH and copy needed files to temporary directory. I suppose you can get away without copying static files to the new location in case of a real media server, nevertheless configuration will be somewhat difficult and a restart might be necessary.


1: For more information you can refer to official documentation.

Bookmark and Share