>
Writing Automation Test Scripts with Python

Testing is an important aspect of the software development lifecycle. Long gone are the days where software was 100% manually tested. Nowadays, most companies are leveraging automation tools as part of the testing lifecycle. These tools help to control and automate the execution of tests on each commit or update to the codebase.

Test automation is the key to a successful project that follows the Agile or DevOps methodology. It significantly improves the efficiency and accuracy of your product during each release. In this tutorial, we are going to learn about a Python testing module called pytest.

Based on its official documentation, pytest is:

“… an open-source framework to write small tests, yet scales to support complex functional testing for applications and libraries.”

Let’s proceed to the next section and start installing the necessary modules.

Setup

It is highly recommended to create a virtual environment before you continue. Activate your virtual environment and run the following command:

pip install pytest

You can confirm the installation via:

pip show pytest

or

pytest -h

The Basics

In this section, we are going to learn about the fundamental concepts behind pytest.

To begin, create a new Python file in your working directory called utils.py. The file serves as the utility functions for our test case. Add the following code inside the file:

def add(x, y):
return x + y
def subtract(x, y):
return x - y
def multiply(x, y):
return x * y

Creating a Test Script

After that, create a new Python file called test_app.py. You can name it anything that you prefer but it must follow either of the following syntax:

– starts with test_ or,
– ends with _test.

Let’s create a few test functions inside test_app.py file. pytest automatically executes functions that start with test_ prefix. All you need to do is to add assertions inside the functions. For example:

import utils
def test_addition():
assert utils.add(2, 4) == 6
def test_subtraction():
assert utils.subtract(9, 4) == 5
def test_multiplication():
assert utils.multiply(3, 3) == 9

Running the Test

Once you are done with it, run the following command at your terminal:

pytest

You should see the following output:

You can specify -v argument for verbose output:

pytest -v

Running the command above yields the following output:

Let’s create a failed test case to understand how pytest reports errors. Inside test_app.py, change the following assertion from:

assert utils.multiply(3, 3) == 9

to:

assert utils.multiply(3, 3) == 8

Run the test again and you should get the following expected output for a failed test case:

Change the value back to 9 once you are done with it.

Grouping Test Cases into Classes

You can group some of the test functions inside a class. The class has to start with Test prefix for it to take effect:

class TestClass:
def test_addition(self):
assert utils.add(2, 4) == 6
def test_subtraction(self):
assert utils.subtract(9, 4) == 5
def test_multiplication(self):
assert utils.multiply(3, 3) == 9

Using Substring Expression

If you intend to run only a subset of the test functions, you can choose to do it via:

  • substring expression, or
  • marker.

For the first use case, simply specify the -k argument followed by the desired string. For example, the following command will only run functions that contain the substring add:

pytest -k add -v

Notice that pytest collected 3 functions and only ran one of it:

Using Markers

Furthermore, you can use markers as well to subset your test functions into different groups. Add the following import statement at the top of the file:

import pytest

Then, decorate the desired function with the following decorator. name represents the custom name for the marker. You can name it anything that you like.

@pytest.mark.<name>

I am going to mark two functions using basic as the name:

import utils
import pytest
class TestClass:
@pytest.mark.basic
def test_addition(self):
assert utils.add(2, 4) == 6
@pytest.mark.basic
def test_subtraction(self):
assert utils.subtract(9, 4) == 5
def test_multiplication(self):
assert utils.multiply(3, 3) == 9

For markers, you need to call -m argument instead as follows:

pytest -m basic -v

The following output will be displayed at your terminal:

Do not be alarmed by the warnings as it is just an indication that your marker is not registered yet. Simply create a new file called pytest.ini in the same directory. Add the following configuration inside it:

[pytest]
markers =
basic

In fact, you can add optional description to it as follows:

[pytest]
markers =
basic: mark test as basic

Parameterize Test Case

pytest comes with built-in parametrize marker which allows you to parameterize input value to your functions. Let’s modify test_multiplication function to accept two input parameters:

  • input value for multiplication
  • expected output

You should end up with the following code snippet:

def test_multiplication(self, value, output):
assert utils.multiply(value, value) == output

Then, decorate the function as follows:

@pytest.mark.parametrize("value, output", [(2, 4), (3, 9), (4, 16), (5, 25)])
def test_multiplication(self, value, output):
assert utils.multiply(value, value) == output

You must provide a string representation of the expected input as the first parameter to the decorator.

The second parameter represents the input values to be passed to the function during testing. Since I have specified 4 items in the list, pytest will run test_multiplication 4 times using the corresponding values.

You should get the following output when you run the test:

Besides, you can store the test data in a variable and reuse it again on other test functions:

data = [(2, 4), (3, 9), (4, 16), (5, 25)]

Our final test script is as follows:

import utils
import pytest
data = [(2, 4), (3, 9), (4, 16), (5, 25)]
class TestClass:
@pytest.mark.basic
def test_addition(self):
assert utils.add(2, 4) == 6
@pytest.mark.basic
def test_subtraction(self):
assert utils.subtract(9, 4) == 5
@pytest.mark.parametrize("value, output", data)
def test_multiplication(self, value, output):
assert utils.multiply(value, value) == output

Conclusion

Let’s recap what we have learned today.

We started off with a brief explanation of the importance of testing and why we should automate the testing process.

Next, we installed the pytest module in our virtual environment. We moved on to implement 3 basic functions and created the corresponding test functions.

Besides, we also learned to group functions create subset test cases using either substring expression or markers.

Finally, we explored decorating our test functions with parameterize decorator.

Thanks for reading this piece. Hope to see you again in the next article!

References

  1. Pytest’s Github
  2. Pytest’s Documentation
  3. Pytest’s Tutorialspoint
Show Comments