py.
test
git clone https://github.com/soasme/pytest_tutorial
• Basic: example / usage
• Fixture: mechanism / builtin
• Plugin: conftest / plugin / hook / 3-party
• Scale
Basic
Getting start!
#
content
of
test_sample.py
def
func(x):
return
x
+
1
!
def
test_answer():
assert
func(3)
==
5
#
content
of
test_sysexit.py
import
pytest
def
f():
raise
SystemExit(1)
!
def
test_mytest():
with
pytest.raises(SystemExit):
f()
class
TestClass:
def
test_one(self):
x
=
"this"
assert
'h'
in
x
!
def
test_two(self):
x
=
"hello"
assert
hasattr(x,
'check')
How to run cases?
• py.test tests/test_mod.py
• py.test tests/
• py.test -k match # def test_match():
How to run cases?
• py.test --showlocals # trace context
• py.test -x # stop on first failure case
• py.test --maxfail=2 # on the second
• py.test -s # enable `print` output
• py.test --durations=10 # list top10 slowest
cases
How to run cases?
• py.test --tb=long # default traceback
• py.test --tb=line # oneline
• py.test --tb=short
• py.test --tb=native # Python default traceback
/tmp % py.test test_a.py --tb=line --pdb
>>>> traceback >>>>
E assert 1 != 1
>>>>> entering PDB >>>>
> /private/tmp/test_a.py(10)test_one()
-> assert num != 1
(Pdb) num
1
(Pdb) exit
How to run cases?
import
pytest
def
test_function():
...
pytest.set_trace()
py.test -h
What to test?
• folder, file.
• recursive
• test_xxx.py, xxx_test.py
• TestClass (without __init__ method)
• all the function or method with prefix `test_`
What to test?
#
setup.cfg
/
tox.ini
/
pytest.ini
[pytest]
python_files=check_*.py
python_classes=Check
python_functions=check
What to test?
#
content
of
check_myapp.py
class
CheckMyApp:
def
check_simple(self):
pass
def
check_complex(self):
pass
Basic configuration
INI-style
• pytest.ini
• tox.ini
• setup.cfg
Basic configuration
Path
• Current dir
• Parent dir
• ...
Basic configuration
#
content
of
pytest.ini
#
(or
tox.ini
or
setup.cfg)
[pytest]
addopts
=
-‐-‐tb=short
-‐x
py.test test_module.py -k test_func
Assertions
• assert expr
• assert a == b
• self.assertEqual(a, b)
• assert expr, “Expected message”
• pytest.raises
Assertions
• Why `assert`?
• simple
• nice output
• http://pytest.org/latest/example/
reportingdemo.html
Assertions
Define Own Comparison
#
content
of
conftest.py
def
pytest_assertrepr_compare(op,
left,
right):
if
(isinstance(left,
Foo)
and
isinstance(right,
Foo)
and
op
==
"=="):
return
['Comparing
Foo
instances:',
'vals:
{0.val}
!=
{1.val}'.format(left,
right)]
Lesson 4
Assertions
Define Own Comparison
def
test_compare():
assert
Foo(1)
==
Foo(2)
>
assert
f1
==
f2
E
assert
Comparing
Foo
instances:
E
vals:
1
!=
2
Assertions
• Py.test refined `assert` statement
• Note: `assert expr, msg` won't output
traceback
Fixtures
• Better than setUp / tearDown:
• Explicit name
• Call only when needed
• Scope: module, class, session, function
• Cascade, fixture A => fixture B => ...
• Scalability
Fixtures as func args
import
pytest
!
@pytest.fixture
def
bookmark(app):
return
Bookmark.create(
user_id=1,
works_id=1)
Fixtures as func args
def
test_get_by_relation(bookmark):
bookmarks
=
Bookmark.get(
user_id=1,
works_id=1
)
assert
bookmarks
assert
bookmarks[0].id
==
bookmark.id
Lesson 01
Fixtures as func args
• Testcase only care about fixture, no import,
no setup, no teardown.
• IoC
Fixtures - scope
@pytest.fixture(scope="module")
def
smtp():
return
smtplib.SMTP("dou.bz")
Fixtures - finalization
@pytest.fixture(scope="session")
def
database(request):
db_name
=
"{}.db".format(time())
deferred_db.init(db_name)
def
finalizer():
if
os.path.exists(db_name):
os.remove(db_name)
request.addfinalizer(finalizer)
return
deferred_db
Lesson 2
Fixtures - parametrizing
@pytest.fixture(params=[
'/',
'/reader/',
])
def
signed_page(request):
return
requests.get(request.param)
!
def
test_fetch_pages_success_in_signed(signed_page):
assert
signed_page.status_code
<
300
Lesson 3.1
Fixtures - modular
class
App(object):
!
def
__init__(self,
request):
self.request
=
request
!
@pytest.fixture
def
app(request,
mc_logger,
db_logger
):
return
App(request)
Fixtures - autouse
class
TestClass:
@pytest.fixture(autouse=True)
def
table(self,
database):
Table.create_table()
!
def
test_select(self):
assert
not
Table.get(id=1)
Fixtures - autouse
@pytest.fixture
def
table(request,
database):
Table.create_table()
request.addfinilizer(
Table.drop_table)
!
@pytest.mark.usefixtures('table')
class
TestClass:
def
test_select(self):
assert
not
Table.get(id=1)
Fixtures - parametrizing
@pytest.mark.parametrize(
"input,expected",
[
("3+5",
8),
("2+4",
6),
("6*9",
42),
pytest.mark.xfail(("6*9",
42))
])
def
test_eval(input,
expected):
assert
eval(input)
==
expected
Lesson 3.2
Fixtures - parametrizing
#
conftest.py
import
pytest
!
def
pytest_generate_tests(metafunc):
if
'payload'
in
metafunc.fixturenames:
metafunc.parametrize('payload',
['/tmp/test.json',
])
!
#
test
file
def
test_meta(payload):
assert
payload
==
'/tmp/test.json'
...
Fixtures - xUnit
def
setup_function(function):
print
'setup'
def
teardown_function(function):
print
'teardown'
def
test_func():
print
'func'
!
#
==>
"""
setup
func
teardown
"""
Fixtures - xUnit
class
TestBookmark:
def
setup_method(self,
method):
print
'setup'
def
teardown_method(self,
method):
print
'teardown'
def
test_method(self):
print
'method'
!
#
==>
"""
setup
method
teardown
"""
Fixtures - xUnit
• setup_module / teardown_module
• setup_class / teardown_class
• setup_method / teardown_method
• setup_function / teardown_function
Fixtures - builtin
import
datetime
import
pytest
!
FAKE_TIME
=
datetime.datetime(2020,
12,
25,
17,
05,
55)
!
@pytest.fixture
def
patch_datetime_now(monkeypatch):
!
class
mydatetime:
@classmethod
def
now(cls):
return
FAKE_TIME
!
monkeypatch.setattr(datetime,
'datetime',
mydatetime)
!
!
def
test_patch_datetime(patch_datetime_now):
assert
datetime.datetime.now()
==
FAKE_TIME
Fixtures - builtin
• monkeypatch
• tmpdir
• capsys / capfd
• `py.test --fixture`
Maker
• pytest.marker
• py.test --marker
• marker is like tag.
• @pytest.mark.skipif(getenv('qaci'))
• @pytest.mark.xfail('oooops')
• @pytest.mark.skipif("config.getvalue('pass')")
• @pytest.mark.ask_sunyi
!
unittest.TestCase
• Compatible
• But be careful. There is no funcargs
mechanism for unittest cases.
Plugin
• py.test supply many hooks.
• collection / configuration / run / output
• Basic types:
• builtin
• 3-party plugins
• conftest.py plugins
Plugin - find conftest.py
• recursive
• `import conftest` X
Plugin - 3-party
• pip install pytest-xxxxx
• pytest-random, pytest-cov
• https://pypi.python.org/pypi?
%3Aaction=search&term=pytest&submit=s
earch
Plugin - load plugin
• py.test -p plugin_name
• py.test -p no:plugin_name
• pytest.ini
• conftest.py `pytest_plugins`
• pytest_plugins = "name1", "name2",
• pytest_plugins = "suites.isolated_cases"
Plugin - hooks
• http://pytest.org/latest/plugins.html#hook-
specification-and-validation
• see source.
Plugin - example
#
content
of
suites.isolated_cases
def
pytest_addoption(parser):
group
=
parser.getgroup("isolated_cases",
"")
group._addoption(
'-‐-‐with-‐data-‐service',
action="store_true",
default=False,
dest='with_data_service',
help=(
"with
MySQL/beansdb/memcached
up
at
the
beginning
of
session"
"and
down
at
the
end
of
session."
)
)
Plugin - example
#
content
of
isolated_cases
def
pytest_configure(config):
if
config.option.with_data_service:
build_tables()
stop_kvstore()
sleep(1)
start_kvstore()
$ py.test --with-data-service tests/
Plugin - example
#
content
of
tests/conftest.py
pytest_plugins
=
"suites.isolated_cases"
$ py.test --with-data-service tests/
EOF