Section 21.1. Nose


21.1. Nose

Nose is a sophisticated and unobtrusive unit test framework for Python. It automatically discovers your tests (based on simple naming conventions), runs them, and reports the result. You can write simple test functions, unit test-based test cases or doctests. Nose will happily execute them all. Nose also knows how to intercept the standard Python assert, so you don't need to learn and depend on some Nose-specific mechanism in your test code. Nose is not TurboGears-specific, and you can (and should) use it to test all your Python code. Let's see Nose in action by testing a factorial function:

def factorial(n, recursive=True):   """Computes the factorial of non-negative integers   >>> factorial(0)   1   >>> factorial(5)   120   >>> factorial(4, recursive=False)   24   """   def recursive_fact(n):     if n == 0:       return 1     return n * recursive_fact(n-1)   def iterative_fact(n):      result = 1      for i in xrange(1, n):         result *= i      return result   if not isinstance(n, int) or n < 0:     raise ValueError('n must be a non-negative integer')   if recursive:     return recursive_fact(n)   else:     return iterative_fact(n)


This function is pretty sophisticated, as far as factorial functions go. It verifies that input is a non-negative integer and supports two algorithms for calculating the factorial: a recursive one and an iterative one. The algorithms are implemented as nested functions. The actual algorithm is selected by the value of the recursive named argument that defaults to True. Another interesting fact is that this function has a doc string that contains doctests. Doctests are interactive Python sessions that contain a command line and a response from the interpreter. The easiest way to write doctests is to copy and paste them from an interactive Python session. You can execute doctests with the doctest module, but why should you? Nose can do it for you. Okay, let's write some test code and put it in a file called test_factorial.py:

from factorial import factorial as f def dummyNotCalled():   assert False def testAlwaysFails():  assert False def test_Success():   assert f(0) == 1   assert f(1) == 1   assert f(2) == 2   assert f(3) == 6   assert f(4) == 28   assert f(5) == 120   assert f(6) == 720 def test_Exception():   for x in (-1, 0.455, 'xxxx'):     try:       f(x)       assert False # should never get here     except ValueError:       pass def tesst_Iterative():   for n, result in ((0,1), (1,1), (2,2), (3,6), (6,720)):     assert f(n, recursive=False) == result


The filename and the names of the test functions are important. Nose discovers your tests by scanning the root directory (current directory, by default) recursively and executing all the functions and methods that match the test signature. This signature (a.k.a.testMatch) is, by default, the following regex: (?:^|[b_.-])[Tt]est. It means anything that starts with test or Test and anything that contains _test, _Test, -test, or -Test. This scheme covers most reasonable naming convention for tests. You can control many aspects of Nose execution by providing various switches. Let's launch Nose and tell it to execute doctests too:

[View full width]

nosetests --with-doctest FFF. ====================================================================== FAIL: Doctest: factorial.factorial ---------------------------------------------------------------------- Traceback (most recent call last): File "/usr/local/lib/python2.4/doctest.py", line 2152, in runTest raise self.failureException(self.format_failure(new.getvalue())) AssertionError: Failed doctest test for factorial.factorial File "/home/gsayfan/trunk/examples/example1/tutorial/nosedemo/factorial.py", line 1, in factorial ---------------------------------------------------------------------- File "/home/gsayfan/trunk/examples/example1/tutorial/nosedemo/factorial.py", line 6, in factorial.factorial Failed example: factorial(5) Expected: 123 Got: 120 ---------------------------------------------------------------------- File "/home/gsayfan/trunk/examples/example1/tutorial/nosedemo/factorial.py", line 9, in factorial.factorial Failed example: factorial(4, recursive=False) Expected: 24 Got: 6 ====================================================================== FAIL: test_factorial.testAlwaysFails ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/gsayfan/nta/eng/linux64/lib/python2.4/site-packages/nose-0.9.0- py2.4.egg/nose/case.py", line 52, in runTest self.testFunc() File "/home/gsayfan/trunk/examples/example1/tutorial/nosedemo/test_factorial. py", line 7, in testAlwaysFails assert False AssertionError ====================================================================== FAIL: test_factorial.test_Recursive ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/gsayfan/nta/eng/linux64/lib/python2.4/site-packages/nose-0.9.0- py2.4.egg/nose/case.py", line 52, in runTest self.testFunc() File "/home/gsayfan/trunk/examples/example1/tutorial/nosedemo/test_factorial. py", line 14, in test_Recursive assert f(4) == 28 AssertionError ---------------------------------------------------------------------- Ran 4 tests in 0.010s FAILED (failures=3)


Wow. Three failures out of four tests. That's impressive. This is one programmer that can really benefit from TDD (Test Driven Development). Nose gives a very nice and detailed report when a test fails. Let's go over the failures in reverse order. The test_Recursive function failed on the line assert f(4) == 28. Wait a minute, this is not right. 4! == 24. This a defective test. It happens. Let's fix it and move on. The next failure is testAlwaysFails(), which contains the statement, assert False, which, of course, always fails. This is easy to fix by simply commenting out this function. Note that dummyNotCalled(), which also contains assert False didn't cause a failure because the function name doesn't match the test signature and Nose just ignored it. Last, but not least, is the doctest failure. Note the friendly output. The problem here seems to be an actual bug in the code. The code that's executing is the iterative flavor of factorial, and it returns 6 for 4! instead of 24.

If you are factorially inclined you probably noticed that 6 is 3! and your nose tells you that an off by one error is involved. Indeed, the loop in iterative_fact stops one iteration short. The xrange() yields the half open range (1, n). The fix is easy, just add 1 to range. Here is the correct function:

def iterative_fact(n):      result = 1      for i in xrange(1, n+1):         result *= i      return result


So, we fixed all the errors and bugs. Now we can run Nose again:

nosetests --with-doctest ... ---------------------------------------------------------------------- Ran 3 tests in 0.009s OK


That looks much better. Because the testAlwaysFails() function is gone, we ran three tests instead of four and they all passed. Nose practices the "silence is golden" principle and by default captures all standard output. Let's try it one more time with the -v switch:

nosetests --with-doctest -v Doctest: factorial.factorial ... ok test_factorial.test_Recursive ... ok test_factorial.test_Exception ... ok ---------------------------------------------------------------------- Ran 3 tests in 0.008s OK


Still okay. Unfortunately, we are missing a test. What happened to test_Iterative? It turns out that we misspelled it as: tesst_Iterative, so it didn't match the test signature and Nose ignored it. After fixing it, the output is finally what we expect:

nosetests --with-doctest -v Doctest: factorial.factorial ... ok test_factorial.test_Recursive ... ok test_factorial.test_Iterative ... ok test_factorial.test_Exception ... ok ---------------------------------------------------------------------- Ran 4 tests in 0.009s OK


Nose intercepts unhandled exceptions and treats them as failures. If you have multiple asserts in a single test case, the first failure will abort the test case and continue to execute other tests. Nose boasts a plugin system that already includes a profiler (hot-shot) and coverage report. We urge you to investigate Nose and put it to good use in your TurboGears applications and other Python projects.




Rapid Web Applications with TurboGears(c) Using Python to Create Ajax-Powered Sites
Rapid Web Applications with TurboGears: Using Python to Create Ajax-Powered Sites
ISBN: 0132433885
EAN: 2147483647
Year: 2006
Pages: 202

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net