## Regression Testing for Robust Software Development

During the course of the software development, in Python, there are deliverables for customers, and deadlines, but developers will find bugs, and issues in their design, or execution, or both. How do software developers build programs, and maintain it across the years, with changing feature requests?

On a simpler level how do change software, code – programs, and algorithms – without losing the original functionality? Software development learns from a maxim, ‘*History repeats itself, if you don’t learn from your mistakes*‘. 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.

## Simple Example – Testing 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 where temporary initialized to 1, will have to start incrementing a counter upto N, and keep track of the product. At the end of the iterations the temporary carries the result of the factorial of N.

However yet another equivalent, and accurate, mathematical definition of factorial function is using the recursive notation; i.e. N! = (N-1)!*N

In other words it is a mathematical statement saying, if you want factorial of N, N!, and you know the factorial of (N-1), (N-1)!, then here is how you can calculate the factorial of N.

Clearly a way of writing this as a program in iterative and recursive ways would be,

# recursive way of witing 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): print("%d! = %g"%(i,fact(i)))0! = 1

## 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

## Finding Bugs:

Not all is well with our small recursion program. Do you know what happens when you call *fact *function with a negative number? Lets try, by changing the code to be,

```
if __name__ == "__main__":
for i in range(0,10+1):
print("%d! = %g"%(i,fact(i)))0! = 1
fact( -5 )
```

and running the code as before you see the error message,

` `

```
File "factorial_v1.py", line 11, in fact
return fact( n - 1)*n;
File "factorial_v1.py", line 11, in fact
return fact( n - 1)*n;
File "factorial_v1.py", line 11, in fact
return fact( n - 1)*n;
File "factorial_v1.py", line 11, in fact
return fact( n - 1)*n;
RuntimeError: maximum recursion depth exceeded
```

This is a signature of infinite recursion, which is a serious bug to program performance. Somewhere in your code, the program is getting past a fence-post.

Clearly the *if-condition* in the factorial is this fence-post. If the definition can be somehow modified to stop the recursion, when the numbers are negative, we will not have issues with the negative input to this function. Our solution will involve raising an exception if we have factorial function is called with a negative input. The correct program listing will be,

# recursive way of writing factorial def fact( n ): if ( n == 0 ): return 1.0 elif ( n < 0 ): raise Exception("Cannot calculate factorial for negative numbers") 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,fact( -5 )))

### Complex numbers

What happens when you try using complex number input? In Python the complex numbers are written like, *5 – 4j*, with the real and imaginary part suffixed with ‘j’. Changing the code to be,

if __name__ == "__main__": i = 5 - 4j print("%d! = %g"%(i,fact(i)))

and rerunning your program, you will see the following error,

Traceback (most recent call last): File "factorial_v1.py", line 20, in <module> print fact( 5 - 4j ) File "factorial_v1.py", line 11, in fact elif ( n < 0 ): TypeError: no ordering relation is defined for complex numbers

Clearly something is wrong with your definition of factorial function, and we need to disallow complex numbers to the input. To fix this, you can make another change you can make to solution is to error out against complex number input, you can add guards to check input as,

# recursive way of writing factorial def fact( n ): if type(n) == complex: raise Exception("Cannot calculate factorial for complex numbers") if ( n == 0 ): return 1.0 elif ( n < 0 ): raise Exception("Cannot calculate factorial for negative numbers") return fact( n - 1)*n;

## 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 your code should run and produce output like,

```
----------------------------------------------------------------------
Ran 2 tests in 0.001s
```

OK

## Unit Test API

Unittest API has other assertion checks which are

`'assertAlmostEqual', 'assertAlmostEquals', 'assertDictContainsSubset', 'assertDictEqual', 'assertEqual', 'assertEquals', 'assertFalse', 'assertGreater', 'assertGreaterEqual', 'assertIn', 'assertIs', 'assertIsInstance', 'assertIsNone', 'assertIsNot', 'assertIsNotNone', 'assertItemsEqual', 'assertLess', 'assertLessEqual', 'assertListEqual', 'assertMultiLineEqual', 'assertNotAlmostEqual', 'assertNotAlmostEquals', 'assertNotEqual', 'assertNotEquals', 'assertNotIn', 'assertNotIsInstance', 'assertNotRegexpMatches', 'assertRaises', 'assertRaisesRegexp', 'assertRegexpMatches', 'assertSequenceEqual', 'assertSetEqual', 'assertTrue', 'assertTupleEqual',`

## Summary

Software testing enables us to write programs without worry of regression. Learn more about regression testing and software design in Python.