Introduction to Functional Web Testing With Twill & Selenium

Part 1 :: Writing Twill Tests :: UnitTest v. NoseTest?

Synopsis

Python ships with a standard module unittest. Per the Python docs:

The Python unit testing framework, sometimes referred to as “PyUnit,” is a Python language version of JUnit, by Kent Beck and Erich Gamma. JUnit is, in turn, a Java version of Kent’s Smalltalk testing framework. Each is the de facto standard unit testing framework for its respective language.

unittest supports test automation, sharing of setup and shutdown code for tests, aggregation of tests into collections, and independence of the tests from the reporting framework. The unittest module provides classes that make it easy to support these qualities for a set of tests.

Unittest is great, but -- as you'll see -- requires a lot of boiler plate code to get up and running. In fact, I would argue that unittest kind of gets in the way of functional testing. Luckily, there's nose.

UnitTest v. NoseTest comparison

Doug Hellmann's PyMOTW has an excellent article on Python's unittest. Before we dive in, let's make sure you're setup:

              [terryp@tpmacbook] ~ :: python
              Python 2.5.4 (r254:67917, Dec 23 2008, 14:57:27)
              [GCC 4.0.1 (Apple Computer, Inc. build 5363)] on darwin
              Type "help", "copyright", "credits" or "license" for more information.
              >>> import unittest
              >>> print unittest.__version__
              1.63
              >>>
            

Awesome. All set. Now let's write a simple test called 'unittest_toy.py:'

              import unittest

              class ToyTest(unittest.TestCase):
                  def test(self):
                      self.assertTrue(True)


              if __name__ == "__main__":
                  unittest.main()
            

OK, now let's run it:

              [terryp@tpmacbook] code :: python unittest_toy.py 
              .
              ----------------------------------------------------------------------
              Ran 1 test in 0.000s

              OK
            

We get a '.' that says we ran one successful test! Chad Whitacre, when on the Testing Panel at PyCon 2007 in Dallas, summarized the significance of dots when he said, "I'm addicted to dots." When I first heard that, I kind of didn't get it, but when you see a screen full of dots there's something incredibly satisfying about it.

So now that we've examined the basics of UnitTest, let's contrast the simple test case we've written with Nose. Nose, although fully compatible with unittest, has a slightly different approach to running tests. Let's rewrite 'unittest_toy.py' as a nose test, 'test_toy.py.'

              
              def test_toy():
                  assert True
            

Yep, that's really it. No imports, no classes, no subclasses, no boilerplate code. Okay, let's run it.

              [terryp@tpmacbook] code :: nosetests test_toy.py 
              .
              ----------------------------------------------------------------------
              Ran 1 test in 0.001s

              OK
            

Took a millisecond longer, but as you can see, we get the same output. Nose, we think, lowers the barrier to writing tests. You don't have to worry about syntax as much, and you can just start testing code quickly without thinking too much about it.

Nose, however, does have a few caveats that you have to follow to make compatible tests:

  • If it looks like a test, it's a test. Names of directories, modules, classes and functions are compared against the testMatch regular expression, and those that match are considered tests. Any class that is a unittest.TestCase subclass is also collected, so long as it is inside of a module that looks like a test.
  • Directories that don't look like tests and aren't packages are not inspected.
  • Packages are always inspected, but they are only collected if they look like tests. This means that you can include your tests inside of your packages (somepackage/tests) and nose will collect the tests without running package code inappropriately.
  • When a project appears to have library and test code organized into separate directories, library directories are examined first.
  • When nose imports a module, it adds that module's directory to sys.path; when the module is inside of a package, like package.module, it will be loaded as package.module and the directory of package will be added to sys.path.
  • If an object defines a __test__ attribute that does not evaluate to True, that object will not be collected, nor will any objects it contains.

If you prefix your test modules with 'test_*' and your test functions within your modules with 'test_*' you'll be good to go.

Oh, and remember how we said nose can run unittests? Let's try it!

              [terryp@tpmacbook] code :: nosetests unittest_toy.py 
              .
              ----------------------------------------------------------------------
              Ran 1 test in 0.001s

              OK
            

 

next

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