9.3 Usage

     

9.3 Usage

Test classes are created by subclassing TestCase . The simplest approach is to override the method runTest( ) , as shown in Example 9-1.

Example 9-1. Simple unit test for the class Book
 booktests.py

"""Unit test for book.py"""



import book

import unittest



class  BookTests  (unittest.TestCase):



   def  runTest  (self):

      """Test book creation"""

      book1 = book.Book( "Cosmos", "Carl Sagan" )

      self.  assertEqual  ( "Cosmos", book1.title ) 

This example creates the test class BookTests . The test method runTest( ) creates a Book and uses the assertEqual( ) test assert method to verify its attributes. Test methods customarily contain a label similar to the example's Test book creation . This test description is printed if the test fails.

The class Book tested by BookTests is given in Example 9-2.

Example 9-2. Simple unit test for the class Book
 book.py



class  Book  :

   title = ""

   author = ""



   def  __init_  _  (self, title, author):

      self.title = title

      self.author = author 

Unit tests may be run from the command line using unittests.py as a test runner. The argument specifies the Python module name, class name, and method name of the test to run. Example 9-3 demonstrates running BookTests this way and shows the result.

Example 9-3. Results of running BookTests
 $ python unittest.py booktests.BookTests.runTest

.

------------------------------------------------

Ran 1 test in 0.000s



OK 

For this command to work as shown, the module unittests.py must be present in the Python search path specified by the environment variable $PYTHONPATH .

Making BookTests fail by changing the test assert statement to self.assertEqual ( " Bad", book1.title ) demonstrates PyUnit's test failure reporting, as shown in Example 9-4.

Example 9-4. BookTests failure
 $ python unittest.py booktests.BookTests.runTest

F

======================================================================

FAIL: Test book creation

----------------------------------------------------------------------

Traceback (most recent call last):

  File "/cygdrive/c/work/UnitTestFrameworks/Python/example1/booktests.

  py", 

line 11, in runTest

    self.assertEqual( "Bad", book1.title )

  File "/cygdrive/c/work/UnitTestFrameworks/Python/example1/unittest.

  py", 

line 302, in failUnlessEqual

    raise self.failureException, \

AssertionError: 'Bad' != 'Cosmos'



----------------------------------------------------------------------

Ran 1 test in 0.000s



FAILED (failures=1) 

The failure report includes the test description Test book creation , as well as the specific assert condition that failed.

Rather than overriding runTest( ) , it is far more common to create uniquely named test methods. This allows building test classes with multiple test methods. Example 9-5 shows BookTests redesigned this way.

Example 9-5. Redesigned BookTest
 booktests.py

"""Unit test for book.py"""



import book

import unittest



class  BookTests  (unittest.TestCase):



   def  testCreateBook  (self):

      """Test book creation"""

      book1 = book.Book( "Cosmos", "Carl Sagan" )

      self.assertEqual( "Cosmos", book1.title )



if __name__ == '__main_  _':

   unittest.main( ) 

The additional two lines of code at the end allow the test to be run directly without using unittest.py , as shown in Example 9-6. All methods that have names starting with test are found and run.

Example 9-6. Running BookTest directly
 $ python booktests.py

.

---------------------

Ran 1 test in 0.000s



OK 

With this approach, multiple test methods can be added to a test class. If they share objects, test fixture behavior should be implemented using the setUp() and tearDown( ) methods. Example 9-7 shows the test fixture LibraryTests .

Example 9-7. . The test class LibraryTests
 librarytests.py

"""Unit test for library.py"""



import book

import library

import unittest



class  LibraryTests  (unittest.TestCase):



   def  setUp  (self):

      self.library = library.Library( )

      book1 = book.Book( "Cosmos", "Carl Sagan" )

      self.library.addBook( book1 )

      book2 = book.Book( "Contact", "Carl Sagan" )

      self.library.addBook( book2 )



   def  tearDown  (self):

      self.library.dispose( )



   def  testGetNumBooks  (self):

      """Test getting number of books"""

      self.  assert_  ( self.library.getNumBooks( )==2 )



   def  testGetBook  (self):

      """Test getting a book from library"""

      book2 = self.library.getBook( "Cosmos" )

      self.assertNotEqual( None, book2, "Book not found" ) 

The setUp( ) method creates a Library and adds two Book s to it, and tearDown( ) disposes of the Library . The test method testGetNumBooks( ) uses the test assert method assert_() to check the library's size . This is the most generic type of test assert, as it simply checks whether its argument evaluates to true .

Example 9-8 shows the Library class that is tested by LibraryTest .

Example 9-8. The Library class
 library.py

class  NonexistentBookError  (Exception):

   """Exception thrown for missing book"""

   pass



class  Library  :

   """A library"""



   def  __init_  _  (self):

      self._  _books = dict( )



   def  dispose  (self):

      self._  _books.clear( )



   def  addBook  (self, book):

      """Add a book"""

      self._  _books[book.title] = book



   def  getBook  (self, title):

      """Find a book by title"""

      return self._  _books.get(title)



   def  removeBook  (self, title):

      """Remove a book"""

      if self._  _books.has_key(title):

         self._  _books.pop(title)

      else:

         raise NonexistentBookError



   def  getNumBooks  (self):

      """Get number of books"""

      return len(self._  _books) 

Library uses the Python dictionary object dict( ) to contain a collection of Book s. The module library.py also defines the exception class NonexistentBookError . This type of exception is thrown by the method removeBook( ) if it cannot find the Book to remove.

The failUnlessRaises() test assert method can be used to check for expected exception behavior, as shown in Example 9-9.

Example 9-9. Testing for an expected exception
 librarytests.py

   def  testRemoveNonexistentBook  (self):

      """Test expected exception from removing a nonexistent book"""

      self.  failUnlessRaises  (library.NonexistentBookError,

         self.library.removeBook, "Nonexistent" ) 

The arguments passed to failUnlessRaises( ) are an exception type, a callable object, and a variable argument list. In this example, the exception type is NonexistentBookError and the callable object is the function removeBook( ) . The object is called with the specified argument list. If an exception of the given exception type is thrown, the test passes . If no exception is thrown, or some other type of error occurs, the test fails.

Multiple tests may be aggregated using TestSuite . Example 9-10 adds a function named suite() to create a TestSuite containing LibraryTest 's test methods and changes the call to run the suite to unittest.main() .

Example 9-10. Creating and running a TestSuite
 librarytests.py

def  suite  ( ):

   suite = unittest.  TestSuite  ( )

   suite.  addTest  (LibraryTests("testGetNumBooks"))

   suite.  addTest  (LibraryTests("testGetBook"))

   suite.  addTest  (LibraryTests("testRemoveNonexistentBook"))

   return suite



if __name__ == '__main_  _':

   unittest.main(defaultTest='suite') 

Tests are added to a TestSuite using its addTest( ) method. It also has an addTests( ) method that allows multiple tests to be added at once.

PyUnit provides a convenience method, makeSuite() , which creates a TestSuite . It finds all methods named with a given prefix, such as test , and returns a suite containing them. Example 9-11 demonstrates makeSuite( ) .

Example 9-11. . Using makeSuite( ) to create a TestSuite
 librarytests.py

def  suite  ( ):

   suite = unittest.  makeSuite  (LibraryTests, "test")

   return suite 

It's often useful to create a module that builds TestSuite containing all the tests in each test class. Example 9-12 shows such a module, named alltests.py .

Example 9-12. Module to run all tests
 alltests.py

import unittest



def  suite  ( ):

   modules_to_test = ('  booktests  ', '  librarytests  ')  alltests  = unittest.  TestSuite  ( )

   for module in map(__import_  _, modules_to_test):

      alltests.addTest(unittest.findTestCases(module))

   return  alltests  if __name__ == '__main_  _':

   unittest.main(defaultTest='suite') 

This example creates and runs a TestSuite named alltests that contains all the tests from booktests.py and librarytests.py .

Python includes a command-line interpreter for interactively running code. PyUnit tests can be run this way. Example 9-13 demonstrates using the interpreter to create and run a unit test.

Example 9-13. Running a test interactively
 $ python

>>> import unittest

>>> import librarytests

>>> runner = unittest.TextTestRunner( )

>>> test = librarytests.LibraryTests("testGetBook")

>>> runner.run(test)

.

----------------------------------------------------------------------

Ran 1 test in 0.000s



OK

<unittest._TextTestResult run=1 errors=0 failures=0> 

In this example, the modules unittest and librarytests are imported and a TextTestRunner is created. Next, a test containing the test method testGetBook( ) is created and run using the test runner.

A TestSuite can be created and run similarly, as shown in Example 9-14.

Example 9-14. Creating a TestSuite interactively
 >>> suite = unittest.makeSuite(librarytests.LibraryTests,'test')

>>> runner.run(suite)

.....

----------------------------------------------------------------------

Ran 5 tests in 0.001s



OK

<unittest._TextTestResult run=5 errors=0 failures=0> 

Example 9-15 illustrates creating a test from BookTests and adding it to the TestSuite .

Example 9-15. Adding a test to the test suite
 >>> import booktests

>>> test2 = booktests.BookTests("testCreateBook")

>>> suite.addTest(test2)

>>> runner.run(suite)

......

----------------------------------------------------

Ran 6 tests in 0.001s



OK

<unittest._TextTestResult run=6 errors=0 failures=0> 

The PyUnit GUI is implemented in the module unittestgui.py . It is not included with the standard Python libraries but is available in downloads from the PyUnit web site. It acts as a test runner, running test modules, classes, and methods, and displays a friendly green or red test results indicator, as well as failure details. It is shown in Figure 9-1.

Figure 9-1. The PyUnit GUI
figs/utf_0901.gif

The name of the test to run is entered at the top. The name can specify a module (e.g., librarytests ), a test class ( librarytests.LibraryTests ), or a test method ( librarytests.LibraryTests.testGetBook ). Except when a specific test method name is specified, all the methods in the module or class being tested that have names starting with test are run.