In Python, as developers find bugs, and issues in their design, they enforce their code with advanced techniques that we will cover in this blog. Any unexpected situations in the code will trigger the assertions and lead to aborting the programs, and identifying the bugs for future improvement. How do software developers build robust programs? It is with assertions and unit tests, that developers improve their codes.
On a simpler level, how do we change software, code – programs, and algorithms – without losing the original functionality? In this blog post we will learn about writing a simple Python program and updating it as requirements change, all the time using unit tests to capture this behavior. We will learn how web developers enforce their designs, and gain confidence.
Meet the Assert Statement
If you a beginning Python user, you may benefit from using the Python interpreter interactively, via the IDLE environment in Windows, or the shell in Linux. Start by typing in the shell,
and you should see the following prompt inviting you to explore the language.
user@host:/usr/username/$ python Python 2.7.3 (default, Sep 26 2013, 20:08:41) [GCC 4.6.3] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>>
Follow along by typing into the interpreter the lines of code, and the outputs are highlighted in red, while the code is in black,
>>> assert True >>> assert 1 + 1 >>> assert 1 + 1 > 0 >>> assert 1 + 1 – 4 >>> assert 0 Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError >>> assert False Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError
Your empirical experiments have shown you something about assert statement; i.e. on True evaluation of expression assert is quiet, on false evaluation of the statement it raises the AssertionError exception.
Syntax of Assert Statement
The assert statement is a keyword in Python language. You can display a list of keywords by typing in the interpreter,
>>> import keyword >>> print keyword.kwlist ['and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield']
The syntax for assert statement looks like,
where the assert statement evaluates the expression and raises an AssertionError exception when the said expression has a False value in Python. If the expression evaluates to None, False, 0, or  then assert triggers the AssertionError.
>>> assert  Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError >>> assert None Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError >>> assert False Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError >>> assert 0 Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError
Programmers use the assert statement to raise errors to catch unexpected behavior of their code. For example if you wrote a program to calculate the volume of a solid, or area of a polygon, you can include an assertion that the volume, or area, respectively should not be zero; i.e. assert area > 0
Examples – Using Assertions in Factorial Function
Factorial of a number, N, is defined to be the product of numbers from 1 to N; i.e. N! = 1*2*3* … N. Clearly a straightforward way to calculate factorial is using a for-loop, but we prefer to use a recursive definition. This recursive form is equivalent, and accurate, mathematical definition of factorial function is using the notation; i.e. N! = (N-1)!*N. Here we can use the assert statement to ensure the result of factorial is always positive.
Clearly a way of writing this as a program in iterative and recursive ways would be,
# recursive way of writing factorial def fact( n ): if ( n == 0 ): return 1.0 return fact( n - 1)*n; if __name__ == "__main__": for i in range(0,10+1): fact_val = fact(i); assert( fact_val > 0 ) #factorial is always non-zero print("%d! = %g"%(i,fact_val))
Running the Program
You can download Python package for your platform from the source website, Python.org, and run the tests and programs as, $ python factorial_v1.py, is the command to interpret the code and then run the program
0! = 1 1! = 1 2! = 2 3! = 6 4! = 24 5! = 120 6! = 720 7! = 5040 8! = 40320 9! = 362880 10! = 3.6288e+06
Bugs and Fixes
There are various bugs in the function in the program few of which are,
- Function does blows up with negative inputs. You need to have include guards
- Function does not work with complex inputs
- Numerical overflow for large input n
The following listing fixes the bugs
# recursive way of writing factorial def fact( n ): if ( isinstance( n, complex) ): raise Exception(“Cannot calculate factorial of complex function”) if ( n == 0 ): return 1.0 elif ( n < 0 ): raise Exception("Cannot calculate factorial for negative numbers") assert( n > 0 ) #since all other cases are treated elsewhere return fact( n - 1)*n; if __name__ == "__main__": for i in range(0,10+1): print("%d! = %g"%(i,fact(i))) print("%d! = %g"%(-5+4j,fact( -5 + 4j ))) #raises exception for complex print("%d! = %g"%(-5,fact( -5 ))) #raises exception
Adding a Unit Test
Now let us focus on writing the test cases which exercise the function in various modes, and lock down the behavior.
Python has a powerful unit test framework called the unittest module, which we will use to write unit tests to ensure our solution against possible regression.
Each test point lives in a function named as ‘test_’ and it exercises the various cases of the ‘fact’ and compares the results against the inbuilt math function ‘math.factorial’. Usually the comparison of function output with known results is done using the unit-test API methods in the next section.
The code listing for ‘factorial_test.py’ follows,
import unittest import math import factorial_v1 from test import test_support class FactorialTest(unittest.TestCase): def setUp(self): print("setup") def tearDown(self): print("cleanup") def test_positives(self): for x in range(0,10+1): act = math.factorial( x ) val = factorial_v1.fact( x ) print("%d! = %g == %g"%(x,val,act)) self.assertAlmostEqual( act, val, 1e-1 ) def test_negative(self): passed = False try: factorial_v1.fact( -3 ) except Exception as e: passed = True and (e.message.find("Cannot calculate")>= 0 ) self.assertTrue( passed ) ## alternate way with self.assertRaises( Exception ) as cm: factorial_v1.fact(-3) if __name__ == "__main__": test_support.run_unittest(FactorialTest)
and run it on the Python interpreter to see a message which shows the 2 unittest have passed, and happy ‘OK’ message is printed on screen.
Software developers use assertions to enforce invariants in the design of programs, and testing those programs against regression. The python assert statement is one of key tools to enforce invariants in code, and in testing enabling users and developers of programs to progress without regression. Continue to stay on top and learn more about regression testing and software design.