Skip to: Site menu | Main content



Django Unittesting on Dreamhost

Unit testing. It’s important. We all know that, even if we don’t practice what we preach. There was zero policy on unit testing at my last job, but now that I have started working at VendAsta, there is plenty of support for proper testing at the highest levels. Now that I am getting back into the habit of writing unit tests for my code at work I have been starting to focus on proper testing for my code at home. Combining Django and hosting with Dreamhost has proven to be a bit of a challenge.

The first hurdle is actually getting a Django site running on Dreamhost. Luckily, there is this blog post which details exactly how to get your site up and running on Dreamhost. I have used this method a couple of times (once modified to get a Satchmo install running) and have met with success each time.

Once the site is running, setting up tests poses another problem. The default test behaviour for a Django site creates and then destroys a whole test database on each test run. Normally this is not a problem, but the way Dreamhost has their MySQL setup running, the commands to create and drop databases are not available outside of their administration panel. What follows is the solution I have come up with and am currently using to run Django unittests with my Dreamhost MySQL databases.

First of all, it is important to understand a little about the default testing setup that Django uses. When you run the command python manage.py test, Django looks at the TEST_RUNNER setting to determine what to do. By default this setting points to django.test.simple.run_tests. A quick look and we can see that this is the functionality we want to alter. Specifically, lines 2 and 4 shown in listing #1 are what we need to replace.

1
2
3
4
from django.db import connection
connection.creation.create_test_db(verbosity, autoclobber=not interactive)
result = unittest.TextTestRunner(verbosity=verbosity).run(suite)
connection.creation.destroy_test_db(old_name, verbosity)
Listing 1: Excerpt from django.test.simple.run_tests

It is these lines which make the calls to create and then drop the test database. The functionality needed, however, in order to work with Dreamhost’s setup, is to “flush” and existing test database both before and after the test run is complete. I have accomplished this through the creation of a method called flush_test_db, shown in Listing #2.

1
2
3
4
5
6
7
8
9
10
11
def flush_test_db(test_db):
    """Method to flush out the test database, removing tables, before
    and after test runs"""
 
    from django.db import connection 
    cursor = connection.cursor() 
 
    current_tables =  connection.introspection.table_names()
    for table in current_tables:
        cursor.execute("use %s" % test_db)
        cursor.execute("drop table %s" % table)
Listing 2: New method to remove all tables from the test db

With this method, we can drop all of the tables in the test db both before and after a test run. It is necessary to do both just in case something goes horribly awry during a test run and we end up with leftover data from a previous test run. With this new method, we can then create a new version of the run_tests method and place both methods into a new file. I called my file test_runner.py and placed it in a tests directory within my app. The complete test_runner.py is shown in Listing 3.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
"""Override of the simple tests module from django in order to skip
database creation and use an existing db"""
import unittest
from django.conf import settings
from django.db.models import get_app, get_apps
from django.db.backends.creation import TEST_DATABASE_PREFIX
from django.test import simple
from django.test.utils import setup_test_environment, teardown_test_environment
from django.core import management 
 
def flush_test_db(test_db):
    """Method to flush out the test database, removing tables, before
    and after test runs"""
 
    from django.db import connection 
    cursor = connection.cursor() 
 
    current_tables =  connection.introspection.table_names()
    for table in current_tables:
        cursor.execute("use %s" % test_db)
        cursor.execute("drop table %s" % table)
 
def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[]):
    """
    Replacement for the run_tests in Django.
    """
    setup_test_environment()
 
    settings.DEBUG = False    
    suite = unittest.TestSuite()
 
    if test_labels:
        for label in test_labels:
            if '.' in label:
                suite.addTest(simple.build_test(label))
            else:
                app = get_app(label)
                suite.addTest(simple.build_suite(app))
    else:
        for app in get_apps():
            suite.addTest(simple.build_suite(app))
 
    for test in extra_tests:
        suite.addTest(test)
 
    old_name = settings.DATABASE_NAME
 
    if settings.TEST_DATABASE_NAME:
        test_database_name = settings.TEST_DATABASE_NAME
    else:
        test_database_name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
 
    #replace the normal DATABASE_NAME with the name of the test db
    settings.DATABASE_NAME = test_database_name
 
    #flush the test db
    flush_test_db(test_database_name)
    #load the test db with models
    management.call_command('syncdb')
 
    result = unittest.TextTestRunner(verbosity=verbosity).run(suite)
 
    #flush the test db again
    flush_test_db(test_database_name)
 
    #reset the value of DATABASE_NAME in settings
    settings.DATABASE_NAME = old_name
    teardown_test_environment()
 
    return len(result.failures) + len(result.errors)
Listing 3: The complete test_runner.py

The final step is to point the test framework to your new test runner by placing a new line in your settings.py. The exact value will vary based on where in your project you have placed the new test runner. In this example I have placed the test_runner.py file, containing the run_tests method in the tests directory of an app called ‘your_app’. This is shown in listing 4.

1
TEST_RUNNER = 'your_app.tests.test_runner.run_tests'
Listing 4: Settings.py setting pointing the test framework towards your new test_runner

Finally a few words of caveat. I have omitted any comments I am using to tell pylint to ignore certain things and I have done little in this sample code to properly handle Exceptions that might arise. Nevertheless, this should get you a good start on running unit tests in Django if you host your site with Dreamhost.

One Response to “Django Unittesting on Dreamhost”

  1. if(is_geek)… » Blog Archive » Splitting Django Models Into Separate Files Says:

    [...] little while ago I wrote a post about getting Django unittests to work on DreamHost. The particular problem with the DreamHost setup is that they don’t allow you to [...]

Leave a Reply