Python pytest tutorial

Here’s a quick pytest basics walkthrough. Pytest is the most widely used package for testing in python. We are going to create few simple functions and test them.

Basic usage

Install pytest

pip install pytest

Create utils/math.py

def calc_total(a, b):
    return a+b

def calc_multiply(a, b):
    return a*b

Create empty utils/__init__.py to sign to python that utils directory is a package that can be imported.

Create test/test_utils_math.py

from utils.math import calc_multiply, calc_total

def test_calc_total():
    res = calc_total(4, 5)
    assert res == 9

def test_calc_multiply():
    res = calc_multiply(3, 5)
    assert res == 15

Files and functions should start with test_. Add __init__.py to test directory too.

To run the test use one of the following options:

python -m pytest

py.test

pytest

Result output:

======================= test session starts ========================
platform linux2 — Python 2.7.13, pytest-3.2.1, py-1.4.34, pluggy-0.4.0
rootdir: /home/oleg/projects/testdemo, inifile:
collected 2 items

test/test_utils_math.py ..

===================== 2 passed in 0.01 seconds =====================

To run with details (verbose), use:

pytest -v

Skip marker

To skip a function use skip marker.

For pytest decorators we need to import pytest. Add to test/test_utils_math.py:

import pytest
@pytest.mark.skip(reason="Deprecated test for now")
def test_calc_total():
    res = calc_total(4, 5)
    assert res == 9

To see reason of skipped functions use:

pytest -v -rsx

Conditional skip

@pytest.mark.skipif(sys.version < (3,5), reason="Skiping test because version less than 3.5")
def test_calc_total():
    res = calc_total(4, 5)
    assert res == 9

Filters

You can filter tests that you want to run.

Filter by function name. Run only functions that contain ‘multiply’ in name:

pytest -k multiply -v

You can filter by custom markers

To create custom marker add decorator:

@pytest.mark.custom_mark1
def test_calc_total():
    res = calc_total(4, 5)
    assert res == 9

To filter by custom marker use -m

pytest -m custom_mark1 -v

Run all except of specific custom mark

pytest -m "not custom_mark1" -v

setup_module and teardown_module functions

setup_module and teardown_module are functions that run on beginning and the end of a file execution. For example it can be useful for database opening and closing connection.

Create file utils/mydb.py (a dummy database package)

class MyDb:
    def __init__(self):
        self.connection = Connection()

    def connect(self, connection_string):
        return self.connection

class Connection:
    def __init__(self):
        self.cur = Cursor()

    def cursor(self):
        return self.cur

    def close(self):
        pass

class Cursor:
    def execute(self, query):
        if query == "select id from employee where name = 'John'":
            return 123
        elif query == "select id from employee where name = 'Tom'":
            return 555
        else:
            return -1

    def close(self):
        pass

Create test file test/test_utils_mydb.py:

from utils.mydb import MyDb

conn = None
cur = None

def setup_module(module):
    global conn
    global cur
    db = MyDb()
    conn = db.connect('server')
    cur = conn.cursor()

def teardown_module(module):
    global conn
    global cur
    cur.close()
    conn.close()

def test_johns_id():
    global cur
    id = cur.execute("select id from employee where name = 'John'")
    assert id == 123

def test_toms_id():
    global cur
    id = cur.execute("select id from employee where name = 'Tom'")
    assert id == 555

Fixtures

Fixture us a function that can be used as test function parameter. For example you can create fixture function that returns database object. Than you can add parameter with same name as this function to get the database object.

To make a fixture function add decorator:

@pytest.fixture

Modify test/test_utils_mydb.py

@pytest.fixture
def cur():
    print "setting connection"
    db = MyDb()
    conn = db.connect('server')
    cur = conn.cursor()
    yield cur
    cur.close()
    conn.close()
    print "connection closed"

def test_johns_id(cur):
    id = cur.execute("select id from employee where name = 'John'")
    assert id == 123

def test_toms_id(cur):
    id = cur.execute("select id from employee where name = 'Tom'")
    assert id == 555

Run the test.

To see print output add –capture=no

pytest -v --capture=no

You should notice that cur() fixture function runs before each test function. Sometimes maybe that’s what you want. But in our case we want it to run only once. We can achieve it by adding (scope=”module”):

@pytest.fixture(scope="module")

Parameterized test

Sometimes you want to run same function for different cases. For demonstration let’s add calc_square() function to utils/math.py:

def calc_square(num):
    return num*num

Now we would like to test that 2^2=4, 5^2=25 and 10^2=100. For that add to test/test_utils_math.py the following test function:

@pytest.mark.parametrize("test_input, expected_output",
                         [
                             (2, 4),
                             (5, 25),
                             (10, 100),
                         ])
def test_calc_square(test_input, expected_output):
    res = calc_square(test_input)
    assert res == expected_output

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s