Django Unittesting on Dreamhost
December 27th, 2008
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.



January 26th, 2009 at 10:16 pm
[...] 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 [...]