Unit Testing in Python
Tags: python, beginners, tutorial, codenewbie
Published: 2021-10-30 20:18:35

---
title: Unit Testing in Python
published: true
description: Unit Testing in Python
tags: python, beginners, tutorial, codenewbie
//cover_image: https://direct_url_to_image.jpg
---
Testing your code is just as important as writing it.

If the user *can* do it, the user *will* do it.

Our programs should be as robust as possible. In other words, a program must work correctly for all possible scenarios. This is why **unit testing** is so important.

Unit testing is the process of verifying and validating code 🔍. A unit test specifies the program input and expected result. You write unit test cases for all possible scenarios. For every input, the program must produce the correct result.

Of course it is not practical to test *every* possible input. But we can generalize our test cases over a range of possible values. Then we can have confidence that our programs work correctly.

Imagine that your task is to write a Python program to count the number of occurrences of a number, or _key_, in an array and return the count. Here is a program that defines a `count()` function to do that:

```python
# countkey.py
def count(k,l):
  # count occurrences of k in l
  num = 0
  for i in l:
    if i == k:
      num += 1
  return num  # return the count
```

Now consider this unit test, where the input values are the number to search for and the array to search, followed by the expected output:

* Input: `1`, `[1,2,3,1]`
* Expected Output: `2`

Since `1` appears in the array twice, the expected return value is `2`.

Python provides a unit testing framework called `unittest` that automates unit test cases. Given the `countkey.py` program that we are testing, here is another program called `tester.py` that automates the unit test:

```python
# tester.py
import countkey  # the program to test
import unittest  # Python test framework

class TesterClass(unittest.TestCase):
  # test cases

  def test1(self):
    self.assertEqual(countkey.count(1,[1,2,3,1]), 2)

if __name__ == '__main__':
  unittest.main()
```

In the above program, there is one test case which is the `test1()` method. This method asserts that the `count()` function returns the value `2`, when given the input parameters: `1` , `[1,2,3,1]`. When you execute the `tester.py` program, the output is:

```bash
$  python tester.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK
$
```

The test case passed. But there are a lot more scenarios to test if we want to ensure that the program is correct and robust. Identifying these scenarios is vital to the success of your programs. Here are some other unit tests:

* Count occurrences of a positive number
* Count occurrences of a negative number
* Count occurrences when the key is at the beginning of the array
* Count occurrences when the key is in the middle of the array
* Count occurrences when the key is at the end of the array
* Count occurrences when the key occurs multiple times
* Count occurrences when the key does not occur at all
* Count occurrences when the array is empty
* Count occurrences when searching for the key `None`, and it is in the array
* Count occurrences when searching for the key `None`, and it is not in the array
* Count occurrences when searching in `None` instead of an array

These are not all possible unit tests, but they do check all types of scenarios such as different numbers, boundaries, empty arrays, etc.

Here is the complete `tester.py` program that automates all of the above unit tests:


```python
# tester.py
import countkey  # the program to test
import unittest  # Python test framework

class TesterClass(unittest.TestCase):
  # test cases

  def test1(self):
    # find positive key
    self.assertEqual(countkey.count(6,[6,7,8]), 1)

  def test2(self):
    # find negative key
    self.assertEqual(countkey.count(-7,[6,-7,8]), 1)    

  def test3(self):
    # key is found at the beginning
    self.assertEqual(countkey.count(6,[6,7,8]), 1)

  def test4(self):
    # key is found in the middle
    self.assertEqual(countkey.count(7,[6,7,8]), 1)    

  def test5(self):
    # key is found at the end
    self.assertEqual(countkey.count(8,[6,7,8]), 1)        

  def test6(self):
    # multiple keys found
    self.assertEqual(countkey.count(1,[1,6,1,7,8,1]), 3)  

  def test7(self):
    # key is not found
    self.assertEqual(countkey.count(9,[1,2,3,1]), 0)

  def test8(self):
    # empty array
    self.assertEqual(countkey.count(3,[]), 0)

  def test9(self):
    # Find None when it is there
    self.assertEqual(countkey.count(None,[1,2,None,3]), 1)

  def test10(self):
    # Find None when it is not there
    self.assertEqual(countkey.count(None,[1,2,3]), 0)

  def test11(self):
    # Find in None?
    self.assertEqual(countkey.count(3,None), 0)

if __name__ == '__main__':
  unittest.main()
```

When you execute `tester.py`, the output is:

```bash
$  python tester.py
..E........
======================================================================
ERROR: test11 (__main__.TesterClass)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tester.py", line 51, in test11
    self.assertEqual(countkey.count(3,None), 0)
  File "/Users/user/Desktop/countkey.py", line 5, in count
    for i in l:
TypeError: 'NoneType' object is not iterable

----------------------------------------------------------------------
Ran 11 tests in 0.001s

FAILED (errors=1)
$
```

The output indicates that `test11()` failed. That is because the program does not properly handle the case when the `count()` function tries to search `None` instead of an array. We can modify `countkey.py` to make it more robust:

```python
# countkey.py
def count(k,l):
  # count occurrences of k in l
  
  # NEW
  if l == None:
    return 0
  
  num = 0
  for i in l:
    if i == k:
      num += 1
  return num  # return the count
```

Here is the output now:

```bash
$  python tester.py
...........
----------------------------------------------------------------------
Ran 11 tests in 0.000s

OK
$
```
Since all the unit test cases are successful, we can be confident that the program is correct. Another advantage of automating unit test cases is that we can easily re-test the `countkey.py` whenever someone modifies it. All you have to do is execute the `tester.py` program. That is why it is important to write good, comprehensive unit test cases for your programs. And remember to update your tester programs as needed.

Unit testing is an important part of program development. Once you identify the full set of unit test cases, automate them with a testing framework like Python `unittest`. This will improve your program and ensure that the program is correct, now and in the future. 

Thanks for reading. 😃

*Follow me on Twitter [`@realEdwinTorres`](https://twitter.com/realEdwinTorres) for more programming tips and help.*