Django tests with nose and coverage

The following guide would help us run test using django-nose. It give us more option to run test either by apps, modules or even just individual tests. We will also include coverage to give us a test report locally whenever test is run.

First, we installed the required package and update requirements.txt.

$ pip install django-nose
$ pip install coverage
$ pip freeze > requirements.txt

Next, we need to update settings.py to setup some configuration required by django-nose.

INSTALLED_APPS = (
...
'django_nose', # Append to INSTALLED_APPS
...
)
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
NOSE_ARGS = [
    '--cover-erase',
    '--cover-package=MY_APP', # Change `MY_APP` to your `app` name
]

Next, we will setup coverage to display a report whenever python manage.py test is run. In the manage.py update with this:

if is_testing:
import coverage
cov = coverage.coverage(source=['app'], omit=['*/tests/*'])
cov.set_option('report:show_missing', True)
cov.erase()
cov.start()
# Add this 5 line above
execute_from_command_line(sys.argv)
# and add this 4 line below
if is_testing:
cov.stop()
cov.save()
cov.report()

The code here is asking coverage to report missing line that is not tested. The reason we are doing this manually instead of using a config file is because of a bug here.

By updating it to the above, whenever python manage.py test is run, we should see a nice report generated like this:

...................
--------------------------------------------------------------------
Ran 19 tests in 3.564s
OK
Destroying test database for alias 'default'...
Name                                     Stmts   Miss  Cover   Missing
--------------------------------------------------------------------
blog/__init__.py                                 0      0   100%
blog/admin.py                                   11      0   100%
blog/apps.py                                     3      3     0%   1-5
blog/forms.py                                    6      0   100%
blog/migrations/0001_initial.py                  9      0   100%
blog/migrations/0002_postimage.py                7      0   100%
blog/migrations/0003_auto_20170409_1343.py       5      0   100%
blog/migrations/0004_auto_20170410_2116.py       5      0   100%
blog/migrations/__init__.py                      0      0   100%
blog/models.py                                  31      3    90%   9, 37, 45
blog/tests.py                                  127      0   100%
blog/urls.py                                     3      0   100%
blog/views.py                                   65      2    97%   15, 58
--------------------------------------------------------------------
TOTAL                                          272      8   96%

If we are using CI, we could also generate a html or xunittest file. To get a html report, update the manage.py file to include this:

cov.save()
cov.html_report(directory='covhtml') # add this line
cov.report()

For generating a xunittest file, we have to update the NOSE_ARGS in settings.py to this:

NOSE_ARGS = [
'--cover-erase',
'--cover-package=create_tasksapp',
'--with-xunit', # Add this and the following line
'--xunit-file=xunittest.xml', # xunittest.xml could be any name
]

For more configuration, visit the following page:

The blogpost is also accessible here at my personal blog.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.