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)