Monday, February 2, 2009

organizing django unit tests

I've had a revelation regarding my unit test organization.

Until recently, I was creating test case subclasses along module boundaries, and defining large numbers of tests within each case to exercise the functionality of that module. For example:


class ModelTestCase(TestCase):
def test_some_model_behavior(self):
MyModel.objects.create(name='foo')

def test_some_other_model_behavior(self):
foo = MyModel.objects.get(pk=1)
MyOtherModel.objects.create(other=foo)


Now, we've ensured that all of the setup for ModelTestCase is performed before OtherModelTestCase, and we can safely sequence each step of a process while testing each step without a lot of copy and paste code. Admittedly, super is kind of tedious, but that is another post on another day.

This worked ok for a while, but eventually, my applications would grow to the point where the cases are not simple gets and creates, but rather entire stories about how a potential user navigates the site.

Consider unit testing a user registration and login, for example. Each of the steps, from getting the registration form to activating the account, and finally logging in needs testing. The real headache comes with trying to organize all those tests so they run in the correct order. One, certainly valid, approach would be to simply put the entire story in a single test. However, this left me repeating certain setup code, and if there's one thing for which I have no patience, it is violations of the DRY principle.

My solution, subclassing test cases to enforce order.


class ModelTestCase(TestCase):
def setUp(self):
self.foo=MyModel.objects.create(name='foo')

def runTest(self):
self.assertEqual(self.foo.name, 'foo')

class OtherModelTestCase(ModelTestCase):
def setUp(self):
super(OtherModelTestCase, self).setUp()
self.bar=self.foo.bar.latest()

def runTest(self):
self.failUnless(self.bar)


Now, I am assured that the ModelTestCase setup has been performed before OtherModelTestCase is run. This allows me to easily sequence steps of a process (like user registration for example, view, submit, email, activate) without having to repeat a lot of code. Admittedly, having to call super all over the place is tedious, but that is another post on another day.

No comments: