A Better Way To Test Django


You shouldn't have to recreate a database every time you want to run unit tests in django.

The idea behind the test database is great -- don't mess with your production data for tests, but there are better ways to achieve this. Multiple environments are easy to maintain with tools like Docker + Digital Ocean so keeping production data and testing data separate is trivial.

I prefer to keep three levels in my stack

  • Development should hold enough testing data to develop. Don't care too much about the quality here, this contains the local DB that you as a developer will be emptying as needed when things get messy.

  • Staging should keep a subset of production data and an exact copy of the production environment for heavy testing. Cleaning this up will happen periodically but definitley not every time you need to run the test suite.

  • Production is the real deal. Validity of the data is a high priority, don't muddle it with tests.

Ideally, your tests should setup and cleanup after themselves, so that you don't actually need to worry about bad data.

Nosetests

Nose is awesome, it extends the standard unittest library and includes tons of useful extras and the option to use 3rd party plugins.

No matter your library of choice for testing, you should be able to use the following ideas in your Django testing

Testing Django

Ok, here's the good stuff. How do you run unit tests on Django without that pesky testing database?

Easy.

Create a new directory in the top level of your project to hold your tests

My projects generally look like this:

    [projectname]/                  <- project root
    ├── [projectname]/              <- Django root
    │
    ├── app_one/
    │
    ├── app_two/
    │
    ├── test/
    │   ├── __init__.py
    │   ├── app_one/
    │   │   ├──__init__.py
    │   │   ├──config.json
    │   │
    │   └── app_two/
    │
    ├── manage.py
    .
    .
    .
Import your Django setting in test/__init__.py

This is a great place to import Django's settings because all packages located here will also pull in the settings and the code will look very similar to the wsgi file for your project.

test/__init__.py:
    import os
    from django.core.wsgi import get_wsgi_application
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "projectname.settings")
    application = get_wsgi_application()
Load test data from a config.json file

JSON is great because its flexible. This means that the test data you have here could be generated from fixtures, formatted to resemble data that your application expects at different interfaces, or a json payload for your API

For this example we just have two objects, a good ruler and a bad ruler.

    {
       "good_ruler":{
           "ruler" : {
               "email": "kingofthenorth@snow.com",
               "has_dragons": true,
               "first_name": "Jon",
               "last_name": "Snow",
           }
       }
       "bad_ruler":{
           "ruler" : {
               "email": "cersei@throne.com",
               "has_dragons": false,
               "first_name": "Cersei",
               "last_name": "Lannister",
           }
       }
    }
Set up a base test class for your package

Now its time to set up your tests -- if you're like me and like to keep things as granular as possible, keeping the having a base class within __init__.py will keep other files within this package clean. This allows you to inherit from this base class very easily within the package. You can also keep the bulk of the dirty work of setup and teardown methods here.

An example of a base test class using our test data:

I've added a test here rather in a new file/new test class for simplicity

test/app_one/__init__.py:

   import json
   import os
   from app_one.models import *
   from app_one.views import create_ruler

   dir_path = os.path.dirname(os.path.realpath(__file__))
   with open(dir_path + '/config.json') as json_data_file:
       data = json.load(json_data_file)


   class TestRulersBaseClass:
       def setUp(self):
           """This method is run once before each test method"""

       def teardown(self):
           """This method is run once after each test method"""

       @classmethod
       def setup_class(self):
           """This method is run once before entire test class"""
           self.data = data

       @classmethod
       def teardown_class(self):
           """This method is run once after entire test class"""
           self.john.delete()
           self.cersei.delete() # Tests that clean up after themselves :)

       def test_01_create_ruler(self):

           self.john  = create_ruler(self.data['good_ruler']['ruler'])
           self.cersei = create_ruler(self.data['bad_ruler']['ruler'])

           assert john.has_dragons is True
           assert cersei.has_dragons is False

These tests can be run with the simple command: nosetests test.app_one

Lets say that I kept things tidy by creating a new file name rulers.py, and added a test class in that file that inherited from my base class

test/app_one/rulers.py:

    from test.app_one import TestRulersBaseClass

    class TestRulersClass(TestRulersBaseClass):
        def test_01(self):
            pass

        def test_02(self):
            pass

I would run it with nosetests test.app_one.rulers

My argument for using this is the same for writing the tutorial -- I don't need testing to be heavy at all times. If I don't want to recreate the testing db every time I most likely don't want to run the full suite every time either. With this structure I can choose which tests to run at a very granular level when I need to.

Archive