I dont believe any of this anymore and you should be using PyTest. I hope to do a in depth pytest write up soon.
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.