Pytest
Links
Install
On Debian:
sudo apt install python3-pytest # Run pytest as 'pytest-3'
Using pip:
python3 -m pip install pytest # Run pytest as 'pytest'
Usage
Basic
Create a file test_basic.py:
def solve(a,b):
return a+b
def test_basic():
assert solve(2,3) == 5
Simply run pytest:
pytest-3
# ========================================================= test session starts =========================================================
# platform linux -- Python 3.7.3, pytest-3.10.1, py-1.7.0, pluggy-0.8.0
# rootdir: /home/peetersm/tmp/_delme/pytest/one, inifile:
# collected 1 item
#
# test_basic.py . [100%]
#
# ====================================================== 1 passed in 0.01 seconds =======================================================
pytest runs all functions matching test_*
in all files matching test_*.py
.
setup / teardown
pytest supports several kind of setup / teardown, also known as fixtures [1]:
- at the beginning and end of a module of test code (
setup_module
/teardown_module
) - at the beginning and end of a class of test methods (
setup_class
/teardown_class
) - alternate style of the class level fixtures (
setup
/teardown
) - before and after a test function call (
setup_function
/teardown_function
) - before and after a test method call (
setup_method
/teardown_method
)
Example from pythontesting.net:
from unnecessary_math import multiply
def setup_module(module):
print ("setup_module module:%s" % module.__name__)
def teardown_module(module):
print ("teardown_module module:%s" % module.__name__)
def setup_function(function):
print ("setup_function function:%s" % function.__name__)
def teardown_function(function):
print ("teardown_function function:%s" % function.__name__)
def test_numbers_3_4():
print 'test_numbers_3_4 <============================ actual test code'
assert multiply(3,4) == 12
def test_strings_a_3():
print 'test_strings_a_3 <============================ actual test code'
assert multiply('a',3) == 'aaa'
class TestUM:
def setup(self):
print ("setup class:TestStuff")
def teardown(self):
print ("teardown class:TestStuff")
def setup_class(cls):
print ("setup_class class:%s" % cls.__name__)
def teardown_class(cls):
print ("teardown_class class:%s" % cls.__name__)
def setup_method(self, method):
print ("setup_method method:%s" % method.__name__)
def teardown_method(self, method):
print ("teardown_method method:%s" % method.__name__)
def test_numbers_5_6(self):
print 'test_numbers_5_6 <============================ actual test code'
assert multiply(5,6) == 30
def test_strings_b_2(self):
print 'test_strings_b_2 <============================ actual test code'
assert multiply('b',2) == 'bb'
Fixtures
- https://docs.pytest.org/en/stable/fixture.html
- https://pythontesting.net/framework/pytest/pytest-fixtures-nuts-bolts/
The basic case, add the fixture as a test function parameter. The fixture uses the decorator
# content of ./test_smtpsimple.py
import pytest
@pytest.fixture
def smtp_connection():
import smtplib
return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
assert 0 # for demo purposes
Another example:
@pytest.fixture()
def before():
print('\nbefore each test')
def test_1(before):
print('test_1()')
def test_2(before):
print('test_2()')
A fixture can also be called explicitly using a decorator at test function level:
@pytest.mark.usefixtures("before")
def test_1():
print('test_1()')
@pytest.mark.usefixtures("before")
def test_2():
print('test_2()')
A fixture can have teardown code using the fixture request param and request.addfinalizer(fin)
:
@pytest.fixture()
def cheese_db(request):
# ...
def fin():
print('\n[teardown] cheese_db finalizer, disconnect from db')
request.addfinalizer(fin)
Alternatively, use yield
(more readable than finalizer, but teardown is not called if exception occurs in fixture code [2],[3]):
@pytest.fixture(scope="module")
def smtp_connection():
smtp_connection = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
yield smtp_connection # provide the fixture value
print("teardown smtp")
smtp_connection.close()
# Also works in "with" statement:
@pytest.fixture(scope="module")
def smtp_connection():
with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection:
yield smtp_connection # provide the fixture value
See links above for more on fixtures:
- fixture scope (function, class, module, package, session).
Capture stdout / stderr
Links:
A simple example:
def hello():
print("Hello")
def test_hello(capsys):
hello()
captured = capsys.readouterr()
assert captured.out == "Hello\n"
- Tips
- Capturing stdout / stderr means these will not be printed in case of failure, which is not handy to debug. This can be changed with option
--capture=tee-sys
since pytest 5.4 [4]:
pytest --capture=tee-sys ...
Tips
Capture SystemExit
Source: https://medium.com/python-pandemonium/testing-sys-exit-with-pytest-10c6e5f7726f
def test_exit(mymodule):
with pytest.raises(SystemExit) as e:
mymodule.will_exit_somewhere()
assert e.type == SystemExit
assert e.value.code == 42