Stubblog


Refactoring Django Apps Part 2, Unit Tests
February 20, 2010, 7:34 pm
Filed under: django | Tags: , ,

In the last post, I looked at moving all your data from one app to another, and today I’m going to go through using Django’s unit test extensions to test the app. This post also covers using signals to validate models that arn’t created from a form. You should of course also unit test the methods on your models outside of the views they’re called from, but there’s nothing special there, and there’s a whole bunch of blog posts that cover them.

The main aim of whos-playing.com is to make organising 5-a-side matches easy, so it’s main features are all around players saying they can or can’t make the game. A player can say they’ll play, but then change their mind, they can only play in matches for teams they are a member of, and they can only say THEY will play, not anyone else, the exception to this being the team organiser, he can add or remove any player. Lets look at the various things we might want to test.

  • A player must be logged in to play
  • A Player can say they will play
  • A player can say they can’t play
  • A player can not add another player
  • A player must be a member of the team to play
  • The organiser can add or remove whoever he likes, as long as they play for his team

Create a file called tests.py in your app folder, and import the test client & TestCase object, don’t forget your apps models too:

from django.test.client import Client
from django.test import TestCase
from match.models import *

Next, I rough out the tests I came up with:

class TestMatch(TestCase):
    def testUnAuthUsersCantPlay(self):
        """Check that a user must be logged in to play."""
        self.fail()

    def testICanPlay(self):
        """Check that a user can play."""
        self.fail()

    def testICantPlay(self):
        """Check that a user can remove themselves from 
           the list of players."""
        self.fail()

    def testPlayAnother(self):
        """Check that a user can play."""
        self.fail()

    def testIsTeamMember(self):
        """Check that a user can play."""
        self.fail()

    def testOrganiser(self):
        """Check that a user can play."""
        self.fail()

Run the tests and you’ll see 6 tests that all fail ,because we told them to.

./manage.py test match

Django provides you with a basic client to test your views with, and while it’s not a substitute for Selenium it’s perfect for testing simple models like this that are only really called via your views.

Lets write our test for un-authorised users first. In my app, it redirects them to the login page:

def testUnAuthUsersCantPlay(self):
    m = Match.objects.get(pk=1)

    response = self.client.get('/match/1/play/')
    self.assertRedirects(response, '/login/')

    # Check the list of players is still empty.
    self.assertEqual(len(m.get_players()), 0)

First we create an instance of the match (we loaded this from fixtures), then we use the client instance that automatically gets created when we run the tests to call the view we set up for marking a player as playing, we know it should redirect to the login page so we use one of Django’s extensions to the TestCase class to check for that too.

Finally, just for sanity we check that there are no players registered against this match. Quite how that might happen, I can’t imagine, but there’s no harm in testing for it.

As well as letting you test your url config and test your views, it also provides a login method that you can call, as long as you are using Django’s standard auth module that is.

This lets us test the other parts of the match model, the bits where you need to be logged in. I’m going to write a test to match sure that only players that are members of the team can play.

def testNonMembersCantPlay(self):
    """Check that non-team members can't play."""
    m = Match.objects.get(pk=1)
    
    login = self.client.login(username='test_user2', 
        password='password')
    self.assertTrue(login)

    response = self.client.get('/match/%s/play' % m.id, 
        follow=True)

    self.assertRedirects(response, '/match/%s/' % m.id, 
        status_code=301)
    self.assertContains(response, "You must be a 
        member of the team to play in this match")

So what’s going on here? We get the same instance of the match from the database, remember that it’s loaded from the fixtures fresh for each test, so anything you’ve done to it in previous tests. I’ve also loaded the database with some players, and matched some of them to the team, test_user2 is not one of them, so shouldn’t be able to play.

self.client.login logs the user in, and returns True if it was succesful, which lets be honest, it should be.

Notice on the assertRedirects call this time we also specify the response code we expect as well as the url.

Finally, we check that the page we get redirected contains the error message we expect to see if that user isn’t a member of the team.

The other tests are all a variation on these calls, we get the match, log the user in and test verious things by calling in to Django’s views.

About these ads

Leave a Comment so far
Leave a comment



Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s



Follow

Get every new post delivered to your Inbox.

%d bloggers like this: