How To Write Tests For Python

5 min read ⋅ 3097 views

An important part of development in any Python project is testing. Testing is used to help define the intent of your code and provide a more decoupled architecture.

At a high-level unit testing is the practice of testing functions or units of code. Thanks to this we are able to verify that our code works correctly. For example, a test could be used to check a certain function returns the correct values and handles invalid inputs correctly. The objective here is to check for failures in the code or logic so that the function can be improved. As you write more tests, you begin to build a suite of tests for the project that can be run at any time to aid you in the development of your software.

Many believe that writing programs from a testing perspective is best practice. This is because you are more likely to write smaller functions focused on performing a specific operation, rather than a large function tasked with performing multiple operations.

Now obviously writing all these tests is going to take time, but it is far more beneficial to have well-tested code with a number of unit tests as it means you can prevent future changes from breaking functionality.

A Few Things to Consider

There are certain rules that should be followed when writing unit tests as they give you solid guidelines to follow for the testing process. Here are some general rules you should aim to follow.

Write fast tests - You should always want your tests to run as fast as possible. We’re talking a few milliseconds max. Any longer and you risk slowing development down. There is also the possibility that the tests will not be run as often as desired if they take too long to complete. Of course, some tests simply can't be fast, especially if they are required to test more complex code. These tests should instead be kept in a separate test suite and run by a scheduled task.

Focus on one piece of functionality - Each testing unit should test one specific thing and aim to prove it correct.

Use long and descriptive names - When you are writing and running code, it is preferred to use short names for methods but with tests you want to be descriptive and precise. This is because testing functions are never called explicitly. For example circumference() is fine when running code but for testing, you would want something like test_circumference_of_circle(). These function names will be displayed when a test fails so you should want them to be as descriptive as possible.

Each unit must be independent - Regardless of the order they are called, each test unit must be able to run independently as well as within the testing suite. This rule implies that each test must be loaded with fresh data and could be required to do some cleanup afterwards.

By following these guidelines you ensure a best practice approach to your testing which not only helps you but your peers as well.

How to Write a Test

There are a few options available for writing Python tests. The most notable ones are Unittest, Nose and PyTest. It is rather subjective as to which one you use but each of them have their own positives and negatives. In the example below, we will be using PyTest as it is probably the most simple of the three. It is also considered the best by many programmers.

First of all, PyTest needs to be installed. This can be done using pip. Simply enter the following command into a Python shell.

pip install -U pytest

You can check the version of pytest with the following command.

pytest --version

Now that pytest is installed we need to write a couple functions to test. Here are the two functions we will be using for this example. This file is named calc.py.

def calc_total(a,b):

return a + b


def calc_multiply(a,b):

return a * b

To test these functions, first we need to create a separate file and call it test_calc.py. Then we need to import calc.py and write some testing functions. The code should look something like this.

import calc


def test_calc_total():

total = calc.calc_total(2,3)

assert total == 5


def test_calc_multiply():

result = calc.calc_multiply(4,5)

assert result == 20

You can see at the top we have imported the calc.py module we created earlier. We have then created two functions that test each of the calc.py functions respectively. The first function tests whether or not the calc_total function produces the correct result. If the function returns a 5 then it is working correctly and will receive a pass. Anything else and the test will give us a failure. The same rules apply to the second function except that this time we are checking that 20 is returned when 4 and 5 are multiplied together.

A key thing to notice here is the assert statement. Assert statements are at the core of what a test is. A test function or method should only ever have one assert statement. The idea is to set up the required input data, call a function or method from the source file and then confirm the result is as expected using the assert statement. Although on its own assert doesn’t provide much information when an assertion fails, PyTest will expand the output to give more context.

Running The Test

To actually run the test you will first want to open up a terminal. Then thanks to the brilliance of PyTest we just need to run the following command in this scenario.

$ py.test

Now we can see the output below.

$ py.test

============================= test session starts ==============================

platform linux -- Python 3.6.3, pytest-3.6.0, py-1.5.3, pluggy-0.6.0

rootdir: /home/sam, inifile:

collected 2 items

pyzo-projects/test_calc.py .. [100%]

=========================== 2 passed in 0.05 seconds ===========================

As you can see, both functions passed the test. This is confirmed by the bottom line of the output. For comparison this the sort of output you could expect if a test fails.

$ py.test

============================= test session starts ==============================

platform linux -- Python 3.6.3, pytest-3.6.0, py-1.5.3, pluggy-0.6.0

rootdir: /home/sam, inifile:

collected 2 items

pyzo-projects/test_calc.py F. [100%]

=================================== FAILURES ===================================

_______________________________ test_calc_total ________________________________

def test_calc_total():

calc.calc_total(2,3)

> assert total == 5

E NameError: name 'total' is not defined

pyzo-projects/test_calc.py:5: NameError

====================== 1 failed, 1 passed in 0.09 seconds ======================

As mentioned above, PyTest has given us useful information as to why the test was a failure. In this case, there was NameError. The issue was actually in the test file itself. Unlike in the previous example, ‘total’ had not been defined as a variable and therefore caused an error when called by the assert statement.

Final Thoughts

Now, it’s important to point out that these are very simple functions and the chances of failure were always going to be minimal. The purpose of this example is just to give you an idea of how to write and run tests for your projects.

As you gain more knowledge and experience your tests will almost certainly become more complicated. This is pretty much unavoidable, but by following the correct guidelines you can minimise the complexities and ensure that testing remains somewhat easy and straightforward.



Python