Introduction to Functional Web Testing With Twill & Selenium

Part 1 :: Extra Time :: Generative Tests

Synopsis

Write lots of tests with as little code as possible.

Nose Generative Tests

One of the key benefits of nose is the concept of test generators. Py.test has the same type of feature, but calls them generative tests.

nose on test generators:

nose will iterate the generator, creating a function test case wrapper for each tuple it yields. As in the example, test generators must yield tuples, the first element of which must be a callable and the remaining elements the arguments to be passed to the callable.

Py.test on generative tests:

Generative tests are test methods that are generator functions which yield callables and their arguments. This is most useful for running a test function multiple times against different parameters.

The concept is to iterate over an iterable and yield up the results to another function. [Sidenote: David Beazley's PyCon 2008 tutorial on Generator Tricks for System Programmers is great.] Simple nose test generator example:

              def test_numbers():
                  for n in [2, 4, 6, 8, 10]:
                      yield n_greater_than_zero, n

              def n_greater_than_zero(n):
                  assert n > 0
            

Here's the output:

              nosetests -v examples/generative/
              test_generative_toy.test_numbers(2,)  ... ok
              test_generative_toy.test_numbers(4,)  ... ok
              test_generative_toy.test_numbers(6,)  ... ok
              test_generative_toy.test_numbers(8,)  ... ok
              test_generative_toy.test_numbers(10,) ... ok
              ---------------------------------------------
            Ran 5 tests in 0.004s OK
            

Five tests in six lines of code!

Now, when you use test generators in a functional testing case, it can get even cooler. Here's another simple example using nose and twill:

              import twill.commands as tc

              def test_sites():
                  for site in ["http://www.yahoo.com/",
                                 "http://www.microsoft.com"]:
                      yield site_is_evil, site

              def site_is_evil(site):
                  tc.go(site)
                  assert evil not in tc.get_browser().get_html()
            

I was reworking some older functional tests and noticed that I was using a lot of twill's 'clear_cookies()' function and was trying to figure out a way to reduce the silly number of times I was using it; nose includes a nifty little module called nose.tools, which has a function called 'with_setup.' Back to the nose docs:

              with_setup(setup=None, teardown=None)

              #Decorator to add setup and/or teardown methods to a test function:

              @with_setup(setup, teardown)
              def test_something():
                  # ...

              #Note that with_setup is useful only for test functions,
              #not for test methods or inside of TestCase subclasses.
            

Using it to initialize the twill browser is really quite handy, especially if you're functionally testing lots of sites that are using cookies or sessions. A test like this -- unless you write around it -- uses one and only one instance of the twill browser class, and it'll therefore store session and cookie information unless you explicitly clear it out. Another way to do that is by using nose.tool's 'with_setup':

              from nose.tools import with_setup
              import twill.commands as tc

              def setup():
                  print "Clear our the cookie jar."
                  tc.clear_cookies()

              def test_sites():
                  for site in ["http://www.yahoo.com/",
                                 "http://www.microsoft.com"]:
                      yield site_is_evil, site

              @with_setup(setup)
              def site_is_evil(site):
                  tc.go(site)
                  assert "evil" not in tc.get_browser().get_html()
            

Notice how the 'with_setup' decorator is attached to the 'site_is_evil' function -- not the test generator, 'test_sites.' Now, when you run the test generator you'll get a clean version of the twill browser for each item in the iterable in the test generator. It's nifty and helpful.

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