Testing Django — Lessons Learned

I recently had the honor to work on a major refactoring of Cadasta’s test code. The goal was to make the test code more consistent and easier to write. One result of this work was a small test framework, which I’m going to write about some other time; another was a bunch of notes that I’m going to summarize in this post.

Terms and definitions

Let’s start with definitions for two terms I’m going to use throughout this post:

  • Test method: A test method is a method within a TestCase; its name starts with test_. Each test method defines a single test and should hence focus on testing exactly one thing.

A testing strategy

When writing tests, you usually aim for 100% test coverage. That means every line of code should be touched by a test at least once. That said, even if a test covers many lines of code it is not necessarily a useful test. When you examine a view, it likely touches many components “below,” such as forms or serializers, managers, and models. By testing a view, you easily end up with a high coverage score.

Keep database hits at a minimum

Database requests slow down test execution, and there is nothing more annoying than having to wait for a slow test to pass. Database hits should be kept to a minimum. There usually three cases that create many database requests:

  1. Try to avoid querying the database for assertions. That often happens when you test views. Some model instances are created and then queried as a QuerySet to check whether the view is rendered with the correct context. Try to avoid those situations by caching model instances. You can combine cashed model instance as a list and use that instead of an actual QuerySet. There is one exception: If you update a model instance, you want to test whether the model has updated the corresponding record in the database.
  2. Always create only those models instances needed for a test to pass. It is tempting to create all the model instances in the setUp method to keep the test methods clean, but many tests only require a fraction of the models created. Those unused model mean unnecessary hits to the database. Create only the model instances which are required throughout all test methods in the test case and add those unique to some tests in the test method itself.

Don’t use iterator factories

Factory boy’s Iterator is very convenient if you can’t decide which value to provide for a field. But they also make tests non-deterministic, which means a test might fail randomly even though the code is not broken.

Don’t DRY test cases

Avoid using class hierarchies for your tests. It’s tempting because tests involve a lot of repetitive code, but it’s bad practice for tests. If a test fails, the person working on the issue will have to figure out how the test is set up to assess what breaks the code that is being tested. The more the test configuration is spread across several classes, the more difficult it is to get the full picture.

I build software that helps people to collect, manage and share geographic information over the Web.

I build software that helps people to collect, manage and share geographic information over the Web.