Catching errors and unit tests

In this tutorial are few examples how catch error and how to perform unit tests in Python.

When you code in Python, keep in mind:

Errors should never pass silently. Unless explicitly silenced. (PEP20 - The Zen of Python)

Error catching and silencing

Following function is designed to sum two variables (numbers - float or integers) together and return this result as float


In [7]:
def sum_together1(a, b):
    return a + b

An example of correct use follows.


In [8]:
sum_together1(1., 2)


Out[8]:
3.0

Example of incorrect use that finish without error follows.


In [9]:
sum_together1("a", "b")


Out[9]:
'ab'

Example of incorrect use that results in error follows


In [10]:
sum_together1("a", 1)


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-10-2f9ca530090e> in <module>()
----> 1 sum_together1("a", 1)

<ipython-input-7-d259ef450882> in sum_together1(a, b)
      1 def sum_together1(a, b):
----> 2     return a + b

TypeError: cannot concatenate 'str' and 'int' objects

As you can see, if the function is used in incorrect way (different then planed) it can or cannot cause an error. If such a function is part of the application, both options are dangerous. Different version of the function above follows. This function allows only variables that are convertable to float.


In [11]:
def sum_together2(a, b):
    a = float(a)
    b = float(b)
    return a + b

In [12]:
sum_together2(1, 2)


Out[12]:
3.0

In [13]:
sum_together2("a", "b")


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-13-d8e70e3a19eb> in <module>()
----> 1 sum_together2("a", "b")

<ipython-input-11-d2a33a95854f> in sum_together2(a, b)
      1 def sum_together2(a, b):
----> 2     a = float(a)
      3     b = float(b)
      4     return a + b

ValueError: could not convert string to float: a

In some cases you cannot allow the applications to crash, but providing of incorrect (predefined) result is ok. Example follows.


In [14]:
def sum_together3(a, b):
    try:
        a = float(a)
        b = float(b)
        return a + b
    except:
        return 0.0

In [15]:
sum_together3(1, 2)


Out[15]:
3.0

In [16]:
sum_together3("a", "b")


Out[16]:
0.0

In practice, you should still report/log this issues somehow, otherwise you will have no information about errors silenced like this.

Unit tests

Unit test is term describing tests for partical code units (functions, classes, blocks). In this tutorial are examples of such a tests. For the design of the tests was used unittest, that is standard Python library. Few simple tests follows. Some test are designed to fail - they are obviously wrong.


In [30]:
import unittest

class Test1(unittest.TestCase):

    def test_type_error_number_and_string(self):
        with self.assertRaises(TypeError):
            1 + "a"
            
    def test_type_error_number_and_number(self): # wrong test!
        with self.assertRaises(TypeError):
            1 + 1

    def test_float_and_int_equality(self):
        self.assertEquals(0, 0.0)
        
    def test_equality(self): # this test is wrong!
        self.assertEquals(0., 1.)            
            
suite = unittest.TestLoader().loadTestsFromTestCase(Test1)
unittest.TextTestRunner(verbosity=3).run(suite)


test_equality (__main__.Test1) ... FAIL
test_float_and_int_equality (__main__.Test1) ... ok
test_type_error_number_and_number (__main__.Test1) ... FAIL
test_type_error_number_and_string (__main__.Test1) ... ok

======================================================================
FAIL: test_equality (__main__.Test1)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython-input-30-1e193cd3b9ef>", line 17, in test_equality
    self.assertEquals(0., 1.)
AssertionError: 0.0 != 1.0

======================================================================
FAIL: test_type_error_number_and_number (__main__.Test1)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython-input-30-1e193cd3b9ef>", line 11, in test_type_error_number_and_number
    1 + 1
AssertionError: TypeError not raised

----------------------------------------------------------------------
Ran 4 tests in 0.007s

FAILED (failures=2)
Out[30]:
<unittest.runner.TextTestResult run=4 errors=0 failures=2>

Example of unit tests for functions created before (sum_together1, sum_together2, sum_together3) follows.


In [43]:
import unittest

class Test2(unittest.TestCase):

    def test_nan_sum_together1(self):
        """
        Check if it throws error only for unsumable inputs.
        """
        # this should pass
        sum_together1("a", "b")
        # this should fail
        with self.assertRaises(TypeError):
            sum_together1(1, "b")
        
    def test_nan_sum_together2(self):
        """
        Check if it throws error every time.
        """
        with self.assertRaises(ValueError):
            sum_together2("a", "b")
        with self.assertRaises(ValueError):
            sum_together2(1, "b")            
   
    def test_nan_sum_together3(self):
        """
        Check if it provides correct default
        """
        self.assertEquals(sum_together3("a", "b"), 0.0)
        self.assertEquals(sum_together3(1, "b"), 0.0)

    def test_validity_sum_together1(self):
        """
        Check if it returns correct values.
        """
        self.assertEquals(sum_together1(0, 0), 0.0)
        self.assertEquals(sum_together1(1, 0), 1.0)
        
    def test_validity_sum_together2(self):
        """
        Check if it returns correct values.
        """
        self.assertEquals(sum_together2(0, 0), 0.0)
        self.assertEquals(sum_together2(1, 0), 1.0)
        
    def test_validity_sum_together3(self):
        """
        Check if it returns correct values.
        """
        self.assertEquals(sum_together3(0, 0), 0.0)
        self.assertEquals(sum_together3(1, 0), 1.0)
            

            
suite = unittest.TestLoader().loadTestsFromTestCase(Test2)
unittest.TextTestRunner(verbosity=3).run(suite)


test_nan_sum_together1 (__main__.Test2) ... ok
test_nan_sum_together2 (__main__.Test2) ... ok
test_nan_sum_together3 (__main__.Test2) ... ok
test_validity_sum_together1 (__main__.Test2) ... ok
test_validity_sum_together2 (__main__.Test2) ... ok
test_validity_sum_together3 (__main__.Test2) ... ok

----------------------------------------------------------------------
Ran 6 tests in 0.005s

OK
Out[43]:
<unittest.runner.TextTestResult run=6 errors=0 failures=0>