In this post, you will learn how to install and use Nose, a tool for running your tests in Django.
You’ll learn how to run and configure Coverage, so that it reports only the packages and directories of interest.
And also, you’ll learn how to install and use Factory Boy in your tests (an alternative to Fixtures).
If you want to learn more about testing, check this post about Django testing: Doctests and Unittests.
First of all, why test with Nose?
- Extends the functionalities of unittest
- It tests your apps by default, and not all the standard ones that are included in the INSTALLED_APPS of your settings file
- It detects all the functions and classes that start with Test or test that you have defined in your code, so no more worrying about how can I make my tests visible to the Test Suite?
- You can test specific apps, folders, files or tests
- You can use all the nose plugins
- And much more!! Comment if you think I forgot something important! 😉
Let’s start with Django Nose!
First, activate your testing environment and install nose and django-nose via pip:
$ pip install nose
$ pip install django-nose
don’t forget to add them in your requirements file for the testing environment. Then, add django-nose to the installed apps in your testing settings.py file:
INSTALLED_APPS = (
  …
  ‘django_nose’,
  …
)
Note: if you use South, make sure you include it before django_nose in the installed apps. This is because South installs its own test command that turns off migrations during testing. This way, you make sure that the django_nose’s test command is used.
In case you have a testing settings file that inherits from another settings file that contains all your installed apps, you can add this package by:
from .settings import *
INSTALLED_APPSÂ += (‘django_nose’,)
Finaly, you have to indicate Django to use the Nose test runner. In the test settings file add:
TEST_RUNNER = ‘django_nose.NoseTestSuiteRunner’
Save and make sure everything is installed properly by running
$ python manage.py test
If you get an error like “permission denied to create database” check this post on Django testing.
Coverage Library
This library is an amazing tool that tells you which parts of your code are covered by tests. It is nicely integrated with django-nose, so to install it, just run:
$ pip install coverage
Then, you can run coverage with
$ python manage.py test –with-coverage
If you want to run your tests with this default behavior, open your test settings file and add the following:
NOSE_ARGS = [
  ‘–with-coverage’,
  ‘–cover-package=myapp1, myapp2’,
  ‘–cover-inclusive’,
]Â
The second option, –cover-package indicates the packages to cover with coverage. In this case we cover two apps, myapp1 and myapp2 (these names are the ones you use in INSTALLED_APPS). And the third option, –cover-inclusive, indicates coverage to scan all the files in the working directories (this is useful to find files that are not being tested).
After this configuration, then you run
$ python manage.py test
only the packages myapp1 and myapp2 will be tested, and you’ll see the corresponding coverage report. However, if you are using South, this report includes all your migration files, and your overall cover score will be affected.
In order to omit these files, create a file in the root directory (at the same level of manage.py) named .coveragerc and write the following:
[run]
omit = ../*migrations*
Run your tests again and you’ll see that the migrations don’t show in the coverage report 🙂
If you change the verbosity displayed in your tests, by adding -v 3
$ python manage.py test -v 3
you’ll see that every time you run the tests, a new database is created and then all the South fixtures are applied. This can take some time, so if you want to prevent this from happening, there are two different ways:
- Create and reuse a database for testing. With this method, you can create a database for testing, and instead of deleting it when all the tests are done, it remains there to be reused every time the tests are run. You can find more information in this post.
- Disable South migrations in your tests. With this method, when the database is created, instead of running manage.py migrate in the apps managed by South, it runs directly manage.py syncdb. To use this method, open the settings.py file you use for testing and add:
      SOUTH_TESTS_MIGRATE = False
Now your tests will run faster!
Factory Boy
As recommended by the 2 Scoops of Django book (amazing Best practices book!) fixtures are hard to maintain as the project evolves and data structures change. One alternative they recommend is the library factory_boy, which helps in the creation of models in your tests.
To install it, use pip:
$ pip install factory_boy
And that’s it! 🙂
Now, let’s look an example of a test that uses some Nose tools and a Factory Model! First, we create a model in models.py:
File: marinamele_models_tests_nose_factory.py
---------------------------------------------
# -*- coding: utf-8 -*-
from django.db import models
from django.utils.translation import ugettext_lazy as _
class House(models.Model):
location = models.CharField(
max_length=200,
verbose_name=_("Location"),
help_text=_("Enter the location of the house"),
)
rooms = models.PositiveSmallIntegerField(
verbose_name=_("Number of rooms"),
help_text=_("Enter the number of rooms of the House"),
)
balkon = models.BooleanField(
verbose_name=_("Has balkon"),
help_text=_("Enter if the house has a balkon or not"),
)
class Meta:
verbose_name = _('House')
verbose_name_plural = _('Houses')
ordering = ["location"]
def __unicode__(self):
return _("House located at %s") % self.location
Next, we create the tests for that model:
File: marinamele_nose_factory_test.py
-------------------------------------
# -*- coding: utf-8 -*-
import nose.tools as nt
import factory
from django.test import TestCase
from myapp.models import House
class HouseFactory(factory.DjangoModelFactory):
FATORY_FOR = House
location = "Barcelona"
rooms = 3
class TestHouseCreation(TestCase):
def setUp(self):
self.house1 = HouseFactory.create(balkon=True)
def tearDown(self):
self.house1.delete()
def test_house_created(self):
nt.eq_(self.house1.location, "Barcelona")
nt.eq_(self.house1.rooms, 3)
nt.eq_(self.house1.balkon, True)
def test_second_house_created(self):
house2 = HouseFactory.create(location="Hannover", balkon=False)
self.assertNotEqual(self.house1.location, house2.location)
self.assertEqual(self.house1.rooms, house2.rooms)
self.assertNotEqual(self.house1.balkon, house2.balkon)
house2.delete()
Note that:
- We create a model with 3 different mandatory fields: location, rooms and balkon.
- In the test file, we create the class HouseFactory, which creates instances of a House every time is called.
- By default, HouseFactory creates a house in Barcelona with 3 rooms. But as balkon does not have any default value, we need to specify it when we create the new instance.
- We can override any default value by specifying it when a new instance is created.
- Nose provides extra tools for testing. Here we show the function eq_, which may not be very interesting, as it is equivalent to assertEqual… 🙂 Check out other functions in nose.tools here.
- And check out the extra tools for testing that provides django-nose here.
Detect Test mode in your functions
Sometimes, you might want to execute a custom function when you’re in the testing environment. For example, when doing API calls.
In order to detect whether you’re in a testing environment, you can introduce a variable in your settings.py file:
import sys
TESTING =Â ‘test’ in sys.argv
so that the variable TESTING will be true when the command line arguments include the word test.
Then, you can call this variable from anywhere in your code with
from django.conf import settings
if settings.TESTING:
  …
Now it’s time you create your tests!
+1 if this was helpful! 😉
Marina Mele has experience in artificial intelligence implementation and has led tech teams for over a decade. On her personal blog (marinamele.com), she writes about personal growth, family values, AI, and other topics she’s passionate about. Marina also publishes a weekly AI newsletter featuring the latest advancements and innovations in the field (marinamele.substack.com)