Introduction to Functional Web Testing With Twill & Selenium

Part 1 :: Extra Time :: Tags

Synopsis

Tagging allows you to organize and run subsets of your tests. As I mentioned before in the section on the differences between UnitTests and nose-style tests, there are some subtle differences between the two methods, and test organization is one of them. Nose provides a very simple and robust tagging system that allows you to easily cherry-pick the tests you want to run.

Nose Tags

Let's say we have the following four tests:

This will be 'test_one_fish.py:'

              def test_one_fish():
                  print "I'm the one fish test."


              test_one_fish.tags = ["number", "one"]
            

This will be 'test_two_fish.py:'

              def test_two_fish():
                  print "I'm the two fish test."


              test_two_fish.tags = ["number", "two"]
            

This will be 'test_red_fish.py:'

              def test_red_fish():
                  print "I'm the red fish test."


              test_red_fish.tags = ["color", "red"]
            

This will be 'test_blue_fish.py:'

              def test_blue_fish():
                  print "I'm the blue fish test."


              test_blue_fish.tags = ["color", "blue"]
            

You'll notice that in each test case, the test function itself has a tags attribute that's a list of strings. We can use that attribute to tell nose that we only want to run tests that have a given tag. Let's start by running them all; this will look familiar:

              [terryp@tpmacbook] sample_tags :: nosetests 
              ....
              ----------------------------------------------------------------------
              Ran 4 tests in 0.008s

              OK
            

Let's turn on a little verbosity:

              [terryp@tpmacbook] sample_tags :: nosetests -v 
              test_blue_fish.test_blue_fish ... ok
              test_one_fish.test_one_fish ... ok
              test_red_fish.test_red_fish ... ok
              test_two_fish.test_two_fish ... ok

              ----------------------------------------------------------------------
              Ran 4 tests in 0.007s

              OK
            

Okay, now let's get tricky. Let's only run tests that we tagged as 'numbers.' If we tagged everything correctly, that'll be 'test_one_fish.py' and 'test_two_fish.py.' The easiest way to do this with nose is by using the -a flag ('a' is for 'attribute'):

              [terryp@tpmacbook] sample_tags :: nosetests -v -a tags=number
              test_one_fish.test_one_fish ... ok
              test_two_fish.test_two_fish ... ok

              ----------------------------------------------------------------------
              Ran 2 tests in 0.006s

              OK
            

We can do the same thing if we want to run the tests that have colors. That should be 'test_red_fish.py' and 'test_blue_fish.py':

              [terryp@tpmacbook] sample_tags :: nosetests -v -a tags=color
              test_blue_fish.test_blue_fish ... ok
              test_red_fish.test_red_fish ... ok

              ----------------------------------------------------------------------
              Ran 2 tests in 0.007s

              OK
            

There are other ways to tag tests, too. You can add one line to each test module for each tag you want to use:

              def test_blue_fish():
                  print "I'm the blue fish test."


              test_blue_fish.color = True
              test_blue_fish.blue = True
            

This has the advantage of letting you use tags as negative descriptors, but obviously it results in a lot of boilerplate code if you're using a lot of tags. The way to get around that is to write a simple decorator function:

              def tag(*tags):
                  def wrap(func):
                      for tag in tags:
                          setattr(func, tag, True)
                      return func

                  return wrap
            

Now you can do this:

              @tag('color', 'blue')
              def test_blue_fish():
                  print "I'm the blue fish test."
            

Which, in turn, allows you to do this:

              [terryp@tpmacbook] sample_tags :: nosetests -v -a '!color'
              test_blue_fish.test_one_fish ... ok
              test_red_fish.test_two_fish ... ok

              ----------------------------------------------------------------------
              Ran 2 tests in 0.007s

              OK
            

You just ran the tests that you did not have tagged with 'color.'

A few things to note about this approach. First, because you're setting the tags themselves as attributes, you run them with simply nosetests -a $tagname, instead of with nosetests -a tags=$tagname. Second, to use them negatively, you have to use single quotes (otherwise the shell will interpret that !). Third, if you combine the tagging decorator with test generators, be sure to put the decorator on the generator, and not the yielded function.

And there you have it: tagging, nose-style.

Fluid 960 Grid System, created by Stephen Bau, based on the 960 Grid System by Nathan Smith. Released under the GPL/ MIT Licenses.