Time for action testing factorial.py

The test suite to accompany factorial.py is called test_factorial.py. Run it and you should see output similar to this:


python test_factorial.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK

Three tests were executed and apparently everything went ok.

What just happened?

The code in test_factorial.py starts by importing both the module we want to test (factorial) and the unittest module. Then we define a single class named Test (highlighted) derived from unittest.TestCase. By deriving from this class, our class will be distinguishable as a test case to the test runner and will provide us with a number of assertion methods.

Our Test class may consist of any number of methods. The ones with names starting with test_ will be recognized as tests by the test runner. Because the names of failing tests will be printed, it is useful to give these tests sensible names reflecting their purpose. Here we define three such methods: test_number(), test_zero(), and test_illegal().

Chapter4/test_factorial.py

import unittest
from factorial import fac
class Test(unittest.TestCase):
	def test_number(self):
		self.assertEqual(24,fac(4))
		self.assertEqual(120,fac(5))
		self.assertEqual(720,fac(6))
	def test_zero(self):
		self.assertEqual(1,fac(0))
	def test_illegal(self):
		with self.assertRaises(ValueError):
			fac(-4)
		with self.assertRaises(ValueError):
			fac(3.1415)
if __name__ == '__main__':
	unittest.main()

test_number() tests a number of regular cases to see if our function returns something reasonable. In this case, we check three different numbers and use the assertEquals() method inherited from the TestCase class to check that the value calculated (passed as the second argument) equals the expected value (the first argument).

test_zero() asserts that the special case of zero indeed returns 1. It again uses the assertEqual() method to check whether the expected value (1) matches the value returned.

test_illegal() finally asserts that only positive arguments are accepted (or rather it asserts that negative values correctly raise a ValueError exception) and that arguments to fac() should be int or raise a ValueError as well.

It utilizes the method assertRaises() provided by TestCase. assertRaises() will return an object that can be used as a context manager in a with statement. Effectively, it will catch any exception and check whether it is an expected one. If not, it will flag the test as failed.

These methods show a familiar pattern in unit testing: a fairly small number of tests check whether the unit behaves correctly in normal cases, while the bulk of the tests are often devoted to special cases (often referred to as edge cases). And, just as important, serious effort is spent on testing that illegal cases are correctly flagged as such.

The last thing we find in test_factorial.py is a call to unittest.main(), the test runner. It will look for any defined classes deriving from TestCase and run any method that starts with test_, tallying the results.

Now what have we gained?

If we would change, for example, the implementation of fac() to something that does not use recursion like the following code, we could rapidly check that it behaves as expected by running test_factorial.py again.

from functools import reduce
def fac(n):
	if n < 0 : raise ValueError("factorial of a negative number is not 
defined")
	if type(n) != int : raise ValueError("argument is not an integer")
	if n == 0 : return 1
	if n == 1 : return 1
	return reduce(lambda x,y:x*y,range(3,n+1))

The special case handling remains the same, but the highlighted line shows that we now calculate the factorial with Python's reduce() function from the functools module. The reduce() function will apply a function to the first pair of items in a list and then again to the result of this and each remaining item. The product of all numbers in a list can be calculated by passing reduce() a function that will return the product of two arguments, in this case, our lambda function.

Note

More on the reduce() function can be found in the documentation of the functools module, Python's powerful functional programming library: http://docs.python.org/py3k/library/functools.html.

Pop quiz spotting the error

  1. Can you anticipate any errors in the previous code? Which test method will fail?
    • test_number()
    • test_zero()
    • test_illegal()
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.68.18