Wednesday, August 25, 2010

How Python's unittest library figures out what functions to run..

Django's django.test.TestCase ultimately inherits from the Python unittest library, so I was curious to figure out how Python actually discerns what test functions to run. For instance, how does unittest.TestCase() ultimately know that there are two test methods (test_details(), and test_index()) to invoke?
from django.test import TestCase

class SimpleTest(TestCase):
    def test_details(self):
        response = self.client.get('/customer/details/')
        self.failUnlessEqual(response.status_code, 200)

    def test_index(self):
        response = self.client.get('/customer/index/')
        self.failUnlessEqual(response.status_code, 200)
Well, we can start by tracing into the django.test.TestCase code:

class TestCase(TransactionTestCase):


class TransactionTestCase(unittest.TestCase):

.. (Python library):

class TestCase:
    """A class whose instances are single test cases.

    By default, the test code itself should be placed in a method named

When you invoke python test, Django calls django.test.simple.DjangoTestSuiteRunner, which inherits from unittest.TextTestRunner. Inside, there is this following line:


The actual instantiation is declared inside

defaultTestLoader = TestLoader()

The loadTestsfromModule() first get invokes, which in turn invokes getTestCaseNames():
def loadTestsFromTestCase(self, testCaseClass):
        """Return a suite of all tests cases contained in testCaseClass"""
        if issubclass(testCaseClass, TestSuite):
            raise TypeError("Test cases should not be derived from TestSuite. Maybe you meant to derive from TestCase?")
        testCaseNames = self.getTestCaseNames(testCaseClass)
        if not testCaseNames and hasattr(testCaseClass, 'runTest'):
            testCaseNames = ['runTest']
        return self.suiteClass(map(testCaseClass, testCaseNames))

The method getTestCaseNames() that searches for all methods that begin with "test_" and are callable:
def getTestCaseNames(self, testCaseClass):
        """Return a sorted sequence of method names found within testCaseClass
        def isTestMethod(attrname, testCaseClass=testCaseClass, prefix=self.testMethodPrefix):
            return attrname.startswith(prefix) and callable(getattr(testCaseClass, attrname))
        testFnNames = filter(isTestMethod, dir(testCaseClass))
        for baseclass in testCaseClass.__bases__:
            for testFnName in self.getTestCaseNames(baseclass):
                if testFnName not in testFnNames:  # handle overridden methods
        if self.sortTestMethodsUsing:
        return testFnNames
So if you invoked the method directly through the Python interpreter, you can see how this function works:

>>> import unittest
>>> b = unittest.TestLoader()
>>> b.getTestCaseNames(SimpleTest)
['test_details', 'test_index']

No comments:

Post a Comment