views:

218

answers:

2

I would like to have a python module containing some unit tests that I can pass to hg bisect --command.

The unit tests are testing some functionality of a django app, but I don't think I can use hg bisect --command manage.py test mytestapp because mytestapp would have to be enabled in settings.py, and the edits to settings.py would be clobbered when hg bisect updates the working directory.

Therefore, I would like to know if something like the following is the best way to go:

import functools, os, sys, unittest

sys.path.append(path_to_myproject)
os.environ['DJANGO_SETTINGS_MODULE'] = 'myapp.settings'


def with_test_db(func):
    """Decorator to setup and teardown test db."""
    @functools.wraps
    def wrapper(*args, **kwargs):
        try:
            # Set up temporary django db
            func(*args, **kwargs)
        finally:
            # Tear down temporary django db


class TestCase(unittest.TestCase):

    @with_test_db
    def test(self):
        # Do some tests using the temporary django db
        self.fail('Mark this revision as bad.')


if '__main__' == __name__:
    unittest.main()

I should be most grateful if you could advise either:

  1. If there is a simpler way, perhaps subclassing django.test.TestCase but not editing settings.py or, if not;
  2. What the lines above that say "Set up temporary django db" and "Tear down temporary django db" should be?
+3  A: 

You must use the internal Django TestCase to do so.

from django.test import TestCase

class TestCase(TestCase):

    # before every call to setUp(), the db is automatically 
    # set back to the state is was after the first syncdb then
    # all these fixture files will be loaded in the db   
    fixtures = ['mammals.json', 'birds']

    # put whatever you want here, you don't need to call the
    # super()
    def setUp(self):
        # Test definitions as before.
        call_setup_methods()

    def test(self):
        # Do some tests using the temporary django db
        self.fail('Mark this revision as bad.')

It's fully compatible with unittest so your code don't need to change much.

You can learn more about django.test, fixtures, flush and loaddata commands.

If you do want to use a decorator to do the job, you can use the call_command to use in your python program any django command. e.g :

from django.core.management import call_command

call_command('flush', 'myapp')
call_command('loaddata', 'myapp')
e-satis
If I put your code into a file called mytest.py, I still can't just run `python mytest.py` to run the unit tests, which is what I need.
blokeley
@blokeley: You have two obvious choices. The tests go in `models.py` or they go in `tests.py`. If you use `tests.py` instead of `mytest.py` you'll be happy.
S.Lott
+3  A: 

Cracked it. I now have one python file completely independent of any django app that can run unit tests with a test database:

#!/usr/bin/env python
"""Run a unit test and return result.

This can be used with `hg bisect`.
It is assumed that this file resides in the same dir as settings.py

"""

import os
from os.path import abspath, dirname
import sys
import unittest

# Set up django
project_dir = abspath(dirname(dirname(__file__)))
sys.path.insert(0, project_dir)
os.environ['DJANGO_SETTINGS_MODULE'] = 'myproject.settings'

from django.db import connection
from django.test import TestCase
from django.test.utils import setup_test_environment, teardown_test_environment

from myproject import settings
from myproject.myapp.models import MyModel


class MyTestCase(TestCase):

    def test_something(self):
        # A failed assertion will make unittest.main() return non-zero
        # which if used with `hg bisect` will mark the revision as bad
        self.assertEqual(0, len(MyModel.objects.all())) # and so on


if '__main__' == __name__:
    try:
        setup_test_environment()
        settings.DEBUG = False    
        verbosity = 0
        old_database_name = settings.DATABASE_NAME
        connection.creation.create_test_db(verbosity)
        unittest.main()
    finally:
        connection.creation.destroy_test_db(old_database_name, verbosity)
        teardown_test_environment()
blokeley
+1 for posting the final solution.
e-satis
Thanks. I hope that this shows others how to set up the django test database for any arbitrary unit tests, including nose tests.
blokeley