Pytest basics#

First, you need to install pytest and pyyaml:

pip install pytest
pip install pyyaml

Although you don’t have to write tests code but to understand it you should look at an example of a test. For example, there is the following code with check_ip function:

import ipaddress


def check_ip(ip):
    try:
        ipaddress.ip_address(ip)
        return True
    except ValueError as err:
        return False


if __name__ == "__main__":
    result = check_ip('10.1.1.1')
    print('Function result:', result)

Function check_ip checks whether the argument given to it is an IP address. An example of calling a function with different arguments:

In [1]: import ipaddress
   ...:
   ...:
   ...: def check_ip(ip):
   ...:     try:
   ...:         ipaddress.ip_address(ip)
   ...:         return True
   ...:     except ValueError as err:
   ...:         return False
   ...:

In [2]: check_ip('10.1.1.1')
Out[2]: True

In [3]: check_ip('10.1.')
Out[3]: False

In [4]: check_ip('a.a.a.a')
Out[4]: False

In [5]: check_ip('500.1.1.1')
Out[5]: False

Now it is necessary to write a test for check_ip function. Test must check that function returns True when correct address is passed and False when wrong argument is passed.

To simplify task, test can be written in the same file. In pytest, test can be a normal function with a name that starts with test_. Inside function you have to write conditions that are checked. In pytest this is done with assert.

assert#

assert does nothing if expression is True and generates an exception if expression is False:

In [6]: assert 5 > 1

In [7]: a = 4

In [8]: assert a in [1,2,3,4]

In [9]: assert a not in [1,2,3,4]
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-9-1956288e2d8e> in <module>
----> 1 assert a not in [1,2,3,4]

AssertionError:

In [10]: assert 5 < 1
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-10-b224d03aab2f> in <module>
----> 1 assert 5 < 1

AssertionError:

After assert and expression you can write a message. If there is a message, it is displayed in exception:

In [11]: assert a not in [1,2,3,4], "a not in a list"
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-11-7a8f87272a54> in <module>
----> 1 assert a not in [1,2,3,4], "a not in a list"

AssertionError: a not in a list

Test example#

pytest uses assert to specify which conditions must be met in order for test to be considered passed.

In pytest, you can write test as a normal function but function name must start with test_. Below is test_check_ip test which verify check_ip function by passing two values to it: correct address and wrong one, and after each check the message is written:

import ipaddress


def check_ip(ip):
    try:
        ipaddress.ip_address(ip)
        return True
    except ValueError as err:
        return False


def test_check_ip():
    assert check_ip('10.1.1.1') == True, 'If IP is correct, the fucntion returns True'
    assert check_ip('500.1.1.1') == False, 'If IP is wrong, the fucntion returns False'


if __name__ == "__main__":
    result = check_ip('10.1.1.1')
    print('Function result:', result)

Code is written in check_ip_functions.py. Now you have to figure out how to call tests. The easiest option is to write pytest word. In this case, pytest will automatically detect tests in the current directory. However, pytest has certain rules, not only by name of function but also by name of test files - file names should also start with test_. If rules are respected, pytest will automatically find tests, if not - you have to specify a test file.

In the case of example above, you have to call a command:

$ pytest check_ip_functions.py
========================= test session starts ==========================
platform linux -- Python 3.7.3, pytest-4.6.2, py-1.5.2, pluggy-0.12.0
rootdir: /home/vagrant/repos/general/pyneng.github.io/code_examples/pytest
collected 1 item

check_ip_functions.py .                                          [100%]

======================= 1 passed in 0.02 seconds =======================

By default if tests pass, each test (test_check_ip function) is marked with a dot. Since in this case there is only one test - test_check_ip function, there is a dot after name check_ip_functions.py and it is also written below that 1 test has passed.

Now, suppose the function does not work correctly and always returns False (write return False at the beginning of function). In this case, test execution will look like:

$ pytest check_ip_functions.py
========================= test session starts ==========================
platform linux -- Python 3.6.3, pytest-4.6.2, py-1.5.2, pluggy-0.12.0
rootdir: /home/vagrant/repos/general/pyneng.github.io/code_examples/pytest
collected 1 item

check_ip_functions.py F                                          [100%]

=============================== FAILURES ===============================
____________________________ test_check_ip _____________________________

    def test_check_ip():
>       assert check_ip('10.1.1.1') == True, 'If IP is correct, the fucntion returns True'
E       AssertionError: If IP is correct, the fucntion returns True
E       assert False == True
E        +  where False = check_ip('10.1.1.1')

check_ip_functions.py:14: AssertionError
======================= 1 failed in 0.06 seconds =======================

If test fails, pytest displays more information and shows where things went wrong. In this case, after execution of assert check_ip('10.1.1.1') == True string, the expression did not return True result, so an exception was generated.

Below, pytest shows what it has compared: assert False == True and specifies that False is check_ip('10.1.1.1'). Looking at the output, one suspects that something is wrong with check_ip function because it returns False to correct address.

Most tests are written in separate files. For this example, test is only one but it is still in a separate file.

File test_check_ip_function.py:

from check_ip_functions import check_ip


def test_check_ip():
    assert check_ip('10.1.1.1') == True, 'If IP is correct, the fucntion returns True'
    assert check_ip('500.1.1.1') == False, 'If IP is wrong, the fucntion returns False'

File check_ip_functions.py:

import ipaddress


def check_ip(ip):
    #return False
    try:
        ipaddress.ip_address(ip)
        return True
    except ValueError as err:
        return False


if __name__ == "__main__":
    result = check_ip('10.1.1.1')
    print('Function result:', result)

In that case, test can be run without specifying a file:

$ pytest
================= test session starts ========================
platform linux -- Python 3.6.3, pytest-4.6.2, py-1.5.2, pluggy-0.12.0
rootdir: /home/vagrant/repos/general/pyneng.github.io/code_examples/pytest
collected 1 item

test_check_ip_function.py .                              [100%]

================= 1 passed in 0.02 seconds ====================