Code Coverage for Django 1.1

Last week I wanted to implement code coverage for my Django application. So, I did what I normal do; a Google search. I found an old articles and alternate method. I worked with some old code that seemed to work for Django <1.0. I modified it so it now works with Django 1.1. I hope this will be useful to someone else.

With the current configuration the test runner will output the code coverage percentages and generate HTML reports displaying the code covered and code not covered.

Output to the console will look similar to this:

Ran 95 tests in 113.249s

FAILED (errors=19)
Name                   Stmts   Exec  Cover
------------------------------------------
myApp.datamethods     479    243    50%
myApp.methods         150     97    64%
myApp.models          432    335    77%
myApp.signals          17     17   100%
myApp.urls              4      4   100%
myApp.views           938    311    33%
------------------------------------------
TOTAL                   2020   1007    49%
 Destroying test database...

You will need coverage.py and coverage_color.py for this to work. These should be placed somewhere on your Python path. I included the files below just in case. I also included the test runner code: tests_with_coverage.py, which should be placed in your django project directory.

In order for this test runner to function you will also need to modify your settings.py to include two constants:

  • TEST_RUNNER points to the location of the tests_with_coverage.py test runner.
  • COVERAGE_MODULES is a list of the modules used in the coverage analysis.

settings.py

TEST_RUNNER = 'myApp.tests_with_coverage.run_tests'
COVERAGE_MODULES = ['myApp.models', 'myApp.views', 'myApp.datamethods', 'myApp.methods', 'myApp.signals', 'myApp.urls']
 

Run your tests as normal using:

./manage.py test myApp

The code below was based on the code found here: http://blogs.23.nu/c0re/2007/07/antville-15428/

tests_with_coverage.py

import unittest, os, coverage, coverage_color
from django.conf import settings
from django.db.models import get_app, get_apps
from django.test.testcases import OutputChecker, DocTestRunner, TestCase
 from django.test.utils import setup_test_environment, teardown_test_environment
from django.test.simple import build_test, reorder_suite, build_suite

def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[]):
 
    coveragemodules = []
    if hasattr(settings, 'COVERAGE_MODULES'):
        coveragemodules = settings.COVERAGE_MODULES
    if coveragemodules:
        coverage.start()
    
    setup_test_environment()
     
    settings.DEBUG = False
    suite = unittest.TestSuite()
     
    if test_labels:
        for label in test_labels:
            if '.' in label:
                suite.addTest(build_test(label))
             else:
                app = get_app(label)
                suite.addTest(build_suite(app))
    else:
        for app in get_apps():
            suite.addTest(build_suite(app))

    for test in extra_tests:
         suite.addTest(test)

    suite = reorder_suite(suite, (TestCase,))
    
    from django.db import connection
    old_name = settings.DATABASE_NAME
    connection.creation.create_test_db(verbosity)
     result = unittest.TextTestRunner(verbosity=verbosity).run(suite)
    if coveragemodules:
        coverage.stop()
        coveragedir = './build/coverage'
        if hasattr(settings, 'COVERAGE_DIR'):
             coveragedir = settings.COVERAGE_DIR
        if not os.path.exists(coveragedir):
            os.makedirs(coveragedir)
        modules = []
        for module_string in coveragemodules:
            module = __import__(module_string, globals(), locals(), [""])
             modules.append(module)
            f,s,m,mf = coverage.analysis(module)
            fp = file(os.path.join(coveragedir, module_string + ".html"), "wb")
            coverage_color.colorize_file(f, outstream=fp, not_covered=mf)
             fp.close()
        coverage.report(modules, show_missing=0)
        coverage.erase()
    connection.creation.destroy_test_db(old_name, verbosity)
    
    teardown_test_environment()
    
     return len(result.failures) + len(result.errors)

Click here to download:
coverage.py (43 KB)

Click here to download:
coverage_color.py (5 KB)

Click here to download:
tests_with_coverage.py (2 KB)