Unit Testing in Python

Pawan Sadhwani
Fasal Engineering
Published in
6 min readDec 29, 2022

Why do we need testing?

Software testing catches bugs and errors in our software before it goes to the field or before it goes live to the customers. Bugs can hurt the business, and the user experience of the customers, and make them shift to different software and no one wants that.

Testing is done in a multi-layered approach of testing and each layer provides a different layer of safety for catching bugs.

The lowest level of testing is Unit testing.

What is Unit testing?

Unit testing is testing isolated units of codes to ensure that each unit is working correctly as expected. Here an isolated unit of code means a single function or a single method, the smallest unit of code that can run independently and can be independently operated.

Why do we need Unit testing?

Unit testing ensures code works correctly as expected. It reduces the chance of bugs in the future and improves the ability to refactor our existing codes. It also acts as additional documentation of what code is doing and how to use it. Unit test is written in such a way that it tests a method for all its positive and negative scenarios and this makes a layer of safety that can detect bugs in the future as well. Execution of test cases should be automated.

That’s a lot about what is unit testing and why it is needed. Now let us understand it with an example that shows how it actually works and how unit tests are written. I will be showing you how to write unit tests in python using the pytest module. First, let’s understand what is pytest and why are we using it.

What is Pytest and why is it used?

Pytest is a python unit testing framework that provides the ability to create tests, test fixtures, and test modules. It has the command line parameter to help to filter which test case should be executed and in what order. It uses python's built-in assert statement which makes it simpler to use compared to other python unit testing frameworks.

Features of PyTest

  • Automatic detection of the test files

Pytest automatically finds test files with the suffix “_test” or the prefix “test_” and runs without specifying the test directory or test files.

Eg:- test_fibonacci.py , fibonacci_test.py.

  • Parametrisation

It enables us to parametrize the input argument and the expected output so that we can try it for various inputs and outputs to check if the unit is working as expected or not.

E.g.

  • Grouping test cases using markers

By using markers we can divide our test functions into groups. This helps in testing some specific functionality of our code present in some different files by just adding pytest.mark.marker_name. It is that simple with pytest and also by marking we are also specifying which part of the code is responsible for which functionality of our software.

E.g., if we want to check whether a function is raising an exception for a particular scenario we can just mark it by using pytest.mark.CheckForExceptions.

  • Skip

Markers can come in with amazing features which can be helpful when we want to skip some functions or if we want to test whether some test cases are failing or not as expected, we can use the below marker:

@pytest.mark.skip(reason="testing the skip marker")
def test_skip():
...

Now this will not fail as we have marked it as skipped.

  • Skipif

It will skip test functions according to some conditions.

E.g.

@pytest.mark.skipif(input > 10000)
def test_function():
...

This marker will skip every time the input value reaches above 10000 so this can help us to save a lot of time and make it simple to write test cases.

  • XFail

By providing this marker to a function pytest will expect the function to fail.

@pytest.mark.xfail
  • Fixtures

Fixtures are used to set up some variables or some pre-requirements before every test case will run. We can use it to specify the input arguments of various functions or we can set up a mock database connection or an API request. This will help us to organize our code better and also it will not be required to define those in each function individually.

E.g.

  • Additional Functionality using different plugins

Pytest has a bunch of plugins that come in handy if you are developing and writing unit tests at a bigger level. Some of the widely used plugins are:

Pytest-cov: This pytest plugin allows you to generate test coverage reports and even can format that report into HTML or XML.

Pytest-mock: This pytest plugin allows you to mock and patch any external dependencies like database or API calls.

Pytest-flake8: This plugin allows you to lint wrap your python code or in simple words helps to check the syntax, styling, and complexity of your code.

Pytest-deadfixtures: This plugin allows you to find unused or duplicate fixtures.

And there are many more such plugins you can learn more about them in the pytest documentation.

So that’s a lot about PyTest and its features. Now let’s move toward a simple and easy-to-understand example for unit testing in python using PyTest.

Here is a simple code snippet for the nth Fibonacci number.

Firstly we have to think about all the negative and positive scenarios for the following function. The below code has some basic test cases written to test this function in python.

There should be as many test cases as a person can think of so by running the pytest command we can check whether our test cases passed or not.

We can also get more details about the test cases by running pytest -v command.

And also if you want to generate a test report for your test coverage just install pytest-cov using pip install pytest-cov then run the following command:

pytest — cov

If you want a report in HTML format then just run this command pytest — cov-report=html and a folder named htmlcov will get generated and will contain all your report files.

That’s all for this time and hope it helped you understand unit testing in python. Thank You :)

--

--