https://docs.pytest.org/en/latest/getting-started.html#create-your-first-test
http://www.testclass.net/pytest
【一】assert & raise
共三个相关文件 test_assert_one.py test_assert_two.py users.dev.json
test_assert_one.py
'''
[basic]
can't has chinese and chinese's symbol,otherwise,the error will run
'''
import pytest
def test_raise1():
with pytest.raises(ZeroDivisionError):
1/0
def test_raise2():
with pytest.raises(ValueError):
2/3
def test_raise3():
with pytest.raises(ZeroDivisionError):
1/2
"""
_________________________________ test_raise2 __________________________________
def test_raise2():
with pytest.raises(ValueError):
2/3
E Failed: DID NOT RAISE <type 'exceptions.ValueError'>
calc_test.py:12: Failed
_________________________________ test_raise3 __________________________________
def test_raise3():
with pytest.raises(ZeroDivisionError):
1/2
E Failed: DID NOT RAISE <type 'exceptions.ZeroDivisionError'>
calc_test.py:16: Failed
"""
[visit exception's basic info]
def test_raise4():
with pytest.raises(RuntimeError) as excinfo:
def f():
f()
f()
assert 'maximum2 recursion' in str(excinfo.value)
'''
_____________________________ test_recursion_depth _____________________________
def test_recursion_depth():
with pytest.raises(RuntimeError) as excinfo:
def f():
f()
f()
assert 'maximum2 recursion' in str(excinfo.value)
E assert 'maximum2 recursion' in 'maximum recursion depth exceeded'
E + where 'maximum recursion depth exceeded' = str(RuntimeError('maximum recursion depth exceeded',))
E + where RuntimeError('maximum recursion depth exceeded',) = .value
test_one.py:49: AssertionError
'''
custom the exc's message
def test_raise5():
with pytest.raises(ZeroDivisionError,message='expected ZeroDivError!!'):
1/2
'''
___________________________ test_custom_exc_message ____________________________
def test_custom_exc_message():
with pytest.raises(ZeroDivisionError,message='expected ZeroDivError!!'):
1/2
E Failed: expected ZeroDivError!!
test_one.py:71: Failed
'''
def test_raise6():
a = 10
b = 11
assert a%2 == 0,'value is odd,should be even'
assert b%2 == 0,'value is odd,should be even'
'''
_________________________________ test_raise6 __________________________________
def test_raise6():
a = 10
b = 11
assert a%2 == 0,'value is odd,should be even'
assert b%2 == 0,'value is odd,should be even'
E AssertionError: value is odd,should be even
E assert (11 % 2) == 0
test_one.py:88: AssertionError
'''
def myfunc():
raise ValueError('Exception 123 raised')
def test_raise7():
with pytest.raises(ValueError,match=r'.* 8888 .*'):
myfunc()
'''
no error , i don't kwow why.....
'''
[Making use of context-sensitive comparisons]
def test_set_comparision():
set1 = set('1308')
set2 = set('8035')
assert set1 == set2
'''
_____________________________ test_set_comparision _____________________________
def test_set_comparision():
set1 = set('1308')
set2 = set('8035')
assert set1 == set2
E assert set(['0', '1', '3', '8']) == set(['0', '3', '5', '8'])
E Extra items in the left set:
E '1'
E Extra items in the right set:
E '5'
E Use -v to get the full diff
test_one.py:122: AssertionError
'''
[Defining your own assertion comparison]
pytest_assertrepr_compare(config, op, left, right)
from test_assert_two import Foo
def pytest_assertrepr_compare(op, left, right):
if isinstance(left, Foo) and isinstance(right, Foo) and op == "==":
return ['Comparing Foo instances:',
' vals: %s != %s' % (left.val, right.val)]
test_assert_two.py
class Foo(object):
def init(self,val):
self.val = val
def __eq__(self, other):
return self.val == other.val
def test_compare():
f1 = Foo(1)
f2 = Foo(2)
assert f1 == f2
'''
if only do this , result as follow:
_________________________________ test_compare _________________________________
def test_compare():
f1 = Foo(1)
f2 = Foo(2)
assert f1 == f2
E assert <test_two.Foo object at 0x1063a6610> == <test_two.Foo object at 0x1063a6550>
test_two.py:11: AssertionError
if has pytest_assertrepr_compare() ,the result also the up ,don't know why????
but the docs is "
E assert Comparing Foo instances:
E vals: 1 != 2"
'''
users.dev.json
[
{"name":"jack","password":"Iloverose"},
{"name":"rose","password":"Ilovejack"},
{"name":"tom","password":"password123"}
]
【二】 fixture
test_fuxture_one.py 和 contest.py
test_fixture_one.py
coding=utf-8
we have file as 'users.dev.json' in the same directory
"""
import pytest
import json
class TestUserPassword(object):
@pytest.fixture
def users(self):
return json.loads(open('./users.dev.json','r').read())
def test_user_password(self,users):
for user in users:
password = user['password']
assert len(password) >= 6
msg= 'user %s has a weak password' % user['name']
assert password != 'password',msg
assert password != 'password123',msg
'''
pytest test_fixture.py
_____________________ TestUserPassword.test_user_password ______________________
self = <test_fixture_one.TestUserPassword object at 0x10b791f50>
users = [{'name': 'jack', 'password': 'Iloverose'}, {'name': 'rose', 'password': 'Ilovejack'}, {'name': 'tom', 'password': 'password123'}]
def test_user_password(self,users):
for user in users:
password = user['password']
assert len(password) >= 6
msg= 'user %s has a weak password' % user['name']
assert password != 'password',msg
assert password != 'password123',msg
E AssertionError: user tom has a weak password
E assert 'password123' != 'password123'
test_fixture_one.py:18: AssertionError
'''
[Fixtures as Function arguments]
'''
Test functions can receive fixture objects by naming them as an input argument.
For each argument name, a fixture function with that name provides the fixture object.
Fixture functions are registered by marking them with @pytest.fixture.
Let’s look at a simple self-contained test module containing a fixture and a test function using it:
Here, the test_ehlo needs the smtp_connection fixture value.
pytest will discover and call the @pytest.fixture marked smtp_connection fixture function.
'''
import pytest
@pytest.fixture
def smtp_connection():
import smtplib
return smtplib.SMTP("smtp.gmail.com", 586, timeout=5)
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
assert 0 # for demo purposes
'''
surendeMacBook-Air:~/PycharmProjects/liaoxuefeng_test1/888 suren$pytest test_fixture_one.py
============================= test session starts ==============================
platform darwin -- Python 2.7.13, pytest-3.0.5, py-1.4.32, pluggy-0.4.0
rootdir: /Users/suren/PycharmProjects/liaoxuefeng_test1/888, inifile:
collected 2 items
test_fixture_one.py FE
==================================== ERRORS ====================================
_________________________ ERROR at setup of test_ehlo __________________________
@pytest.fixture
def smtp_connection():
import smtplib
return smtplib.SMTP("smtp.gmail.com", 586, timeout=5)
test_fixture_one.py:48:
../../../ST/anaconda/anaconda/lib/python2.7/smtplib.py:256: in init
(code, msg) = self.connect(host, port)
../../../ST/anaconda/anaconda/lib/python2.7/smtplib.py:316: in connect
self.sock = self._get_socket(host, port, self.timeout)
../../../ST/anaconda/anaconda/lib/python2.7/smtplib.py:291: in _get_socket
return socket.create_connection((host, port), timeout)
address = ('smtp.gmail.com', 586), timeout = 5, source_address = None
def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
source_address=None):
#Connect to *address* and return the socket object.
#Convenience function. Connect to *address* (a 2-tuple (host,
#port)) and return the socket object. Passing the optional
#*timeout* parameter will set the timeout on the socket instance
#before attempting to connect. If no *timeout* is supplied, the
#global default timeout setting returned by :func:`getdefaulttimeout`
#is used. If *source_address* is set it must be a tuple of (host, port)
#for the socket to bind as a source address before making the connection.
#A host of '' or port 0 tells the OS to use the default.
#'''
host, port = address
err = None
for res in getaddrinfo(host, port, 0, SOCK_STREAM):
af, socktype, proto, canonname, sa = res
sock = None
try:
sock = socket(af, socktype, proto)
if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
sock.settimeout(timeout)
if source_address:
sock.bind(source_address)
sock.connect(sa)
return sock
except error as _:
err = _
if sock is not None:
sock.close()
if err is not None:
raise err
E error: [Errno 65] No route to host
../../../ST/anaconda/anaconda/lib/python2.7/socket.py:575: error
'''
import pytest
@pytest.fixture
def smtp_conn2():
import smtplib
return smtplib.SMTP('smtp.gmail.com',587,timeout=5)
def test_ehlo2(smtp_conn2):
response,msg = smtp_conn2.ehlo()
assert response == 250
'''
surendeMacBook-Air:~/PycharmProjects/liaoxuefeng_test1/888 suren$pytest test_fixture_one.py
============================= test session starts ==============================
platform darwin -- Python 2.7.13, pytest-3.0.5, py-1.4.32, pluggy-0.4.0
rootdir: /Users/suren/PycharmProjects/liaoxuefeng_test1/888, inifile:
collected 1 items
test_fixture_one.py .
=========================== 1 passed in 5.86 seconds ===========================
'''
import pytest
@pytest.fixture
def smtp_conn3():
import smtplib
return smtplib.SMTP('smtp.gmail.com',587,timeout=5)
def test_ehlo3(smtp_conn3):
response ,msg = smtp_conn3.ehlo()
assert response == 250
assert 0
'''
surendeMacBook-Air:~/PycharmProjects/liaoxuefeng_test1/888 suren$pytest test_fixture_one.py
============================= test session starts ==============================
platform darwin -- Python 2.7.13, pytest-3.0.5, py-1.4.32, pluggy-0.4.0
rootdir: /Users/suren/PycharmProjects/liaoxuefeng_test1/888, inifile:
collected 1 items
test_fixture_one.py F
=================================== FAILURES ===================================
__________________________________ test_ehlo3 __________________________________
smtp_conn3 = <smtplib.SMTP instance at 0x10e3fe488>
def test_ehlo3(smtp_conn3):
response ,msg = smtp_conn3.ehlo()
assert response == 250
assert 0
E assert 0
test_fixture_one.py:161: AssertionError
=========================== 1 failed in 5.90 seconds ===========================
[tips]
Note
You can always issue
pytest --fixtures test_simplefactory.py
to see available fixtures (fixtures with leading _ are only shown if you add the -v option).
'''
'''
surendeMacBook-Air:~/PycharmProjects/liaoxuefeng_test1/888 suren$pytest --fixtures test_fixture_one.py
============================= test session starts ==============================
platform darwin -- Python 2.7.13, pytest-3.0.5, py-1.4.32, pluggy-0.4.0
rootdir: /Users/suren/PycharmProjects/liaoxuefeng_test1/888, inifile:
collected 1 items
cache
Return a cache object that can persist state between testing sessions.
cache.get(key, default)
cache.set(key, value)
Keys must be a ``/`` separated value, where the first part is usually the
name of your plugin or application to avoid *es with other cache users.
Values can be any object handled by the json stdlib module.
capsys
Enable capturing of writes to sys.stdout/sys.stderr and make
captured output available via capsys.readouterr()
method calls
which return a (out, err)
tuple.
capfd
Enable capturing of writes to file descriptors 1 and 2 and make
captured output available via capfd.readouterr()
method calls
which return a (out, err)
tuple.
doctest_namespace
Inject names into the doctest namespace.
pytestconfig
the pytest config object with access to command line opts.
record_xml_property
Add extra xml properties to the tag for the calling test.
The fixture is callable with (name, value)
, with value being automatically
xml-encoded.
monkeypatch
The returned monkeypatch
fixture provides these
helper methods to modify objects, dictionaries or os.environ::
monkeypatch.setattr(obj, name, value, raising=True)
monkeypatch.delattr(obj, name, raising=True)
monkeypatch.setitem(mapping, name, value)
monkeypatch.delitem(obj, name, raising=True)
monkeypatch.setenv(name, value, prepend=False)
monkeypatch.delenv(name, value, raising=True)
monkeypatch.syspath_prepend(path)
monkeypatch.chdir(path)
All modifications will be undone after the requesting
test function or fixture has finished. The ``raising``
parameter determines if a KeyError or AttributeError
will be raised if the set/deletion operation has no target.
recwarn
Return a WarningsRecorder instance that provides these methods:
* ``pop(category=None)``: return last warning matching the category.
* ``clear()``: clear list of warnings
See http://docs.python.org/library/warnings.html for information
on warning categories.
tmpdir_factory
Return a TempdirFactory instance for the test session.
tmpdir
Return a temporary directory path object
which is unique to each test function invocation,
created as a sub directory of the base temporary
directory. The returned object is a py.path.local
_
path object.
-------------------- fixtures defined from test_fixture_one --------------------
smtp_conn3
test_fixture_one.py:169: no docstring available
'''
例子 conftest.py 和当前文件在同一目录下
def test_conn5(smtp_conn):
response,msg = smtp_conn.ehlo()
assert response == 250
'''
surendeMacBook-Air:~/PycharmProjects/liaoxuefeng_test1/888 suren$pytest test_fixture_one.py
============================= test session starts ==============================
platform darwin -- Python 2.7.13, pytest-3.0.5, py-1.4.32, pluggy-0.4.0
rootdir: /Users/suren/PycharmProjects/liaoxuefeng_test1/888, inifile:
collected 1 items
test_fixture_one.py .
=========================== 1 passed in 5.93 seconds ===========================
'''
例子 conftest.py 和当前文件不在同一目录下
def test_conn5(smtp_conn):
response,msg = smtp_conn.ehlo()
assert response == 250
assert 0
'''
============================= test session starts ==============================
platform darwin -- Python 2.7.13, pytest-3.0.5, py-1.4.32, pluggy-0.4.0
rootdir: /Users/suren/PycharmProjects/liaoxuefeng_test1/888, inifile:
collected 1 items
test_fixture_one.py F
=================================== FAILURES ===================================
__________________________________ test_conn5 __________________________________
smtp_conn = <smtplib.SMTP instance at 0x10578e3f8>
def test_conn5(smtp_conn):
response,msg = smtp_conn.ehlo()
assert response == 250
assert 0
E assert 0
test_fixture_one.py:312: AssertionError
=========================== 1 failed in 5.76 seconds ===========================
'''
例子 conftest.py 不叫 这个名字,换个 conftest1.py
def test_conn5(smtp_conn):
response,msg = smtp_conn.ehlo()
assert response == 250
'''
========================== 1 failed in 5.76 seconds ===========================
surendeMacBook-Air:~/PycharmProjects/liaoxuefeng_test1/888 suren$pytest test_fixture_one.py
============================= test session starts ==============================
platform darwin -- Python 2.7.13, pytest-3.0.5, py-1.4.32, pluggy-0.4.0
rootdir: /Users/suren/PycharmProjects/liaoxuefeng_test1/888, inifile:
collected 1 items
test_fixture_one.py E
==================================== ERRORS ====================================
_________________________ ERROR at setup of test_conn5 _________________________
file /Users/suren/PycharmProjects/liaoxuefeng_test1/888/test_fixture_one.py, line 341
def test_conn5(smtp_conn):
E fixture 'smtp_conn' not found
available fixtures: cache, capfd, capsys, doctest_namespace, monkeypatch, pytestconfig, record_xml_property, recwarn, tmpdir, tmpdir_factory
use 'pytest --fixtures [testpath]' for help on them.
/Users/suren/PycharmProjects/liaoxuefeng_test1/888/test_fixture_one.py:341
=========================== 1 error in 0.01 seconds ============================
'''
"""
conftest.py
import pytest
@pytest.fixture
def smtp_conn():
import smtplib
return smtplib.SMTP('smtp.gmail.com',587,timeout=5)
"""
例子 test_fixture_one.py在外部,conftest.py在与test_fixture_one.py同目录的文件夹内部
def test_ehlo(smtp_conn):
response,msg = smtp_conn.ehlo()
assert response == 250
assert b'smtp.gmail.com' in msg
assert 0
'''
C:/Users/rq/PycharmProjects/untitled>pytest test_fixture_one.py
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0
rootdir: C:/Users/rq/PycharmProjects/untitled, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 1 item
test_fixture_one.py E [100%]
=================================== ERRORS ====================================
________________________ ERROR at setup of test_conn6 _________________________
file C:/Users/rq/PycharmProjects/untitled/test_fixture_one.py, line 4
def test_conn6(smtp_conn):
E fixture 'smtp_conn' not found
available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, metadata, monkeypatch, pytestconfig, record_xml_attribute, record_xml_property, recwarn, tmpdir, tmpdir_factory, worker_id
use 'pytest --fixtures [testpath]' for help on them.
C:/Users/rq/PycharmProjects/untitled/test_fixture_one.py:4
=========================== 1 error in 0.02 seconds ===========================
'''
【Scope: sharing a fixture instance across tests in a class, module or session】
例子
def test_ehlo(smtp_conn):
response,msg = smtp_conn.ehlo()
assert response == 250
assert b'smtp.gmail.com' in msg
assert 0
def test_noop(smtp_conn):
response,msg = smtp_conn.noop()
assert response == 250
assert 0
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest test_one.py
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0
rootdir: C:/Users/rq/PycharmProjects/untitled/test1, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 2 items
test_one.py FF [100%]
================================== FAILURES ===================================
__________________________________ test_ehlo __________________________________
smtp_conn = <smtplib.SMTP object at 0x00000224E9266B70>
def test_ehlo(smtp_conn):
response,msg = smtp_conn.ehlo()
assert response == 250
assert b'smtp.gmail.com' in msg
assert 0
E assert 0
test_one.py:8: AssertionError
__________________________________ test_noop __________________________________
smtp_conn = <smtplib.SMTP object at 0x00000224E92EAA90>
def test_noop(smtp_conn):
response,msg = smtp_conn.noop()
assert response == 250
assert 0
E assert 0
test_one.py:13: AssertionError
========================== 2 failed in 1.36 seconds ===========================
'''
例子 如果拼错一个函数参数或想使用一个不可用的函数参数,将会看到一个包含可用函数参数列表的错误。我们可以使用以下命令看到可用的fixture:
pytest --fixtures test_simplefactory.py
def test_ehlo(smtp_conn1):
response, msg = smtp_conn1.ehlo()
assert response == 250
assert 0
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest test_one.py
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0
rootdir: C:/Users/rq/PycharmProjects/untitled/test1, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 1 item
test_one.py E [100%]
=================================== ERRORS ====================================
_________________________ ERROR at setup of test_ehlo _________________________
file C:/Users/rq/PycharmProjects/untitled/test1/test_one.py, line 87
def test_ehlo(smtp_conn1):
E fixture 'smtp_conn1' not found
available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, metadata, monkeypatch, pytestconfig, record_xml_attribute, record_xml_property, recwarn, smtp_conn, tmpdir, tmpdir_factory, w
orker_id
use 'pytest --fixtures [testpath]' for help on them.
C:/Users/rq/PycharmProjects/untitled/test1/test_one.py:87
=========================== 1 error in 0.02 seconds ===========================
'''
例子 @pytest.fixture(scope='module')
'''
[conftest.py]
import pytest
@pytest.fixture(scope='module')
def smtp_conn():
import smtplib
return smtplib.SMTP('smtp.gmail.com',587,timeout=5)
'''
def test_ehlo(smtp_conn):
response,msg = smtp_conn.ehlo()
assert response == 250
assert b'smtp.gmail.com' in msg
assert 0
def test_noop(smtp_conn):
response,msg = smtp_conn.noop()
assert response == 250
assert 0
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest test_one.py
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0
rootdir: C:/Users/rq/PycharmProjects/untitled/test1, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 2 items
test_one.py FF [100%]
================================== FAILURES ===================================
__________________________________ test_ehlo __________________________________
smtp_conn = <smtplib.SMTP object at 0x00000154E03D3B70>
def test_ehlo(smtp_conn):
response,msg = smtp_conn.ehlo()
assert response == 250
assert b'smtp.gmail.com' in msg
assert 0
E assert 0
test_one.py:134: AssertionError
__________________________________ test_noop __________________________________
smtp_conn = <smtplib.SMTP object at 0x00000154E03D3B70>
def test_noop(smtp_conn):
response,msg = smtp_conn.noop()
assert response == 250
assert 0
E assert 0
test_one.py:139: AssertionError
========================== 2 failed in 0.98 seconds ===========================
【分析】我们可以看到两个assert 0失败,更重要的是,我们也可以看到同一模块范围内的smtp对象被传递到两个测试函数中,
因为pytest显示了回溯中的传入参数值。因此,使用smtp的两个测试函数的运行速度与单个测试函数一样快,因为它们重用了相同的实例。
'''
例子
'''
同目录下conftest.py
import pytest
@pytest.fixture(scope='module')
def smtp_conn():
import smtplib
return smtplib.SMTP('smtp.gmail.com',587,timeout=5)
同目录下还有个文件test_two.py
def test_noop(smtp_conn):
resp,msg = smtp_conn.noop()
assert resp == 250
执行:pytest -s -v
'''
def test_ehlo(smtp_conn):
resp,msg = smtp_conn.ehlo()
assert resp == 250
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest -s -v
======================================================================================================= test session starts ========================================================================================================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0 -- c:/users/rq/appdata/local/programs/python/python37/python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.7.0', 'Platform': 'Windows-10-10.0.17134-SP0', 'Packages': {'pytest': '3.4.0', 'py': '1.7.0', 'pluggy': '0.6.0'}, 'Plugins': {'xdist': '1.22.0', 'rerunfailures': '4.1', 'metadata': '1.7.0', 'html': '1.16.1
', 'forked': '0.2'}, 'JAVA_HOME': 'C://Program Files//Java//jdk1.8.0_181'}
rootdir: C:/Users/rq/PycharmProjects/untitled/test1, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 2 items
test_one.py::test_ehlo PASSED
test_two.py::test_noop PASSED
===================================================================================================== 2 passed in 3.24 seconds =====================================================================================================
'''
例子 scope='session'运行结果同上
def test_ehlo(smtp_conn):
resp,msg = smtp_conn.ehlo()
assert resp == 250
例子 fixture的完成与拆卸代码
'''当fixture超出范围时候,pytest支持执行fixture特定的最终代码,通过使用yield而不是return,yield语句之后的所有代码都用做拆卸代码
[conftest.py]
import pytest
import smtplib
@pytest.fixture(scope='module')
def smtp_conn():
smtp = smtplib.SMTP('smtp.gmail.com',587,timeout=5)
yield smtp
print ('拆卸smtp')
smtp.close()
[test_two.py]
def test_noop(smtp_conn):
resp,msg = smtp_conn.noop()
assert resp == 250
'''
def test_noop(smtp_conn):
resp,msg = smtp_conn.noop()
assert resp == 250
def test_ehlo(smtp_conn):
resp,msg = smtp_conn.ehlo()
assert resp == 250
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest -s -q --tb=no
..拆卸smtp
.拆卸smtp
3 passed in 1.55 seconds
'''
例子 没有test_two.py fixture完成与拆卸代码
'''
[conftest.py]
import pytest
import smtplib
@pytest.fixture(scope='module')
def smtp_conn():
smtp = smtplib.SMTP('smtp.gmail.com',587,timeout=5)
yield smtp
print ('拆卸smtp')
smtp.close()
'''
def test_noop(smtp_conn):
resp,msg = smtp_conn.noop()
assert resp == 250
def test_ehlo(smtp_conn):
resp,msg = smtp_conn.ehlo()
assert resp == 250
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest -s -q --tb=no
..拆卸smtp
2 passed in 0.67 seconds
'''
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest -q --tb=no
.. [100%]
2 passed in 0.82 seconds
'''
例子 scope='function'
'''
[conftest.py]
import pytest
import smtplib
@pytest.fixture(scope='function')
def smtp_conn():
smtp = smtplib.SMTP('smtp.gmail.com',587,timeout=5)
yield smtp
print ('拆卸smtp')
smtp.close()
'''
def test_noop(smtp_conn):
resp,msg = smtp_conn.noop()
assert resp == 250
def test_ehlo(smtp_conn):
resp,msg = smtp_conn.ehlo()
assert resp == 250
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest -s -q --tb=no
.拆卸smtp
.拆卸smtp
2 passed in 2.65 seconds
'''
例子 我们可通过with语句无缝使用yield语法,这样测试完成后,smtp连接被关闭,因为当with语句结束时,smtp对象会自动关闭
'''
[conftest.py]
import pytest
import smtplib
@pytest.fixture(scope='module')
def smtp_conn():
with smtplib.SMTP('smtp.gmail.com',587,timeout=5) as smtp_conn:
yield smtp_conn
print ('拆卸代码')
'''
def test_noop(smtp_conn):
resp,msg = smtp_conn.noop()
assert resp == 250
def test_ehlo(smtp_conn):
resp,msg = smtp_conn.ehlo()
assert resp == 250
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest -s -q --tb=no
..拆卸代码
2 passed in 0.96 seconds
'''
例子 如果在设置代码,即yield关键字之前,期间发生异常,则不会调用拆卸代码,即即yield关键字之后的代码
'''
[conftest.py]
import pytest
import smtplib
@pytest.fixture(scope='module')
def smtp_conn():
with smtplib.SMTP('smtp.gmail.com',587,timeout=5) as smtp_conn:
yield smtp_conn
raise ValueError
print ('拆卸代码')
'''
def test_noop(smtp_conn):
resp,msg = smtp_conn.noop()
assert resp == 250
def test_ehlo(smtp_conn):
resp,msg = smtp_conn.ehlo()
assert resp == 250
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest -s -q --tb=no
..E
2 passed, 1 error in 1.31 seconds
'''
例子 执行拆卸代码的另一种方式是 利用请求上下文对象的addfinalizer方法来注册完成函数。
'''
[conftest.py]
import pytest
import smtplib
@pytest.fixture(scope='module')
def smtp_conn(request):
smtp = smtplib.SMTP('smtp.gmail.com',587,timeout=5)
def fin():
print ('拆卸代码')
smtp.close()
request.addfinalizer(fin)
return smtp
'''
def test_noop(smtp_conn):
resp,msg = smtp_conn.noop()
assert resp == 250
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest -s -q --tb=no
.拆卸代码
1 passed in 0.73 seconds
'''
'''
yield和addfinalizer方法在测试结束后通过调用它们的代码来工作,但addfinalizer与yield相比有两个关键的区别。第一点是,可以注册多个完成函数。第二点是,无论fixture设置代码是否引发异常,完成函数将始终被调用,即使其中一个未能创建与获取,也可以正确关闭由fixture创建的所有资源:
@pytest.fixture
def equipments(request):
r = []
for port in ('C1', 'C3', 'C28'):
equip = connect(port)
request.addfinalizer(equip.disconnect)
r.append(equip)
return r
在上面的代码中,如果“C28”发生异常,“C1”和“C3”仍然会被正确关闭。当然,如果在完成函数注册之前发生异常,那么它将不会被执行。
'''
"""
coding=utf-8
"""
例子 fixture反向获取请求的测试环境
'''
fixture函数可以通过接收request对象来反向获取请求中的测试函数、类或模块上下文,进一步扩展之前的smtp fixture
示例,让我们从fixture的测试模块读取可选的服务器URL
[conftest.py]
coding=utf-8
import pytest
import smtplib
@pytest.fixture(scope='module')
def smtp(request):
server = getattr(request.module,'smtpserver','smtp.qq.com')
smtp = smtplib.SMTP(server,587,timeout=5)
yield smtp
print ('完成 %s(%s) % (smtp,server)')
smtp.close()
'''
def test_elho(smtp):
resp, msg = smtp.ehlo()
assert resp == 250
'''
surendeMacBook-Air:~/PycharmProjects/liaoxuefeng_test1/888 suren$pytest -s -q --tb=no
.完成 <smtplib.SMTP instance at 0x10ac34200>(smtp.qq.com)
1 passed in 5.15 seconds
'''
例子 再让我们快速创建另一个测试模块,在其模块名称空间中实际设置服务器URL,
'''
新建一个test_anothersmtp.py文件,输入以下代码:
coding=utf-8
smtpserver = 'mail.python.org'
def test_showehlo(smtp):
assert 0,smtp.ehlo()
[conftest.py]
coding=utf-8
import pytest
import smtplib
@pytest.fixture(scope='module')
def smtp(request):
server = getattr(request.module,'smtpserver','smtp.qq.com')
smtp = smtplib.SMTP(server,587,timeout=5)
yield smtp
print ('完成 %s(%s) % (smtp,server)')
smtp.close()
执行结果:
surendeMacBook-Air:~/PycharmProjects/liaoxuefeng_test1/888 suren$pytest -qq --tb=short test_assert_one.py
F
=================================== FAILURES ===================================
________________________________ test_showehlo _________________________________
test_assert_one.py:6: in test_showehlo
assert 0,smtp.ehlo()
E AssertionError: (250, 'mail.python.org
E PIPELINING
E SIZE 51200000
E ETRN
E STARTTLS
E AUTH DIGEST-MD5 NTLM CRAM-MD5
E ENHANCEDSTATUSCODES
E 8BITMIME
E DSN
E SMTPUTF8
E CHUNKING')
E assert 0
--------------------------- Captured stdout teardown ---------------------------
完成 <smtplib.SMTP instance at 0x1086138c0>(mail.python.org)
'''
例子 参数化fixture
'''
fixture函数可以参数化,在这种情况下,它们将被多次调用,每次执行一组相关测试
即依赖于这个fixture的测试,测试函数通常不需要知道它们的重新运行。
fixture参数化可以用于一些有多种方式配置的功能测试。
扩展前面的例子,我们可以标记fixture来创建两个smtp fixture实例,
这将导致使用fixture的所有测试运行两次,fixture函数通过特殊的request对象访问每个参数:
[conftest.py]
coding=utf-8
import pytest
import smtplib
@pytest.fixture(scope='module',params=['smtp.qq.com','mail.python.org'])
def smtp(request):
smtp = smtplib.SMTP(request.param,587,timeout=5)
yield smtp
print ('完成 %s' % smtp)
smtp.close()
'''
def test_ehlo(smtp):
resp,msg = smtp.ehlo()
assert resp == 250
assert b'smtp.qq.com' in msg
def test_noop(smtp):
resp,msg = smtp.noop()
assert resp == 250
assert b'smtp.qq.com' in msg
'''
surendeMacBook-Air:~/PycharmProjects/liaoxuefeng_test1/888 suren$pytest -q test_fixture_one.py
.FFF
=================================== FAILURES ===================================
____________________________ test_noop[smtp.qq.com] ____________________________
smtp = <smtplib.SMTP instance at 0x10b8ea5a8>
def test_noop(smtp):
resp,msg = smtp.noop()
assert resp == 250
E assert 530 == 250
test_fixture_one.py:109: AssertionError
__________________________ test_ehlo[mail.python.org] __________________________
smtp = <smtplib.SMTP instance at 0x10b8ea9e0>
def test_ehlo(smtp):
resp,msg = smtp.ehlo()
assert resp == 250
assert b'smtp.qq.com' in msg
E assert 'smtp.qq.com' in 'mail.python.org\nPIPELINING\nSIZE 51200000\nETRN\nSTARTTLS\nAUTH DIGEST-MD5 NTLM CRAM-MD5\nENHANCEDSTATUSCODES\n8BITMIME\nDSN\nSMTPUTF8\nCHUNKING'
test_fixture_one.py:105: AssertionError
---------------------------- Captured stdout setup -----------------------------
完成 <smtplib.SMTP instance at 0x10b8ea5a8>
__________________________ test_noop[mail.python.org] __________________________
smtp = <smtplib.SMTP instance at 0x10b8ea9e0>
def test_noop(smtp):
resp,msg = smtp.noop()
assert resp == 250
assert b'smtp.qq.com' in msg
E assert 'smtp.qq.com' in '2.0.0 Ok'
test_fixture_one.py:110: AssertionError
--------------------------- Captured stdout teardown ---------------------------
完成 <smtplib.SMTP instance at 0x10b8ea9e0>
3 failed, 1 passed in 11.15 seconds
'''
例子
'''
[conftest.py]
coding=utf-8
import pytest
import smtplib
@pytest.fixture(scope='module')
def smtp():
smtp = smtplib.SMTP('smtp.gmail.com',587,timeout=5)
yield smtp
print ('完成 %s' % smtp)
smtp.close()
'''
def test_ehlo(smtp):
resp,msg = smtp.ehlo()
assert resp == 250
assert b'smtp.qq.com' in msg
def test_noop(smtp):
resp,msg = smtp.noop()
assert resp == 250
assert b'smtp.qq.com' in msg
'''
surendeMacBook-Air:~/PycharmProjects/liaoxuefeng_test1/888 suren$pytest -q test_fixture_one.py
FF
=================================== FAILURES ===================================
__________________________________ test_ehlo ___________________________________
smtp = <smtplib.SMTP instance at 0x107c8ed88>
def test_ehlo(smtp):
resp,msg = smtp.ehlo()
assert resp == 250
assert b'smtp.qq.com' in msg
E assert 'smtp.qq.com' in 'smtp.gmail.com at your service, [180.157.84.37]\nSIZE 35882577\n8BITMIME\nSTARTTLS\nENHANCEDSTATUSCODES\nPIPELINING\nCHUNKING\nSMTPUTF8'
test_fixture_one.py:105: AssertionError
__________________________________ test_noop ___________________________________
smtp = <smtplib.SMTP instance at 0x107c8ed88>
def test_noop(smtp):
resp,msg = smtp.noop()
assert resp == 250
assert b'smtp.qq.com' in msg
E assert 'smtp.qq.com' in '2.0.0 OK h64sm24958677pfc.142 - gsmtp'
test_fixture_one.py:110: AssertionError
--------------------------- Captured stdout teardown ---------------------------
完成 <smtplib.SMTP instance at 0x107c8ed88>
2 failed in 6.08 seconds
'''
"""
encoding=utf-8
'''
我们可以看到,我们的两个测试函数每个都运行了两次,而且是针对不同的smtp实例。pytest将建立一个字符串,它是参数化fixture中每个fixture值的测试ID,在上面的例子中,test_ehlo[smtp.qq.com]和test_ehlo[mail.python.org],这些ID可以与-k一起使用来选择要运行的特定实例,还可以在发生故障时识别特定实例。使用--collect-only运行pytest会显示生成的ID。
数字、字符串、布尔值和None将在测试ID中使用其通常的字符串表示形式,对于其他对象,pytest会根据参数名称创建一个字符串,可以通过使用ids关键字参数来自定义用于测试ID的字符串。新建一个test_ids.py文件,输入以下代码:
ids可以是一个要使用的字符串列表,还可以是一个将用fixture值调用的函数,然后返回一个字符串来使用。在后一种情况下,如果函数返回None,那么将使用pytest的自动生成的ID
'''
例子
"""
import pytest
@pytest.fixture(params=[2,5],ids=['spam','ham'])
def a(request):
return request.param
def test_a(a):
pass
def idfn(fixture_value):
if fixture_value == 5:
return 'eggs'
else:
return None
@pytest.fixture(params=[7,5],ids=idfn)
def b(request):
return request.param
def test_b(b):
pass
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest --collect-only
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0
rootdir: C:/Users/rq/PycharmProjects/untitled/test1, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 4 items
<Module 'test_one.py'>
<Function 'test_a[spam]'>
<Function 'test_a[ham]'>
<Function 'test_b[7]'>
<Function 'test_b[eggs]'>
======================== no tests ran in 0.02 seconds =========================
'''
例子
import pytest
@pytest.fixture(params=[0,1],ids=['spam','ham'])
def a(request):
return request.param
def test_a(a):
pass
def idfn(fixture_val):
if fixture_val == 1:
return 'eggs'
else:
return None
@pytest.fixture(params=[1,0],ids=idfn)
def b(request):
return request.param
def test_b(b):
pass
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest --collect-only
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0
rootdir: C:/Users/rq/PycharmProjects/untitled/test1, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 4 items
<Module 'test_one.py'>
<Function 'test_a[spam]'>
<Function 'test_a[ham]'>
<Function 'test_b[eggs]'>
<Function 'test_b[0]'>
======================== no tests ran in 0.02 seconds =========================
'''
例子
import pytest
@pytest.fixture(ids=['spam','ham'])
def a(request):
return request.param
def test_a(a):
pass
def idfn(val):
if val == 9:
return 'eggs'
else:
return None
@pytest.fixture(params=[3,9],ids=idfn)
def b(request):
return request.param
def test_b(b):
pass
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest --collect-only
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0
rootdir: C:/Users/rq/PycharmProjects/untitled/test1, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 3 items
<Module 'test_one.py'>
<Function 'test_a'>
<Function 'test_b[3]'>
<Function 'test_b[eggs]'>
======================== no tests ran in 0.02 seconds =========================
'''
例子
import pytest
@pytest.fixture(ids=['spam','ham'])
def a(request):
return 22
def test_a(a):
assert a == 22
def idfn(val):
if val == 9:
return 'eggs'
else:
return None
@pytest.fixture(params=[3,9],ids=idfn)
def b(request):
print (request.param)
return request.param
def test_b(b):
assert b in (1,9)
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest test_one.py
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0
rootdir: C:/Users/rq/PycharmProjects/untitled/test1, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 3 items
test_one.py .F. [100%]
================================== FAILURES ===================================
__________________________________ test_b[3] __________________________________
b = 3
def test_b(b):
assert b in (1,9)
E assert 3 in (1, 9)
test_one.py:157: AssertionError
---------------------------- Captured stdout setup ----------------------------
3
===================== 1 failed, 2 passed in 0.05 seconds ======================
'''
例子 使用fixture函数的fixture
'''
我们不仅可以在测试函数中使用fixture,而且fixture函数也可以使用其他fixture,这有助于fixture的模块化设计,
并允许在许多项目中重新使用框架特定的fixture。例如,我们可以扩展前面的例子,并实例化一个app对象
[conftest.py]
import pytest
import smtplib
@pytest.fixture(scope='module')
def smtp():
smtp = smtplib.SMTP('smtp.qq.com',587,timeout=5)
return smtp
'''
import pytest
class App(object):
def init(self,smtp):
self.smtp = smtp
@pytest.fixture(scope='module')
def app(smtp):
return App(smtp)
def test_smtp_exists(app):
assert app.smtp
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest -v test_one.py
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0 -- c:/users/rq/appdata/local/programs/python/python37/python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.7.0', 'Platform': 'Windows-10-10.0.17134-SP0', 'Packages': {'pytest': '3.4.0', 'py': '1.7.0', 'pluggy': '0.6.0'}, 'Plugins': {'xdist': '1.22.0', 'rerunfailures': '4.1', 'metadata': '1.7.0', 'html': '1.16.1
', 'forked': '0.2'}, 'JAVA_HOME': 'C://Program Files//Java//jdk1.8.0_181'}
rootdir: C:/Users/rq/PycharmProjects/untitled/test1, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 1 item
test_one.py::test_smtp_exists PASSED [100%]
========================== 1 passed in 0.09 seconds ===========================
'''
例子 通过fixture实例自动分组测试
'''
pytest在测试运行期间会最小化活动fixture的数量,如果我们有一个参数化的fixture,那么所有使用它的测试将首先执行一个实例,然后在下一个fixture实例被创建之前调用终结器。
'''
import pytest
@pytest.fixture(scope='module',params=['mod1','mod2'])
def modarg(request):
param = request.param
print (' 设置 modarg %s' % param)
yield param
print (' 拆卸 modarg %s' % param)
@pytest.fixture(scope='function',params=[1,2])
def otherarg(request):
param = request.param
print (' 设置 otherarg %s' % param)
yield param
print (' 拆卸 otherarg %s' % param)
def test_0(otherarg):
print (' 用 otherarg %s 运行 test0' % otherarg)
def test_1(modarg):
print (' 用 modarg %s 运行 test1' % modarg)
def test_2(otherarg,modarg):
print (' 用 otherarg %s 和 modarg %s 运行 test2' % (otherarg,modarg))
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest -v -s test_one.py
======================================================================================================= test session starts ========================================================================================================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0 -- c:/users/rq/appdata/local/programs/python/python37/python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.7.0', 'Platform': 'Windows-10-10.0.17134-SP0', 'Packages': {'pytest': '3.4.0', 'py': '1.7.0', 'pluggy': '0.6.0'}, 'Plugins': {'xdist': '1.22.0', 'rerunfailures': '4.1', 'metadata': '1.7.0', 'html': '1.16.1
', 'forked': '0.2'}, 'JAVA_HOME': 'C://Program Files//Java//jdk1.8.0_181'}
rootdir: C:/Users/rq/PycharmProjects/untitled/test1, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 8 items
test_one.py::test_0[1] 设置 otherarg 1
用 otherarg 1 运行 test0
PASSED 拆卸 otherarg 1
test_one.py::test_0[2] 设置 otherarg 2
用 otherarg 2 运行 test0
PASSED 拆卸 otherarg 2
test_one.py::test_1[mod1] 设置 modarg mod1
用 modarg mod1 运行 test1
PASSED
test_one.py::test_2[1-mod1] 设置 otherarg 1
用 otherarg 1 和 modarg mod1 运行 test2
PASSED 拆卸 otherarg 1
test_one.py::test_2[2-mod1] 设置 otherarg 2
用 otherarg 2 和 modarg mod1 运行 test2
PASSED 拆卸 otherarg 2
test_one.py::test_1[mod2] 拆卸 modarg mod1
设置 modarg mod2
用 modarg mod2 运行 test1
PASSED
test_one.py::test_2[1-mod2] 设置 otherarg 1
用 otherarg 1 和 modarg mod2 运行 test2
PASSED 拆卸 otherarg 1
test_one.py::test_2[2-mod2] 设置 otherarg 2
用 otherarg 2 和 modarg mod2 运行 test2
PASSED 拆卸 otherarg 2
拆卸 modarg mod2
===================================================================================================== 8 passed in 0.03 seconds =====================================================================================================
[解析]
我们可以看到参数化的module(模块)范围的modarg资源影响了测试执行的排序,使用了最少的活动资源。
mod1参数化资源的终结器是在mod2资源建立之前执行的。特别要注意test_0是完全独立的,会首先完成,
然后用mod1执行test_1,再然后用mod1执行test_2,再然后用mod2执行test_1,最后用mod2执行test_2。otherarg参数化资源是function(函数)的范围,是在每次使用测试之后建立起来的。
'''
例子 使用类、模块或项目的fixture
'''
有时测试函数不需要直接访问一个fixture对象,例如,测试可能需要使用空目录作为当前工作目录,不关心具体目录。这里使用标准的tempfile和pytest fixture来实现它,我们将fixture的创建分隔成一个conftest.py文件:
import pytest
import tempfile
import os
@pytest.fixture()
def cleandir():
newpath = tempfile.mkdtemp()
os.chdir(newpath)
'''
import os
import pytest
@pytest.mark.usefixtures('cleandir')
class TestDirectoryInit(object):
def test_cwd_starts_empty(self):
assert os.listdir(os.getcwd()) == []
with open('myfile','w') as f:
f.write('hello')
def test_cwd_again_starts_empty(self):
assert os.listdir(os.getcwd()) == []
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest -q
.. [100%]
2 passed in 0.02 seconds
[备注]
我们可以像这样指定多个fixture:
@pytest.mark.usefixtures("cleandir", "anotherfixture")
'''
例子 模块级别的fixture
'''
[conftest.py]
import pytest
import tempfile
import os
@pytest.fixture()
def cleandir():
newpath = tempfile.mkdtemp()
os.chdir(newpath)
'''
import pytest
import os
pytestmark = pytest.mark.usefixtures("cleandir")
class TestDirectoryInit(object):
def test_cwd_starts_empty(self):
assert os.listdir(os.getcwd()) == []
with open('myfile','w') as f:
f.write('hello')
class TestDirectoryInit2(object):
def test_cwd_again_starts_empty(self):
assert os.listdir(os.getcwd()) == []
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest -q
.. [100%]
2 passed in 0.02 seconds
'''
[注意]
分配的变量必须被称为pytestmark,例如,分配foomark不会激活fixture。
例子 我们可以将项目中所有测试所需的fixture放入一个pytest.ini文件中
'''
我们可以将项目中所有测试所需的fixture放入一个pytest.ini文件中,内容如下:
[pytest]
usefixtures = cleandir
[conftest.py]文件内容如下:
import pytest
import tempfile
import os
@pytest.fixture()
def cleandir():
newpath = tempfile.mkdtemp()
os.chdir(newpath)
同目录下[test_two.py]
import os
class TestDirectoryInit2(object):
def test_cwd_again_starts_empty(self):
assert os.listdir(os.getcwd()) == []
'''
import os
class TestDirectoryInit(object):
def test_cwd_starts_empty(self):
assert os.listdir(os.getcwd()) == []
with open('myfile','w') as f:
f.write('hello')
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest -q
.. [100%]
2 passed in 0.02 seconds
'''
例子 自动使用fixture
'''
有时候,我们可能希望自动调用fixture,而不是显式生命函数参数或使用usefixtures装饰器,例如,我们有一个数据库fixture,它有一个开始、回滚、提交的体系结构
我们希望通过一个事务和一个回滚自动地包含每一个测试方法
'''
import pytest
class DB(object):
def init(self):
self.intransaction = []
def begin(self,name):
self.intransaction.append(name)
def rollback(self):
self.intransaction.pop()
@pytest.fixture(scope='module')
def db():
return DB()
class TestClass(object):
@pytest.fixture(autouse=True)
def transaction(self,request,db):
db.begin(request.function.name)
yield
db.rollback()
def test_method1(self,db):
assert db.intransaction == ['test_method1']
def test_method2(self,db):
assert db.intransaction == ['test_method2']
'''
类级别的transact fixture被标记为autouse=true,
这意味着类中的所有测试方法将使用该fixture,而不需要在测试函数签名或类级别的usefixtures装饰器中陈述它。如果我们运行它,会得到两个通过的测试
C:/Users/rq/PycharmProjects/untitled/test1>pytest -q
.. [100%]
2 passed in 0.02 seconds
'''
'''
以下是在其他范围内如何使用自动fixture:
自动fixture遵循scope=关键字参数,如果一个自动fixture的scope='session',它将只运行一次,不管它在哪里定义。scope='class'表示每个类会运行一次,等等。
如果在一个测试模块中定义一个自动fixture,所有的测试函数都会自动使用它。
如果在conftest.py文件中定义了自动fixture,那么在其目录下的所有测试模块中的所有测试都将调用fixture。
最后,要小心使用自动fixture,如果我们在插件中定义了一个自动fixture,它将在插件安装的所有项目中的所有测试中被调用。例如,在pytest.ini文件中,这样一个全局性的fixture应该真的应该做任何工作,避免无用的导入或计算。
'''
例子 transaction 方法名字是自定义的,叫什么都行
例子 如果在conftest.py文件中定义了自动fixture,那么在其目录下的所有测试模块中的所有测试都将调用fixture
'''
[conftest.py]
import pytest
class DB(object):
def init(self):
self.intransaction = []
def begin(self,name):
self.intransaction.append(name)
def rollback(self):
self.intransaction.pop()
@pytest.fixture(scope='module')
def db():
return DB()
[test_two.py]
import pytest
class TestClass(object):
@pytest.fixture(autouse=True)
def transaction(self,request,db):
db.begin(request.function.name)
yield
db.rollback()
def test_method3(self,db):
assert db.intransaction == ['test_method3']
'''
import pytest
class TestClass(object):
@pytest.fixture(autouse=True)
def transaction(self,request,db):
db.begin(request.function.name)
yield
db.rollback()
def test_method1(self,db):
assert db.intransaction == ['test_method1']
def test_method2(self,db):
assert db.intransaction == ['test_method2']
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest -q
.. [100%]
3 passed in 0.02 seconds
'''
例子 transact fixture定义放在conftest.py文件
'''
[conftest.py]
import pytest
class DB(object):
def init(self):
self.intransaction = []
def begin(self,name):
self.intransaction.append(name)
def rollback(self):
self.intransaction.pop()
@pytest.fixture(scope='module')
def db():
return DB()
@pytest.fixture
def transact(request,db):
db.begin(request.function.name)
yield
db.rollback()
[test_two.py]
import pytest
class TestClass(object):
def test_method3(self,db):
assert db.intransaction == ['test_method3']
'''
import pytest
@pytest.mark.usefixtures('transact')
class TestClass(object):
def test_method1(self,db):
assert db.intransaction == ['test_method1']
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest -q
.F [100%]
================================== FAILURES ===================================
___________________________ TestClass.test_method3 ____________________________
self = <test1.test_two.TestClass object at 0x000001DAFFBBF278>
db = <test1.conftest.DB object at 0x000001DAFFBBF208>
def test_method3(self,db):
assert db.intransaction == ['test_method3']
E AssertionError: assert [] == ['test_method3']
E Right contains more items, first extra item: 'test_method3'
E Full diff:
E - []
E + ['test_method3']
test_two.py:8: AssertionError
1 failed, 1 passed in 0.05 seconds
'''
例子 上面的例子要通过的话 在test_two.py 的 class 上方加@pytest.mark.usefixtures('transact')
'''
[conftest.py]
import pytest
class DB(object):
def init(self):
self.intransaction = []
def begin(self,name):
self.intransaction.append(name)
def rollback(self):
self.intransaction.pop()
@pytest.fixture(scope='module')
def db():
return DB()
@pytest.fixture
def transact(request,db):
db.begin(request.function.name)
yield
db.rollback()
[test_two.py]
import pytest
@pytest.mark.usefixtures('transact')
class TestClass(object):
def test_method3(self,db):
assert db.intransaction == ['test_method3']
'''
import pytest
@pytest.mark.usefixtures('transact')
class TestClass(object):
def test_method1(self,db):
assert db.intransaction == ['test_method1']
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest -q
.. [100%]
2 passed in 0.02 seconds
'''
例子 pytest.ini 形式
'''
[pytest.ini]内容如下:
[pytest]
usefixtures = transact
[test_two.py]内容如下:
import pytest
class TestClass(object):
def test_method3(self,db):
assert db.intransaction == ['test_method3']
'''
class TestClass(object):
def test_method1(self,db):
assert db.intransaction == ['test_method1']
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest -q
.. [100%]
2 passed in 0.02 seconds
'''
【覆盖不同级别的fixture】
'''在相对较大的测试套件中,我们很可能需要使用本地定义的套件重写全局或根fixture,从而保持测试代码的可读性和可维护性。'''
例子 覆盖文件夹级别的fixture
'''
[test1/conftest.py]
import pytest
@pytest.fixture
def username():
return 'username'
[test1/subfolder/conftest.py]
import pytest
@pytest.fixture
def username(username):
return 'overridden-' + username
[test1/subfolder/test_two.py]
def test_username(username):
assert username == 'overridden-username'
'''
def test_username(username):
assert username == 'username'
'''
C:/Users/rq/PycharmProjects/untitled/test1>pytest -q
.. [100%]
2 passed in 0.02 seconds
'''
"""
例子 覆盖测试模块级别的fixture
import pytest
@pytest.fixture
def username(username):
return
coding=utf-8
"""
例子 覆盖测试模块级别的fixture 某个测试模块可以覆盖同名的fixture。
'''
同目录[conftest.py]
coding=utf-8
import pytest
@pytest.fixture
def user():
return 'user'
同目录[test_two.py]
coding=utf-8
import pytest
@pytest.fixture
def username(user):
return 'overridden-' + user
def test_username(username):
assert username == 'overridden-user'
'''
import pytest
@pytest.fixture
def username(user):
return 'overridden-else-' + user
def test_username(username):
assert username == 'overridden-else-user'
'''
surendeMacBook-Air:~/PycharmProjects/liaoxuefeng_test1/888 suren$pytest -q
..
2 passed in 0.02 seconds
'''
例子 直接用测试参数化覆盖fixture fixture值被测试参数值覆盖,即使测试不直接使用它,fixture的值也可以用这种方式重写。
'''
同目录[conftest.py]
coding=utf-8
import pytest
@pytest.fixture
def username():
return 'username'
@pytest.fixture
def other_username(username):
return 'other-' + username
'''
import pytest
@pytest.mark.parametrize('username',['directly-overridden-username'])
def test_username(username):
assert username == 'directly-overridden-username'
@pytest.mark.parametrize('username',['directly-overridden-username-other'])
def test_username_other(other_username):
assert other_username == 'other-directly-overridden-username-other'
'''
surendeMacBook-Air:~/PycharmProjects/liaoxuefeng_test1/888 suren$pytest -q
..
2 passed in 0.02 seconds
'''
例子 使用非参数化参数替代参数化的fixture
'''
一个参数化的fixture被一个非参数化的版本覆盖,一个非参数化的fixture被某个测试模块的参数化版本覆盖,这同样适用于测试文件夹级别。
同目录[conftest.py]
coding=utf-8
import pytest
@pytest.fixture(params=['one','two','three'])
def parametrized_username(request):
return request.param
@pytest.fixture
def non_parametrized_username(request):
return 'username'
同目录[test_two.py]
coding=utf-8
import pytest
def test_parametrized_username(parametrized_username):
assert parametrized_username in ['one','two','three']
def test_non_parametrized_username(non_parametrized_username):
assert non_parametrized_username == 'username'
'''
import pytest
@pytest.fixture
def parametrized_username():
return 'overridden-username'
@pytest.fixture(params=['one','two','three'])
def non_parametrized_username(request):
return request.param
def test_parametrized_username(parametrized_username):
assert parametrized_username == 'overridden-username'
def test_non_parametrized_username(non_parametrized_username):
assert non_parametrized_username in ['one','two','three']
'''
surendeMacBook-Air:~/PycharmProjects/liaoxuefeng_test1/888 suren$pytest -q
..
8 passed in 0.02 seconds
'''
"""
Parametrizing tests¶
pytest allows to easily parametrize test functions. For basic docs, see Parametrizing fixtures and test functions.
In the following we provide some examples using the builtin mechanisms.
Generating parameters combinations, depending on command line
Let’s say we want to execute a test with different computation parameters and the parameter range shall be determined by a command line argument. Let’s first write a simple (do-nothing) computation test:
content of test_compute.py
def test_compute(param1):
assert param1 < 4
Now we add a test configuration like this:
content of conftest.py
def pytest_addoption(parser):
parser.addoption("--all", action="store_true",
help="run all combinations")
def pytest_generate_tests(metafunc):
if 'param1' in metafunc.fixturenames:
if metafunc.config.getoption('all'):
end = 5
else:
end = 2
metafunc.parametrize("param1", range(end))
This means that we only run 2 tests if we do not pass --all:
$ pytest -q test_compute.py
.. [100%]
2 passed in 0.12 seconds
We run only two computations, so we see two dots. let’s run the full monty:
$ pytest -q --all
....F [100%]
================================= FAILURES =================================
_____________________________ test_compute[4] ______________________________
param1 = 4
def test_compute(param1):
assert param1 < 4
E assert 4 < 4
test_compute.py:3: AssertionError
1 failed, 4 passed in 0.12 seconds
As expected when running the full range of param1 values we’ll get an error on the last one.
Different options for test IDs
pytest will build a string that is the test ID for each set of values in a parametrized test. These IDs can be used with -k to select specific cases to run, and they will also identify the specific case when one is failing. Running pytest with --collect-only will show the generated IDs.
Numbers, strings, booleans and None will have their usual string representation used in the test ID. For other objects, pytest will make a string based on the argument name:
content of test_time.py
import pytest
from datetime import datetime, timedelta
testdata = [
(datetime(2001, 12, 12), datetime(2001, 12, 11), timedelta(1)),
(datetime(2001, 12, 11), datetime(2001, 12, 12), timedelta(-1)),
]
@pytest.mark.parametrize("a,b,expected", testdata)
def test_timedistance_v0(a, b, expected):
diff = a - b
assert diff == expected
@pytest.mark.parametrize("a,b,expected", testdata, ids=["forward", "backward"])
def test_timedistance_v1(a, b, expected):
diff = a - b
assert diff == expected
def idfn(val):
if isinstance(val, (datetime,)):
# note this wouldn't show any hours/minutes/seconds
return val.strftime('%Y%m%d')
@pytest.mark.parametrize("a,b,expected", testdata, ids=idfn)
def test_timedistance_v2(a, b, expected):
diff = a - b
assert diff == expected
@pytest.mark.parametrize("a,b,expected", [
pytest.param(datetime(2001, 12, 12), datetime(2001, 12, 11),
timedelta(1), id='forward'),
pytest.param(datetime(2001, 12, 11), datetime(2001, 12, 12),
timedelta(-1), id='backward'),
])
def test_timedistance_v3(a, b, expected):
diff = a - b
assert diff == expected
In test_timedistance_v0, we let pytest generate the test IDs.
In test_timedistance_v1, we specified ids as a list of strings which were used as the test IDs. These are succinct, but can be a pain to maintain.
In test_timedistance_v2, we specified ids as a function that can generate a string representation to make part of the test ID. So our datetime values use the label generated by idfn, but because we didn’t generate a label for timedelta objects, they are still using the default pytest representation:
$ pytest test_time.py --collect-only
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 8 items
<Module 'test_time.py'>
<Function 'test_timedistance_v0[a0-b0-expected0]'>
<Function 'test_timedistance_v0[a1-b1-expected1]'>
<Function 'test_timedistance_v1[forward]'>
<Function 'test_timedistance_v1[backward]'>
<Function 'test_timedistance_v2[20011212-20011211-expected0]'>
<Function 'test_timedistance_v2[20011211-20011212-expected1]'>
<Function 'test_timedistance_v3[forward]'>
<Function 'test_timedistance_v3[backward]'>
======================= no tests ran in 0.12 seconds =======================
In test_timedistance_v3, we used pytest.param to specify the test IDs together with the actual data, instead of listing them separately.
A quick port of “testscenarios”
Here is a quick port to run tests configured with test scenarios, an add-on from Robert Collins for the standard unittest framework. We only have to work a bit to construct the correct arguments for pytest’s Metafunc.parametrize():
content of test_scenarios.py
def pytest_generate_tests(metafunc):
idlist = []
argvalues = []
for scenario in metafunc.cls.scenarios:
idlist.append(scenario[0])
items = scenario[1].items()
argnames = [x[0] for x in items]
argvalues.append(([x[1] for x in items]))
metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class")
scenario1 = ('basic', {'attribute': 'value'})
scenario2 = ('advanced', {'attribute': 'value2'})
class TestSampleWithScenarios(object):
scenarios = [scenario1, scenario2]
def test_demo1(self, attribute):
assert isinstance(attribute, str)
def test_demo2(self, attribute):
assert isinstance(attribute, str)
this is a fully self-contained example which you can run with:
$ pytest test_scenarios.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items
test_scenarios.py .... [100%]
========================= 4 passed in 0.12 seconds =========================
If you just collect tests you’ll also nicely see ‘advanced’ and ‘basic’ as variants for the test function:
$ pytest --collect-only test_scenarios.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items
<Module 'test_scenarios.py'>
<Class 'TestSampleWithScenarios'>
<Function 'test_demo1[basic]'>
<Function 'test_demo2[basic]'>
<Function 'test_demo1[advanced]'>
<Function 'test_demo2[advanced]'>
======================= no tests ran in 0.12 seconds =======================
Note that we told metafunc.parametrize() that your scenario values should be considered class-scoped. With pytest-2.3 this leads to a resource-based ordering.
Deferring the setup of parametrized resources
The parametrization of test functions happens at collection time. It is a good idea to setup expensive resources like DB connections or subprocess only when the actual test is run. Here is a simple example how you can achieve that, first the actual test requiring a db object:
content of test_backends.py
import pytest
def test_db_initialized(db):
# a dummy test
if db.class.name == "DB2":
pytest.fail("deliberately failing for demo purposes")
We can now add a test configuration that generates two invocations of the test_db_initialized function and also implements a factory that creates a database object for the actual test invocations:
content of conftest.py
import pytest
def pytest_generate_tests(metafunc):
if 'db' in metafunc.fixturenames:
metafunc.parametrize("db", ['d1', 'd2'], indirect=True)
class DB1(object):
"one database object"
class DB2(object):
"alternative database object"
@pytest.fixture
def db(request):
if request.param == "d1":
return DB1()
elif request.param == "d2":
return DB2()
else:
raise ValueError("invalid internal test config")
Let’s first see how it looks like at collection time:
$ pytest test_backends.py --collect-only
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items
<Module 'test_backends.py'>
<Function 'test_db_initialized[d1]'>
<Function 'test_db_initialized[d2]'>
======================= no tests ran in 0.12 seconds =======================
And then when we run the test:
$ pytest -q test_backends.py
.F [100%]
================================= FAILURES =================================
_________________________ test_db_initialized[d2] __________________________
db = <conftest.DB2 object at 0xdeadbeef>
def test_db_initialized(db):
# a dummy test
if db.__class__.__name__ == "DB2":
pytest.fail("deliberately failing for demo purposes")
E Failed: deliberately failing for demo purposes
test_backends.py:6: Failed
1 failed, 1 passed in 0.12 seconds
The first invocation with db == "DB1" passed while the second with db == "DB2" failed. Our db fixture function has instantiated each of the DB values during the setup phase while the pytest_generate_tests generated two according calls to the test_db_initialized during the collection phase.
coding=utf-8
"""
例子 Apply indirect on particular arguments
'''
Very often parametrization uses more than one argument name.
There is opportunity to apply indirect parameter on particular arguments.
t can be done by passing list or tuple of arguments’ names to indirect.
In the example below there is a function test_indirect which uses two fixtures: x and y.
Here we give to indirect the list, which contains the name of the fixture x.
The indirect parameter will be applied to this argument only,
and the value a will be passed to respective fixture function:
'''
import pytest
from datetime import datetime,timedelta
testdata = [
(datetime(2001,12,12),datetime(2001,12,11),timedelta(1)),
(datetime(2001,12,11),datetime(2001,12,12),timedelta(-1)),
]
testdata3 = [
(3,2,1),
(5,2,3)
]
testdata2 = [
('a','b')
]
@pytest.mark.parametrize('a,b,expected',testdata)
def test_timedistance_v0(a,b,expected):
diff = a - b
assert diff == expected
assert 0
@pytest.mark.parametrize('x,y,expected',testdata3)
def test_testdata3(x,y,expected):
diff = x - y
assert diff == expected
assert 0
@pytest.fixture(scope='function')
def x(request):
return request.param*3
@pytest.fixture(scope='function')
def y(request):
return request.param*2
@pytest.mark.parametrize('x,y',testdata2, indirect=['y'])
def test_indirect(x,y):
assert x == 'a'
assert y == 'bb'
assert 0
'''
surendeMacBook-Air:~/PycharmProjects/liaoxuefeng_test1/888 suren$pytest test_fixture_one.py
============================= test session starts ==============================
platform darwin -- Python 2.7.13, pytest-3.0.5, py-1.4.32, pluggy-0.4.0
rootdir: /Users/suren/PycharmProjects/liaoxuefeng_test1/888, inifile:
collected 5 items
test_fixture_one.py FFFFF
=================================== FAILURES ===================================
____________________ test_timedistance_v0[a0-b0-expected0] _____________________
a = datetime.datetime(2001, 12, 12, 0, 0)
b = datetime.datetime(2001, 12, 11, 0, 0), expected = datetime.timedelta(1)
@pytest.mark.parametrize('a,b,expected',testdata)
def test_timedistance_v0(a,b,expected):
diff = a - b
assert diff == expected
assert 0
E assert 0
test_fixture_one.py:36: AssertionError
____________________ test_timedistance_v0[a1-b1-expected1] _____________________
a = datetime.datetime(2001, 12, 11, 0, 0)
b = datetime.datetime(2001, 12, 12, 0, 0), expected = datetime.timedelta(-1)
@pytest.mark.parametrize('a,b,expected',testdata)
def test_timedistance_v0(a,b,expected):
diff = a - b
assert diff == expected
assert 0
E assert 0
test_fixture_one.py:36: AssertionError
____________________________ test_testdata3[3-2-1] _____________________________
x = 3, y = 2, expected = 1
@pytest.mark.parametrize('x,y,expected',testdata3)
def test_testdata3(x,y,expected):
diff = x - y
assert diff == expected
assert 0
E assert 0
test_fixture_one.py:42: AssertionError
____________________________ test_testdata3[5-2-3] _____________________________
x = 5, y = 2, expected = 3
@pytest.mark.parametrize('x,y,expected',testdata3)
def test_testdata3(x,y,expected):
diff = x - y
assert diff == expected
assert 0
E assert 0
test_fixture_one.py:42: AssertionError
______________________________ test_indirect[a-b] ______________________________
x = 'a', y = 'bb'
@pytest.mark.parametrize('x,y',testdata2, indirect=['y'])
def test_indirect(x,y):
assert x == 'a'
assert y == 'bb'
assert 0
E assert 0
test_fixture_one.py:56: AssertionError
=========================== 5 failed in 0.06 seconds ===========================
'''
例子
import pytest
from datetime import datetime,timedelta
testdata = [
(datetime(2001,12,12),datetime(2001,12,11),timedelta(1)),
(datetime(2001,12,11),datetime(2001,12,12),timedelta(-1)),
]
testdata3 = [
(3,2,1),
(5,2,3)
]
testdata2 = [
('a','b')
]
@pytest.mark.parametrize('a,b,expected',testdata)
def test_timedistance_v0(a,b,expected):
diff = a - b
assert diff == expected
assert 0
@pytest.mark.parametrize('x,y,expected',testdata3)
def test_testdata3(x,y,expected):
diff = x - y
assert diff == expected
assert 0
@pytest.fixture(scope='function')
def x(request):
return request.param*3
@pytest.fixture(scope='function')
def y(request):
return request.param*2
@pytest.mark.parametrize('x,y',testdata2, indirect=['x','y'])
def test_indirect(x,y):
assert x == 'aaa'
assert y == 'bb'
assert 0
'''
surendeMacBook-Air:~/PycharmProjects/liaoxuefeng_test1/888 suren$pytest test_fixture_one.py
============================= test session starts ==============================
platform darwin -- Python 2.7.13, pytest-3.0.5, py-1.4.32, pluggy-0.4.0
rootdir: /Users/suren/PycharmProjects/liaoxuefeng_test1/888, inifile:
collected 5 items
test_fixture_one.py FFFFF
=================================== FAILURES ===================================
____________________ test_timedistance_v0[a0-b0-expected0] _____________________
a = datetime.datetime(2001, 12, 12, 0, 0)
b = datetime.datetime(2001, 12, 11, 0, 0), expected = datetime.timedelta(1)
@pytest.mark.parametrize('a,b,expected',testdata)
def test_timedistance_v0(a,b,expected):
diff = a - b
assert diff == expected
assert 0
E assert 0
test_fixture_one.py:158: AssertionError
____________________ test_timedistance_v0[a1-b1-expected1] _____________________
a = datetime.datetime(2001, 12, 11, 0, 0)
b = datetime.datetime(2001, 12, 12, 0, 0), expected = datetime.timedelta(-1)
@pytest.mark.parametrize('a,b,expected',testdata)
def test_timedistance_v0(a,b,expected):
diff = a - b
assert diff == expected
assert 0
E assert 0
test_fixture_one.py:158: AssertionError
____________________________ test_testdata3[3-2-1] _____________________________
x = 3, y = 2, expected = 1
@pytest.mark.parametrize('x,y,expected',testdata3)
def test_testdata3(x,y,expected):
diff = x - y
assert diff == expected
assert 0
E assert 0
test_fixture_one.py:164: AssertionError
____________________________ test_testdata3[5-2-3] _____________________________
x = 5, y = 2, expected = 3
@pytest.mark.parametrize('x,y,expected',testdata3)
def test_testdata3(x,y,expected):
diff = x - y
assert diff == expected
assert 0
E assert 0
test_fixture_one.py:164: AssertionError
______________________________ test_indirect[a-b] ______________________________
x = 'aaa', y = 'bb'
@pytest.mark.parametrize('x,y',testdata2, indirect=['x','y'])
def test_indirect(x,y):
assert x == 'aaa'
assert y == 'bb'
assert 0
E assert 0
test_fixture_one.py:178: AssertionError
=========================== 5 failed in 0.07 seconds ===========================
[备注]
这个函数的ID如果没有显式提供,则根据参数拼接,如果根据参数值拼接后的字符串不长,则按照参数值拼接结果
如果反之,则按照参数名拼接结果
'''
例子 Parametrizing test methods through per-class configuration
a = [dict(c=1,b=2,z=0),dict(a=3,b=3),]
b = [dict(a=1,b=0),]
aa = sorted(a[0])
bb = sorted(b[0])
print (a[0]) #{'c': 1, 'b': 2, 'z': 0}
print (aa) #['b', 'c', 'z']
print (b[0]) #{'a': 1, 'b': 0}
print (bb) #['a', 'b']
print (a[0][aa[0]]) #2
funcarglist = [dict(a=1, b=2), dict(a=3, b=3), ]
argnames = sorted(funcarglist[0])
print (argnames) #['a', 'b']
argvalues = [funcargs[name] for name in argnames for funcargs in funcarglist]
argvalues2 = [[funcargs[name] for name in argnames]for funcargs in funcarglist]
argvalues4 = [[funcargs[name] for funcargs in funcarglist] for name in argnames]
argvalues3 = [[funcargs[name] for name in argnames for funcargs in funcarglist]]
print (argvalues) #[1, 3, 2, 3]
print (argvalues2) #[[1, 2], [3, 3]]
print (argvalues3) #[[1, 3, 2, 3]]
print (argvalues4) #[[1, 3], [2, 3]]
import pytest
def pytest_generate_tests(metafunc):
# called once per each test function
funcarglist = metafunc.cls.params[metafunc.function.name]
argnames = sorted(funcarglist[0])
metafunc.parametrize(argnames, [[funcargs[name] for name in argnames]
for funcargs in funcarglist])
class TestClass(object):
# a map specifying multiple argument sets for a test method
params = {
'test_equals': [dict(a=1, b=2), dict(a=3, b=3), ],
'test_zerodivision': [dict(a=1, b=0), ],
}
def test_equals(self, a, b):
assert a == b
def test_zerodivision(self, a, b):
pytest.raises(ZeroDivisionError, "a/b")
'''
surendeMacBook-Air:~/PycharmProjects/liaoxuefeng_test1/888 suren$pytest -q
F..
=================================== FAILURES ===================================
__________________________ TestClass.test_equals[1-2] __________________________
self = <test_fixture_one.TestClass object at 0x10ec61fd0>, a = 1, b = 2
def test_equals(self, a, b):
assert a == b
E assert 1 == 2
test_fixture_one.py:306: AssertionError
1 failed, 2 passed in 0.06 seconds
'''
"""
例子 Indirect parametrization of optional implementations/imports
'''
content of conftest.py
import pytest
@pytest.fixture(scope="session")
def basemod(request):
return pytest.importorskip("base")
@pytest.fixture(scope="session", params=["opt1", "opt2"])
def optmod(request):
return pytest.importorskip(request.param)
And then a base implementation of a simple function:
content of base.py
def func1():
return 1
And an optimized version:
content of opt1.py
def func1():
return 1.0001
And finally a little test module:
content of test_module.py
def test_func1(basemod, optmod):
assert round(basemod.func1(), 3) == round(optmod.func1(), 3)
If you run this with reporting for skips enabled:
$ pytest -rs test_module.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items
test_module.py .s [100%]
========================= short test summary info ==========================
SKIP [1] $REGENDOC_TMPDIR/conftest.py:11: could not import 'opt2'
=================== 1 passed, 1 skipped in 0.12 seconds ====================
'''
例子 Set marks or test ID for individual parametrized test
'''
Set marks or test ID for individual parametrized test
Use pytest.param to apply marks or set test ID to individual parametrized test. For example:
content of test_pytest_param_example.py
import pytest
@pytest.mark.parametrize('test_input,expected', [
('3+5', 8),
pytest.param('1+7', 8,
marks=pytest.mark.basic),
pytest.param('2+4', 6,
marks=pytest.mark.basic,
id='basic_2+4'),
pytest.param('69', 42,
marks=[pytest.mark.basic, pytest.mark.xfail],
id='basic_69'),
])
def test_eval(test_input, expected):
assert eval(test_input) == expected
In this example, we have 4 parametrized tests. Except for the first test, we mark the rest three parametrized tests with the custom marker basic, and for the fourth test we also use the built-in mark xfail to indicate this test is expected to fail. For explicitness, we set test ids for some tests.
Then run pytest with verbose mode and with only the basic marker:
$ pytest -v -m basic
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 17 items / 14 deselected
test_pytest_param_example.py::test_eval[1+7-8] PASSED [ 33%]
test_pytest_param_example.py::test_eval[basic_2+4] PASSED [ 66%]
test_pytest_param_example.py::test_eval[basic_6*9] xfail [100%]
============ 2 passed, 14 deselected, 1 xfailed in 0.12 seconds ============
As the result:
Four tests were collected
One test was deselected because it doesn’t have the basic mark.
Three tests with the basic mark was selected.
The test test_eval[1+7-8] passed, but the name is autogenerated and confusing.
The test test_eval[basic_2+4] passed.
The test test_eval[basic_6*9] was expected to fail and did fail.
'''
【备注】上面的例子我自己运行提示"AttributeError: 'module' object has no attribute 'param'" 原因不明
"""
例子 换一种运行方式
'''
[conftest.py]
encoding=utf-8
import pytest
def pytest_addoption(parser):
parser.addoption(
'--cmdopt',action='store',default='type1',help='my option: type1 or type2'
)
@pytest.fixture
def cmdopt(request):
return request.config.getoption('--cmdopt')
'''
import pytest
def test_answer(cmdopt):
if cmdopt == 'type1':
print ('first')
elif cmdopt == 'type2':
print ('second')
assert 0
if name == "main":
#方法一
#pytest.main(['-s','test_fixture_one.py','--cmdopt=type2'])
#方法二
pytest.main(['-s','test_fixture_one.py','--cmdopt','type2'])
'''
C:/Users/rq/AppData/Local/Programs/Python/Python37/python.exe C:/Users/rq/PycharmProjects/untitled/test_fixture_one.py
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0
rootdir: C:/Users/rq/PycharmProjects/untitled, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 1 item
test_fixture_one.py second
F
================================== FAILURES ===================================
_________________________________ test_answer _________________________________
cmdopt = 'type2'
def test_answer(cmdopt):
if cmdopt == 'type1':
print ('first')
elif cmdopt == 'type2':
print ('second')
assert 0
E assert 0
test_fixture_one.py:13: AssertionError
========================== 1 failed in 0.04 seconds ===========================
'''
例子 pytest的参数化 通过命令行
'''
[conftest.py]
encoding=utf-8
import pytest
def pytest_addoption(parser):
parser.addoption('--al',action='store_true',help='run all combinations')
def pytest_generate_tests(metafunc):
if 'param2' in metafunc.fixturenames:
if metafunc.config.option.al:
end = 103
else:
end = 2
metafunc.parametrize('param2',range(101,end))
'''
def test_compute(param2):
assert param2 == 100
'''
C:/Users/rq/PycharmProjects/untitled>pytest -q test_fixture_one.py --al
FF [100%]
================================== FAILURES ===================================
______________________________ test_compute[101] ______________________________
param2 = 101
def test_compute(param2):
assert param2 == 100
E assert 101 == 100
test_fixture_one.py:70: AssertionError
______________________________ test_compute[102] ______________________________
param2 = 102
def test_compute(param2):
assert param2 == 100
E assert 102 == 100
test_fixture_one.py:70: AssertionError
2 failed in 0.04 seconds
执行py.test -q test_compute.py 会发现只有2个case
'''
例子 Different options for test IDs
import pytest
from datetime import datetime,timedelta
testdata = [
(datetime(2001,12,12),datetime(2001,12,11),timedelta(1)),
(datetime(2001,12,11),datetime(2001,12,12),timedelta(-1)),
]
@pytest.mark.parametrize('a,b,expected',testdata)
def test_timedistance_v0(a,b,expected):
diff = a - b
assert diff == expected
@pytest.mark.parametrize('a,b,expected',testdata,ids=['forward','backward'])
def test_timedistance_v1(a,b,expected):
diff = a - b
assert diff == expected
def idfn(val):
if isinstance(val,(datetime,)):
return val.strftime('%Y%m%d')
@pytest.mark.parametrize('a,b,expected',testdata,ids=idfn)
def test_timedistance_v2(a,b,expected):
diff = a - b
assert diff == expected
@pytest.mark.parametrize('a,b,expected',[
pytest.param(datetime(2001,12,12),datetime(2001,12,11),timedelta(1),id='forward'),
pytest.param(datetime(2001,12,11),datetime(2001,12,12),timedelta(-1),id='backward'),
])
def test_timedistance_v3(a,b,expected):
diff = a - b
assert diff == expected
'''
C:/Users/rq/PycharmProjects/untitled>pytest --collect-only
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0
rootdir: C:/Users/rq/PycharmProjects/untitled, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 8 items
<Module 'test_fixture_one.py'>
<Function 'test_timedistance_v0[a0-b0-expected0]'>
<Function 'test_timedistance_v0[a1-b1-expected1]'>
<Function 'test_timedistance_v1[forward]'>
<Function 'test_timedistance_v1[backward]'>
<Function 'test_timedistance_v2[20011212-20011211-expected0]'>
<Function 'test_timedistance_v2[20011211-20011212-expected1]'>
<Function 'test_timedistance_v3[forward]'>
<Function 'test_timedistance_v3[backward]'>
======================== no tests ran in 0.03 seconds =========================
'''
'''
C:/Users/rq/PycharmProjects/untitled>pytest test_fixture_one.py
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0
rootdir: C:/Users/rq/PycharmProjects/untitled, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 8 items
test_fixture_one.py ........ [100%]
========================== 8 passed in 0.03 seconds ===========================
'''
例子 A quick port of “testscenarios”
def pytest_generate_tests(metafunc):
idlist = []
argvalues = []
for scenario in metafunc.cls.scenarios:
idlist.append(scenario[0])
items = scenario[1].items()
argnames = [x[0] for x in items]
argvalues.append([x[1] for x in items])
metafunc.parametrize(argnames,argvalues,ids=idlist,scope='class')
scenario1 = ('basic', {'attribute': 'value'})
scenario2 = ('advanced', {'attribute1': 'value2'})
class TestSampleWithScenarios(object):
scenarios = [scenario1,scenario2]
def test_demo1(self,attribute):
assert isinstance(attribute,str)
assert 0
def test_demo2(self,attribute):
assert isinstance(attribute,str)
assert 0
'''
C:/Users/rq/PycharmProjects/untitled>pytest test_fixture_one.py
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0
rootdir: C:/Users/rq/PycharmProjects/untitled, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 0 items / 1 errors
=================================== ERRORS ====================================
____________________ ERROR collecting test_fixture_one.py _____________________
test_fixture_one.py:206: in pytest_generate_tests
metafunc.parametrize(argnames,argvalues,ids=idlist,scope='class')
../../appdata/local/programs/python/python37/lib/site-packages/_pytest/python.py:805: in parametrize
self.function, name, arg))
E ValueError: <function TestSampleWithScenarios.test_demo1 at 0x0000027853D39950> uses no argument 'attribute1'
!!!!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!!
=========================== 1 error in 0.10 seconds ===========================
'''
例子 把scenario2中改成attribute,就
'''
C:/Users/rq/PycharmProjects/untitled>pytest test_fixture_one.py
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0
rootdir: C:/Users/rq/PycharmProjects/untitled, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 4 items
test_fixture_one.py FFFF [100%]
================================== FAILURES ===================================
__________________ TestSampleWithScenarios.test_demo1[basic] __________________
self = <test_fixture_one.TestSampleWithScenarios object at 0x000001945291EFD0>
attribute = 'value'
def test_demo1(self,attribute):
assert isinstance(attribute,str)
assert 0
E assert 0
test_fixture_one.py:218: AssertionError
__________________ TestSampleWithScenarios.test_demo2[basic] __________________
self = <test_fixture_one.TestSampleWithScenarios object at 0x00000194528F19E8>
attribute = 'value'
def test_demo2(self,attribute):
assert isinstance(attribute,str)
assert 0
E assert 0
test_fixture_one.py:222: AssertionError
________________ TestSampleWithScenarios.test_demo1[advanced] _________________
self = <test_fixture_one.TestSampleWithScenarios object at 0x0000019452911DA0>
attribute = 'value2'
def test_demo1(self,attribute):
assert isinstance(attribute,str)
assert 0
E assert 0
test_fixture_one.py:218: AssertionError
________________ TestSampleWithScenarios.test_demo2[advanced] _________________
self = <test_fixture_one.TestSampleWithScenarios object at 0x00000194528F1DD8>
attribute = 'value2'
def test_demo2(self,attribute):
assert isinstance(attribute,str)
assert 0
E assert 0
test_fixture_one.py:222: AssertionError
========================== 4 failed in 0.06 seconds ===========================
'''
"""
Deferring the setup of parametrized resources
import pytest
def test_db_initialized(db):
if db.class.name == 'DB2':
pytest.fail('deliberately failing for demo purposes')
encoding=utf-8
"""
'''
[conftest.py]
import pytest
def pytest_addoption(parser):
parser.addoption(
'--runslow',action='store_true',default=False,help='run slow tests'
)
def pytest_collection_modifyitems(config,items):
if config.getoption('--runslow'):
return
skip_slow = pytest.mark.skip(reason='need --runslow option to run')
for item in items:
if item in items:
if 'slowo' in item.keywords:
item.add_marker(skip_slow)
'''
import pytest
def test_func_fast():
pass
@pytest.mark.slowo
def test_func_sllow():
pass
'''
C:/Users/rq/PycharmProjects/untitled>pytest -rs
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0
rootdir: C:/Users/rq/PycharmProjects/untitled, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 2 items
test_fixture_one.py .s [100%]
=========================== short test summary info ===========================
SKIP [1] test_fixture_one.py:9: need --runslow option to run
===================== 1 passed, 1 skipped in 0.02 seconds =====================
'''
例子 Control skipping of tests according to command line option
'''
[conftest.py]
import pytest
def pytest_addoption(parser):
parser.addoption(
'--runslow',action='store_true',default=False,help='run slow tests'
)
def pytest_collection_modifyitems(config,items):
if config.getoption('--runslow'):
return
skip_slow = pytest.mark.skip(reason='need --runslow option to run')
for item in items:
if item in items:
if 'slowo' in item.keywords:
item.add_marker(skip_slow)
'''
import pytest
def test_func_fast():
pass
@pytest.mark.slowo
def test_func_sllow():
pas
'''
C:/Users/rq/PsycharmProjects/untitled>pytest --runslow
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0
rootdir: C:/Users/rq/PycharmProjects/untitled, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 2 items
test_fixture_one.py .. [100%]
========================== 2 passed in 0.02 seconds ===========================
'''
例子
import pytest
def checkconfig(x):
tracebackhide = True
if not hasattr(x,'config'):
pytest.fail('not configured:%s' % (x,))
def test_something():
checkconfig(42)
'''
C:/Users/rq/PycharmProjects/untitled>pytest -q test_fixture_one.py
F [100%]
================================== FAILURES ===================================
_______________________________ test_something ________________________________
def test_something():
checkconfig(42)
E Failed: not configured:42
test_fixture_one.py:102: Failed
1 failed in 0.03 seconds
'''
'''
If you only want to hide certain exceptions, you can set tracebackhide to a callable which gets the ExceptionInfo object.
You can for example use this to make sure unexpected exception types aren’t hidden:
'''
例子
import operator
import pytest
class ConfigException(Exception):
pass
def checkconfig(x):
tracebackhide = operator.methodcaller("errisinstance", ConfigException)
if not hasattr(x, "config"):
raise ConfigException("not configured: %s" % (x,))
def test_something():
checkconfig(42)
'''
C:/Users/rq/PycharmProjects/untitled>pytest -q test_fixture_one.py
F [100%]
================================== FAILURES ===================================
_______________________________ test_something ________________________________
def test_something():
checkconfig(42)
E test_fixture_one.ConfigException: not configured: 42
test_fixture_one.py:135: ConfigException
1 failed in 0.03 seconds
'''
例子 Detect if running from within a pytest run
'''
[conftest.py]
encoding=utf-8
def pytest_configure(config):
import sys
sys._called_from_test = True
def pytest_unconfigure(config):
import sys
del sys._called_from_test
'''
'''
Usually it is a bad idea to make application code behave differently if called from a test.
But if you absolutely must find out if your application code is running from a test you can do something like this
'''
import sys
def test_sysconfig():
if hasattr(sys,'_called_from_test'):
print ('has_called_from_test')
else:
print ('has not _called_from_test')
assert 0
'''
C:/Users/rq/PycharmProjects/untitled>pytest -q test_fixture_one.py
F [100%]
================================== FAILURES ===================================
_______________________________ test_sysconfig ________________________________
def test_sysconfig():
if hasattr(sys,'_called_from_test'):
print ('has_called_from_test')
else:
print ('has not _called_from_test')
assert 0
E assert 0
test_fixture_one.py:167: AssertionError
---------------------------- Captured stdout call -----------------------------
has_called_from_test
1 failed in 0.03 seconds
'''
例子 Adding info to test report header 正例
'''
[conftest]
encoding=utf-8
def pytest_report_header(config):
return 'project deps:mylib-1.1'
'''
'''
C:/Users/rq/PycharmProjects/untitled>pytest
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0
project deps:mylib-1.1
rootdir: C:/Users/rq/PycharmProjects/untitled, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 0 items
======================== no tests ran in 0.02 seconds =========================
'''
例子 Adding info to test report header 反例 conftest.py 没有 def pytest_report_header(config):
'''
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 0 items
======================= no tests ran in 0.12 seconds =======================
'''
例子 You may consider config.getoption('verbose') in order to display more information if applicable
'''
[conftest.py]
encoding=utf-8
def pytest_report_header(config):
if config.getoption('verbose')>0:
return ['info1:did you know that...','did you?']
'''
'''
C:/Users/rq/PycharmProjects/untitled>pytest -v
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0 -- c:/users/rq/appdata/local/programs/python/python37/python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.7.0', 'Platform': 'Windows-10-10.0.17134-SP0', 'Packages': {'pytest': '3.4.0', 'py': '1.7.0', 'pluggy': '0.6.0'}, 'Plugins': {'xdist': '1.22.0', 'rerunfailures': '4.1', 'metadata': '1.7.0', 'html': '1.16.1
', 'forked': '0.2'}, 'JAVA_HOME': 'C://Program Files//Java//jdk1.8.0_181'}
info1:did you know that...
did you?
rootdir: C:/Users/rq/PycharmProjects/untitled, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 0 items
======================== no tests ran in 0.02 seconds =========================
'''
例子 and nothing when run plainly:
'''
[conftest.py]
encoding=utf-8
def pytest_report_header(config):
if config.getoption('verbose')>0:
return ['info1:did you know that...','did you?']
'''
'''
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 0 items
======================= no tests ran in 0.12 seconds =======================
'''
例子 profiling test duration
import time
def test_funcfast():
time.sleep(0.1)
def test_funcslow1():
time.sleep(0.2)
def test_funcslow2():
time.sleep(0.3)
'''
C:/Users/rq/PycharmProjects/untitled>pytest --duration=3
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0
rootdir: C:/Users/rq/PycharmProjects/untitled, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 3 items
test_fixture_one.py ... [100%]
========================== slowest 3 test durations ===========================
0.30s call test_fixture_one.py::test_funcslow2
0.20s call test_fixture_one.py::test_funcslow1
0.10s call test_fixture_one.py::test_funcfast
========================== 3 passed in 0.63 seconds ===========================
'''
例子 incremental testing - test steps
'''
Sometimes you may have a testing situation which consists of a series of test steps.
If one step fails it makes no sense to execute further steps as they are all expected to fail anyway and their tracebacks add no insight.
Here is a simple conftest.py file which introduces an incremental marker which is to be used on classes'''
import pytest
@pytest.mark.incremental
class TestUserHandling(object):
def test_login(self):
pass
def test_modification(self):
assert 0
def test_deletion(self):
pass
def test_normal():
pass
'''
C:/Users/rq/PycharmProjects/untitled>pytest -rx
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0
rootdir: C:/Users/rq/PycharmProjects/untitled, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 4 items
test_fixture_one.py .F.. [100%]
================================== FAILURES ===================================
_____________________ TestUserHandling.test_modification ______________________
'''
例子 incremental testing - test steps
'''
[conftest.py]
encoding=utf-8
import pytest
def pytest_runtest_makereport(item,call):
if 'incremental' in item.keywords:
if call.excinfo is not None:
parent = item.parent
parent._previousfailed = item
def pytest_runtest_setup(item):
if 'incremental' in item.keywords:
previousfailed = getattr(item.parent,'_previousfailed',None)
if previousfailed is not None:
pytest.xfail('previous test failed (%s)' % previousfailed.name)
'''
'''
C:/Users/rq/PycharmProjects/untitled>pytest -rx
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-3.4.0, py-1.7.0, pluggy-0.6.0
rootdir: C:/Users/rq/PycharmProjects/untitled, inifile:
plugins: xdist-1.22.0, rerunfailures-4.1, metadata-1.7.0, html-1.16.1, forked-0.2
collected 4 items
test_fixture_one.py .Fx. [100%]
=========================== short test summary info ===========================
XFAIL test_fixture_one.py::TestUserHandling: