Skip to content

Marina Mele's site

Reflections on family, values, and personal growth

Menu
  • Home
  • About
Menu

Tools for Testing in Django: Nose, Coverage and Factory Boy

Posted on March 19, 2014September 26, 2014 by Marina Mele

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?

These are some of the reasons why you should try Nose for testing:
  • 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

Testing-terminal2

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 Melé
Marina Mele

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)

Leave a Reply Cancel reply

You must be logged in to post a comment.

Categories

  • Personal Growth and Development
  • Artificial Intelligence
  • Mindful Parenting and Family Life
  • Productivity and Time Management
  • Mindfulness and Wellness
  • Values and Life Lessons
  • Posts en català
  • Other things to learn

Recent Posts

  • BlueSky Social – A Sneak Peek at the Future of Social Media
  • The Incredible Journey of AI Image Generation
  • AI and Fundamental Rights: How the AI Act Aims to Protect Individuals
  • Overcoming Regrets: Finding the Strength to Move Forward
  • Thinking Outside the Box: Creative Problem-Solving with Critical Thinking

RSS

  • Entries RSS
Follow @marina_mele
  • Cookie Policy
  • Privacy Policy
©2023 Marina Mele's site | Built using WordPress and Responsive Blogily theme by Superb
This website uses cookies to improve your experience. If you keep navigating through this website, we'll assume you're ok with this, but you can opt-out if you wish.Accept Read More
Privacy & Cookies Policy

Privacy Overview

This website uses cookies to improve your experience while you navigate through the website. Out of these, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may affect your browsing experience.
Necessary
Always Enabled
Necessary cookies are absolutely essential for the website to function properly. This category only includes cookies that ensures basic functionalities and security features of the website. These cookies do not store any personal information.
Non-necessary
Any cookies that may not be particularly necessary for the website to function and is used specifically to collect user personal data via analytics, ads, other embedded contents are termed as non-necessary cookies. It is mandatory to procure user consent prior to running these cookies on your website.
SAVE & ACCEPT