How to Test Python Code using the unittest Module
Tags: python, programming, debugging, testing
Published: 2023-07-02 09:06:15

You must have written numerous functions and a series of tasks while developing software or a web app. These functions and tasks must work properly. If we encounter errors in the code, debugging becomes difficult.

A good practice would be to divide our code into small units or parts and test them independently to ensure that they work properly.

Python provides a built-in module called `unittest` that allows us to write and run unit tests.

## Getting Started With unittest

The `unittest` module includes a number of methods and classes for creating and running test cases. Let's look at a simple example where we used the `unittest` module to create a test case.

```python
# basic.py
import unittest

class TestSample(unittest.TestCase):
    def test_equal(self):
        self.assertEqual(round(3.155), 3.0)

    def test_search(self):
        self.assertIn("G", "Geek")
```

First, we imported the `unittest` module, which will enable us to use the classes that will be used to write and execute test cases.

The `TestSample` class is defined that inherits from the `unittest.TestCase` which will allow us to use the various **assertion methods** within our test cases.

We defined two test methods within our `TestSample` class: `test_equal` and `test_search`.

The test method `test_equal()` tests if `round(3.155)` is equal to `3.0` using the `assertEqual()` assertion method.

The test method `test_search()` tests if the character `"G"` is present in the string `"Geek"` using the `assertIn()` assertion method.

To run these tests, we need to execute the following command in the terminal.

```bash
python -m unittest basic.py
```

This command will launch `unittest` as a module that searches for and executes the tests in the `basic.py` file.

Note: The `unittest` module only discovers and executes those methods that start with `test_` or `test`.

![unittest output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/62o8i2oa6x7nnq2azjjn.png)

By the way, these dots represent a successful test.

We can use the `unittest.main()` function and put it in the following form at the end of the test script to load and run the tests from the module.

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

This will allow us to run our test file, `basic.py` in this case, as the main module.


![output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cyc3sbh6h4x88emdgqme.png)

**More Detailed Result**

We can use the `-v` flag in the terminal or pass an argument `verbosity=2` inside the `unittest.main()` function to get a detailed output of the test.


![output using -v flag](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/08hoqwin2l2t2kx66c4h.png)

### Commonly Used Assertion Methods

Here is the list of the most commonly used assertion methods in unit testing.

| Method | Checks that |
| --- | --- |
| `assertEqual(a, b)` | `a == b` |
| `assertNotEqual(a, b)` | `a != b` |
| `assertTrue(x)` | `bool(x) is True` |
| `assertFalse(x)` | `bool(x) is False` |
| `assertIs(a, b)` | `a is b` |
| `assertIsNot(a, b)` | `a is not b` |
| `assertIsNone(x)` | `x is None` |
| `assertIsNotNone(x)` | `x is not None` |
| `assertIn(a, b)` | `a in b` |
| `assertNotIn(a, b)` | `a not in b` |
| `assertIsInstance(a, b)` | `isinstance(a, b)` |
| `assertNotIsInstance(a, b)` | `not isinstance(a, b)` |

## Example

Assume we have some code and want to perform unit testing on it using the `unittest` module.

```python
# triangle.py
class Triangle:
    def __init__(self, base, height):
        self.base = base
        self.height = height

    def area(self):
        return 0.5 * self.base * self.height

    def perimeter(self, side):
        return self.base + self.height + side
```

The code defines a class called `Triangle` which has an init method that initializes the object with the instance variables `self.base` and `self.height`.

There are two more methods in the `Triangle` class: `area()` and `perimeter()`.

The `area()` method returns the area of the triangle, which is half the product of the base and height (`0.5 * self.base * self.height`).

The method `parameter()` accepts a parameter called `side`, and because the triangle's parameter is the sum of its three sides, the `base` and `height` variables take the place of the other two sides.

Now we can create another Python file in which we'll write some tests and then execute them.

```python
# test_sample.py
from triangle import Triangle
import unittest

class TestTriangle(unittest.TestCase):
    t = Triangle(9, 8)

    def test_area(self):
        self.assertEqual(self.t.area(), 36)

    def test_perimeter(self):
        self.assertEqual(self.t.perimeter(5), 22)

    def test_valid_base(self):
        self.assertGreater(self.t.base, 0)

    def test_valid_height(self):
        self.assertGreater(self.t.height, 0)

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

The above code imports the `Triangle` class from the `triangle` module (`triangle.py` file) as well as imports the `unittest` module to write test cases.

The `TestTriangle` class inherits from the `unittest.TestCase` which has four test methods. The `Triangle` class was instantiated with a `base` of `9` and `height` of `8` and stored inside the variable `t`.

The `test_area` method tests whether `self.t.area()` is equal to the expected result `36` using the `assertEqual()` assertion.

The `test_perimeter` method tests whether `self.t.perimeter(5)` is equal to `22` using the `assertEqual()` assertion.

The `test_valid_base` and `test_valid_height` methods are defined to test if the base (`self.t.base`) and height (`self.t.height`) of the triangle are greater than `0` using the `assertGreater()` assertion.

The `unittest.main(verbosity=2)` method retrieves and executes the tests from the `TestTriangle` class. We'll get a detailed output because we used the `verbosity=2` argument.


![TestTriangle output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xx0y4aqq4p5m5c5vzw38.png)

## Test for Exception

If you've used [assert statements](https://geekpython.in/python-assert) before, you'll know that when one fails, it throws an `AssertionError`. Similarly, whenever a test method fails, an `AssertionError` is raised.

We can predetermine the conditions under which our code will generate an error, and then test those conditions to see if they generate errors. This is possible with the `assertRaises()` method.

The `assertRaises()` method can be used with [context manager](https://geekpython.in/context-managers-and-python-with-statement) so we'll use it in the following form:

```python
def test_method(self):
    with assertRaises(exception_name):
        function_name(argument)
```

Consider the following function `gen_odd()`, which generates a series of odd numbers up to the argument `n` by incrementing the `num` by `3` and contains only a few checks, where the argument `n` must be of type `int` and greater than `0`.

```python
# odd.py
def gen_odd(n):
    if type(n) != int:
        raise TypeError("Invalid argument type.")
    if n < 0:
        raise ValueError("Value must be greater than 0.")
    num = 0
    while num <= n:
        if num % 2 == 1:
            print(num)
        num += 3
```

Now we'll write test methods to simulate conditions that could cause the above code to fail.

```python
from odd import gen_odd
import unittest

class OddTestCase(unittest.TestCase):

    def test_negative_val(self):
        with self.assertRaises(ValueError):
            gen_odd(-5)

    def test_float_val(self):
        with self.assertRaises(TypeError):
            gen_odd(99.9)

    def test_string_val(self):
        with self.assertRaises(TypeError):
            gen_odd('10')

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

We wrote three test methods in the `OddTestCase` class to ensure that when invalid arguments are passed, the corresponding error is raised.

The `test_negative_val()` method asserts that `ValueError` is raised when `gen_odd(-5)` is called.

Similarly, the `test_float_val()` and `test_string_val()` methods assert that when `gen_odd(99.9)` and `gen_odd('10')` are called, respectively, `TypeError` is raised.


![exception checking output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ehpn2b75vzum3avwcoh5.png)

All three tests in the above code passed, which means they all raised corresponding errors, otherwise, the tests would have failed or raised the errors if another exception was raised. Let's put it to the test.

```python
from odd import gen_odd
import unittest

class OddTestCase(unittest.TestCase):

    def test_valid_arg(self):
        with self.assertRaises(TypeError, msg="Valid argument"):
            gen_odd(10)

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

The above condition within the `test_valid_arg()` method will not throw a `TypeError` because `gen_odd()` function is passed with a valid argument.


![OddTestCase output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tep9lrx4nvz8j2ckd56w.png)

The above test method failed and raised an `AssertionError` with the message `TypeError not raised : Valid argument`.

## Skipping Tests

The `unittest` makes use of the `skip()` decorator or `skipTest()` to skip any test method or whole test class on purpose, and we are required to specify the reason why the test is being skipped.

Consider the previous example's code, which we modified by adding the `skip()` decorator.

```python
from odd import gen_odd
import unittest

class OddTestCase(unittest.TestCase):

    @unittest.skip("Valid argument")
    def test_valid_arg(self):
        with self.assertRaises(TypeError):
            gen_odd(10)

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

It's clear that the above condition will fail and throw an `AssertionError`, so we skipped the testing on purpose.


![output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e9ykunqtcqdnanv6rg1w.png)

**What if we wanted to skip the test if a particular condition was true?** We can accomplish this by using the `skipIf()` decorator, which allows us to specify a condition and skip the test if it is true.

```python
from odd import gen_odd
import sys
import unittest

class OddTestCase(unittest.TestCase):

    @unittest.skipIf(sys.getsizeof(gen_odd(10)) > 10, "Exceeded limit")
    def test_memory_use(self):
        self.assertTrue(sys.getsizeof(gen_odd(10)) > 10)
        print(f"Size: {sys.getsizeof(gen_odd(10))} bytes")

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

The condition in the above `skipIf()` decorator checks whether the size of `gen_odd(10)` is greater than `10` bytes, if the condition is **true**, the test method `test_memory_use()` is skipped, otherwise, the test is executed.


![slipIf output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x85wavdimgaxgdbyegrd.png)

### Expected Failure

If we have a test method or test class with conditions that are expected to be false, we can use the `expectedFailure()` decorator to mark them as expected failures instead of checking for errors.

```python
from odd import gen_odd
import sys
import unittest

class OddTestCase(unittest.TestCase):

    @unittest.expectedFailure
    def test_memory_use(self):
        self.assertTrue(sys.getsizeof(gen_odd(10)) < 10, msg="Expected to be failed")
        print(f"Size: {sys.getsizeof(gen_odd(10))} bytes")

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

We've modified the previous code and the condition we are checking inside the `test_memory_use()` method is expected to be false, which is why the method is decorated with the `@unittest.expectedFailure` decorator.


![expected failure output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cc74qsx7e4pcrc8m6zp0.png)

## Conclusion

We can use the `unittest` module to write and run tests to ensure that the code is working properly. The test can result in one of three outcomes: OK, FAIL, or Error.

The `unittest` module provides several assertion methods that are used to validate the code.

Let's recall, what we've learned:

* the basic usage of `unittest` module.
    
* CLI commands to run the tests.
    
* testing if the condition is raising an exception.
    
* skipping the tests on purpose and when a certain condition is true.
    
* marking a test as an expected failure.
    

---

🏆**Other articles you might be interested in if you liked this one**

✅[How to use assert statements for debugging in Python](https://geekpython.in/python-assert)?

✅[What are the uses of asterisk(\*) in Python](https://geekpython.in/asterisk-in-python)?

✅[What are \_\_init\_\_ and \_\_new\_\_ methods in Python](https://geekpython.in/init-vs-new)?

✅[How to implement getitem, setitem and delitem in Python classes](https://geekpython.in/implement-getitem-setitem-and-delitem-in-python)?

✅[How to change the string representation of the object using str and repr methods](https://geekpython.in/str-and-repr-in-python)?

✅[How to generate temporary files and directories using tempfile in Python](https://geekpython.in/tempfile-in-python)?

✅[Build a custom deep learning model using the transfer learning technique](https://geekpython.in/using-transfer-learning-for-deep-learning-model).

---

**That's all for now**

**Keep coding✌✌**