安装和入门#
pytest 是一个能够简化测试系统构建、方便测试规模扩展的框架,它让测试变得更具表现力和可读性。只需要几分钟的时间,你就可以开始一个简单的单元测试或者复杂的功能测试。
安装 pytest#
-
在命令行执行以下命令:
$ pip install pytest==6.1.1
-
检查版本:
$ pytest --version pytest 6.1.1
创建你的第一个测试用例#
它只有四行代码:
# src/chapter-1/test_sample.py
def func(x):
return x + 1
def test_answer():
assert func(3) == 5
现在开始执行这个测试用例:
$ pipenv run pytest src/chapter-1/test_sample.py
================================ test session starts =================================
platform darwin -- Python 3.8.4, pytest-6.1.1, py-1.9.0, pluggy-0.13.1
rootdir: /Users/yaomeng/Private/projects/pytest-chinese-doc
collected 1 item
src/chapter-1/test_sample.py F [100%]
====================================== FAILURES ======================================
____________________________________ test_answer _____________________________________
def test_answer():
> assert func(3) == 5
E assert 4 == 5
E + where 4 = func(3)
src/chapter-1/test_sample.py:6: AssertionError
============================== short test summary info ===============================
FAILED src/chapter-1/test_sample.py::test_answer - assert 4 == 5
================================= 1 failed in 0.05s ==================================
[100%]
表示执行所有测试用例的总体进度。因为func(3)
不等于5
,所以最后 pytest 会显示一个表示测试失败的报告。
Note
在上面的例子中,我们直接使用assert
来验证测试用例中的预期结果,这是因为 pytest 提供了高级的断言自省功能,可以智能的为我们展示失败处的中间信息(where 4 = func(3)
),因此我们就无需使用assertEqual
、assertNotEqual
之类的方法。
执行多个测试用例#
pytest 会执行当前及其子文件夹中,所有命名符合test_*.py
或者*_test.py
规则的文件中的所有的测试用例;
触发一个指定异常的断言#
使用raises
可以验证某些代码是否抛出一个指定的异常:
# src/chapter-1/test_sysexit.py
import pytest
def f():
# 请求退出解释器
raise SystemExit(1)
def test_mytest():
with pytest.raises(SystemExit):
f()
执行这个测试用例时,加上-q
选项可以精简测试结果的输出:
$ pipenv run pytest src/chapter-1/test_sysexit.py
================================ test session starts =================================
platform darwin -- Python 3.8.4, pytest-6.1.1, py-1.9.0, pluggy-0.13.1
rootdir: /Users/yaomeng/Private/projects/pytest-chinese-doc
collected 1 item
src/chapter-1/test_sysexit.py . [100%]
================================= 1 passed in 0.01s ==================================
Note
使用-q/--quiet
命令行标记可以简化测试结果输出。
在一个类中组织多个测试用例#
pytest 可以让你很容易的创建一个测试类来包含多个测试用例:
# src/chapter-1/test_class.py
class TestClass:
def test_one(self):
x = "this"
assert "h" in x
def test_two(self):
x = "hello"
assert hasattr(x, "check")
我们无需让测试类继承自任何基类,但是要确保类名的前缀为Test
,否则将忽略其中的测试用例。通过传入文件路径就可以执行这个测试类:
$ pipenv run pytest -q src/chapter-1/test_class.py
.F [100%]
====================================== FAILURES ======================================
_________________________________ TestClass.test_two _________________________________
self = <test_class.TestClass object at 0x1046f7df0>
def test_two(self):
x = "hello"
> assert hasattr(x, "check")
E AssertionError: assert False
E + where False = hasattr('hello', 'check')
src/chapter-1/test_class.py:8: AssertionError
============================== short test summary info ===============================
FAILED src/chapter-1/test_class.py::TestClass::test_two - AssertionError: assert False
1 failed, 1 passed in 0.04s
从输出的结果中我们可以看到:
test_one
测试成功,用.
表示;test_two
测试失败,用F
表示;- 并且我们清楚的看到
test_two
失败处的中间值的信息:where False = hasattr('hello', 'check')
Warning
测试类要符合特定的规则,pytest 才能发现它:
- 测试类的命名要符合
Test*
规则; - 测试类中不能有
__init__()
方法,否则 pytest 无法采集到其中的测试用例;PytestCollectionWarning: cannot collect test class 'TestClass' because it has a __init__ constructor.
在类中组织多个测试用例的好处体现以下几个方面:
- 结构化测试的组织;
- 仅在指定的类中共享
fixture
; - 应用在类上的
marker
将隐式的应用于其中所有的测试用例上;
需要注意的一点是,测试类中的每个用例都拥有该类的唯一实例。这是因为如果让所有测试用例共享同一个类实例,将不利于测试的隔离。
# src/chapter-1/test_class_demo.py
class TestClassDemoInstance:
def test_one(self):
assert 0
def test_two(self):
assert 0
$ pipenv run pytest -q src/chapter-1/test_class_demo.py
FF [100%]
====================================== FAILURES ======================================
___________________________ TestClassDemoInstance.test_one ___________________________
self = <test_class_demo.TestClassDemoInstance object at 0x10ea69ee0>
def test_one(self):
> assert 0
E assert 0
src/chapter-1/test_class_demo.py:3: AssertionError
___________________________ TestClassDemoInstance.test_two ___________________________
self = <test_class_demo.TestClassDemoInstance object at 0x10ea5c7f0>
def test_two(self):
> assert 0
E assert 0
src/chapter-1/test_class_demo.py:6: AssertionError
============================== short test summary info ===============================
FAILED src/chapter-1/test_class_demo.py::TestClassDemoInstance::test_one - assert 0
FAILED src/chapter-1/test_class_demo.py::TestClassDemoInstance::test_two - assert 0
2 failed in 0.05s
可以看到,这个例子中的两个测试用例拥有不同的类实例:0x10ea69ee0
和0x10ea5c7f0
。
申请一个唯一的临时目录#
pytest 提供一些内置的fixtures
,用于请求一些系统的资源。例如,一个唯一的临时目录:
# src/chapter-1/test_tmpdir.py
def test_tempdir(tmpdir):
print(tmpdir)
assert 0
在这个例子中,在形参的位子列出tempdir
fixture,pytest 会在每个用例执行之前创建一个唯一的临时目录。
现在,我们来执行这个测试用例:
$ pipenv run pytest -q src/chapter-1/test_tempdir.py
F [100%]
====================================== FAILURES ======================================
____________________________________ test_tempdir ____________________________________
tmpdir = local('/private/var/folders/7r/2gv2hwyx6bj_wz30cb9r9b5r0000gn/T/pytest-of-yaomeng/pytest-0/test_tempdir0')
def test_tempdir(tmpdir):
print(tmpdir)
> assert 0
E assert 0
src/chapter-1/test_tempdir.py:3: AssertionError
-------------------------------- Captured stdout call --------------------------------
/private/var/folders/7r/2gv2hwyx6bj_wz30cb9r9b5r0000gn/T/pytest-of-yaomeng/pytest-0/test_tempdir0
============================== short test summary info ===============================
FAILED src/chapter-1/test_tempdir.py::test_tempdir - assert 0
1 failed in 0.04s
可以使用如下命令查看所有可用的 fixtures,如果想同时查看以_
开头的 fixtures,需要添加-v
选项:
$ pipenv run pytest -v --fixtures
================================ test session starts =================================
platform darwin -- Python 3.8.4, pytest-6.1.1, py-1.9.0, pluggy-0.13.1 -- /Users/yaomeng/.local/share/virtualenvs/pytest-chinese-doc-DA9roatD/bin/python
cachedir: .pytest_cache
rootdir: /Users/yaomeng/Private/projects/pytest-chinese-doc
collected 7 items
cache -- ../../../.local/share/virtualenvs/pytest-chinese-doc-DA9roatD/lib/python3.8/site-packages/_pytest/cacheprovider.py:473
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 clashes with other cache users.
Values can be any object handled by the json stdlib module.
capsys -- ../../../.local/share/virtualenvs/pytest-chinese-doc-DA9roatD/lib/python3.8/site-packages/_pytest/capture.py:843
Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.
The captured output is made available via ``capsys.readouterr()`` method
calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``text`` objects.
capsysbinary -- ../../../.local/share/virtualenvs/pytest-chinese-doc-DA9roatD/lib/python3.8/site-packages/_pytest/capture.py:860
Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``.
The captured output is made available via ``capsysbinary.readouterr()``
method calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``bytes`` objects.
capfd -- ../../../.local/share/virtualenvs/pytest-chinese-doc-DA9roatD/lib/python3.8/site-packages/_pytest/capture.py:877
Enable text capturing of writes to file descriptors ``1`` and ``2``.
The captured output is made available via ``capfd.readouterr()`` method
calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``text`` objects.
capfdbinary -- ../../../.local/share/virtualenvs/pytest-chinese-doc-DA9roatD/lib/python3.8/site-packages/_pytest/capture.py:894
Enable bytes capturing of writes to file descriptors ``1`` and ``2``.
The captured output is made available via ``capfd.readouterr()`` method
calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``byte`` objects.
doctest_namespace [session scope] -- ../../../.local/share/virtualenvs/pytest-chinese-doc-DA9roatD/lib/python3.8/site-packages/_pytest/doctest.py:738
Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests.
pytestconfig [session scope] -- ../../../.local/share/virtualenvs/pytest-chinese-doc-DA9roatD/lib/python3.8/site-packages/_pytest/fixtures.py:1398
Session-scoped fixture that returns the :class:`_pytest.config.Config` object.
Example::
def test_foo(pytestconfig):
if pytestconfig.getoption("verbose") > 0:
...
record_property -- ../../../.local/share/virtualenvs/pytest-chinese-doc-DA9roatD/lib/python3.8/site-packages/_pytest/junitxml.py:305
Add extra properties to the calling test.
User properties become part of the test report and are available to the
configured reporters, like JUnit XML.
The fixture is callable with ``name, value``. The value is automatically
XML-encoded.
Example::
def test_function(record_property):
record_property("example_key", 1)
record_xml_attribute -- ../../../.local/share/virtualenvs/pytest-chinese-doc-DA9roatD/lib/python3.8/site-packages/_pytest/junitxml.py:328
Add extra xml attributes to the tag for the calling test.
The fixture is callable with ``name, value``. The value is
automatically XML-encoded.
record_testsuite_property [session scope] -- ../../../.local/share/virtualenvs/pytest-chinese-doc-DA9roatD/lib/python3.8/site-packages/_pytest/junitxml.py:366
Records a new ``<property>`` tag as child of the root ``<testsuite>``. This is suitable to
writing global information regarding the entire test suite, and is compatible with ``xunit2`` JUnit family.
This is a ``session``-scoped fixture which is called with ``(name, value)``. Example:
.. code-block:: python
def test_foo(record_testsuite_property):
record_testsuite_property("ARCH", "PPC")
record_testsuite_property("STORAGE_TYPE", "CEPH")
``name`` must be a string, ``value`` will be converted to a string and properly xml-escaped.
caplog -- ../../../.local/share/virtualenvs/pytest-chinese-doc-DA9roatD/lib/python3.8/site-packages/_pytest/logging.py:467
Access and control log capturing.
Captured logs are available through the following properties/methods::
* caplog.messages -> list of format-interpolated log messages
* caplog.text -> string containing formatted log output
* caplog.records -> list of logging.LogRecord instances
* caplog.record_tuples -> list of (logger_name, level, message) tuples
* caplog.clear() -> clear captured records and formatted log output string
monkeypatch -- ../../../.local/share/virtualenvs/pytest-chinese-doc-DA9roatD/lib/python3.8/site-packages/_pytest/monkeypatch.py:29
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, 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 -- ../../../.local/share/virtualenvs/pytest-chinese-doc-DA9roatD/lib/python3.8/site-packages/_pytest/recwarn.py:29
Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions.
See http://docs.python.org/library/warnings.html for information
on warning categories.
tmpdir_factory [session scope] -- ../../../.local/share/virtualenvs/pytest-chinese-doc-DA9roatD/lib/python3.8/site-packages/_pytest/tmpdir.py:155
Return a :class:`_pytest.tmpdir.TempdirFactory` instance for the test session.
tmp_path_factory [session scope] -- ../../../.local/share/virtualenvs/pytest-chinese-doc-DA9roatD/lib/python3.8/site-packages/_pytest/tmpdir.py:163
Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session.
tmpdir -- ../../../.local/share/virtualenvs/pytest-chinese-doc-DA9roatD/lib/python3.8/site-packages/_pytest/tmpdir.py:179
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.
.. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
tmp_path -- ../../../.local/share/virtualenvs/pytest-chinese-doc-DA9roatD/lib/python3.8/site-packages/_pytest/tmpdir.py:192
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 :class:`pathlib.Path`
object.
.. note::
in python < 3.6 this is a pathlib2.Path
=============================== no tests ran in 0.05s ================================