python第三方测试框架pytest
Pytest vs Unittest
-
测试用例设计
- Unitest
- 测试类必须继承 unittest.TestCase
- 测试函数必须以 test_ 开头
- 测试类必须有 unittest.main() 方法
- Pytest
- 测试文件必须以 test_ 开头,或者以 _test 结尾
- 测试类必须以 Test 开头
- 测试函数必须以 test 开头
- 测试类里不能使用 "____init" 方法
- Pytest 兼容 Unittest 测试用例,但 Unittest 不兼容 Pytest
- Unitest
-
断言比较
- Unittest
- assertEqual(a, b) # 判断a和b是否相等
- assertNotEqual(a, b) # 判断a不等于b
- assertTrue(a) # 判断a是否为Ture
- assertFalse(a) #判断a是否为False
- assertIn(a, b) # a 包含在b里面
- asserNotIn(a, b) # a 不包含在b里面
- Pytest
- assert 后面加需要断言的条件就可以了,例如:
- assert a = = b # 判断a是否等于b
- assert a != b # 判断a不等于b、
- assert a in b # 判断b包含a
- Unittest
-
用例前置和后置
- Unittest
- 通过setup每个用例执行前执行,teardown每个用例执行后执行
- 通过setupclass类里面所有用例执行前执行,teardownclass类里面所有用例执行后执行
- Pytest
- 模块级别:setup_module/teardown_module,整个.py全部用例开始前执行/全部用例执行完后执行
- 函数级别:setup_function/teardown_function,只对函数级别生效,每个用例开始前和结束后执行一次
- 类级别:setup_class/teardown_function,只对类级别生效,类里面所有用例开始前执行一次,所有用例执行完执行一次
- 方法级别:setup_method/teardown_method,只是类里面方法级别生效,方法开始前执行一致,方法结束后执行一次
- 方法级别:setup/teardown,这个与setup_method/teardown_method用法很类似,但是级别比method级别要低,也就是说在同一个方法中会先执行setup_method再执行setup,方法结束后先执行teardown再执行teardown_method
- 通过firture可以自定义pytest的前置和后置,格式fixture(scope=”function”, params=None, autouse=False, ids=None, name=None)
- scope:有四个级别,function(默认),class, module, session
- params:参数列表
- autouse:False为默认值,意思代表需要根据设置的条件(scope级别)来激活fixture,如果为Ture,则表示所有function级别的都被激活fixture
- ids:每个字符串id的列表,感觉没啥实质性作用
- name:fixture的名字
- Unittest
-
参数化
- Unittest:可以通过nose_parameterized来实现,格式:@nose_parameterized.parameterized.expand(data), ‘data’为list格式的参数化的数据
- Pytest:通过装饰器@pytest.mark.parametrize来实现
-
生成报告方式
- Unittest:通过HTMLTestRunner生成
- Pytest:通过pytest-html生成html格式报告;结合 Allure 可以生成更漂亮的测试报告
环境配置
介绍
pytest 是一个非常成熟的 Python 测试框架,可以做到做个场景的测试工作,如:单元测试、接口测试、web测试等。
- pytest-selenium(集成 selenium)
- pytest-html(完美html测试报告生成)
- pytest-rerunfailures(失败 case 重复执行)
- pytest-xdist(多CPU分发)
- 测试用例的 skip 和 xfail 处理
- 可以很好的和 jenkins 集成
pytest 是一个插件化平台,这就是它比 unittest 强大的原因,丰富的插件扩展增强了它的功能,也可以根据自己的需要定制化开发自己的插件,非常的灵活。
安装
# 安装 pytest
# !pip3 install pytest
Collecting pytest
Downloading http://mirrors.tencentyun.com/pypi/packages/c7/e2/c19c667f42f72716a7d03e8dd4d6f63f47d39feadd44cc1ee7ca3089862c/pytest-5.4.1-py3-none-any.whl (246kB)
[K 100% |████████████████████████████████| 256kB 1.6MB/s ta 0:00:01
[?25hCollecting more-itertools>=4.0.0 (from pytest)
Downloading http://mirrors.tencentyun.com/pypi/packages/72/96/4297306cc270eef1e3461da034a3bebe7c84eff052326b130824e98fc3fb/more_itertools-8.2.0-py3-none-any.whl (43kB)
[K 100% |████████████████████████████████| 51kB 48.1MB/s ta 0:00:01
[?25hCollecting packaging (from pytest)
Downloading http://mirrors.tencentyun.com/pypi/packages/62/0a/34641d2bf5c917c96db0ded85ae4da25b6cd922d6b794648d4e7e07c88e5/packaging-20.3-py2.py3-none-any.whl
Collecting py>=1.5.0 (from pytest)
Downloading http://mirrors.tencentyun.com/pypi/packages/99/8d/21e1767c009211a62a8e3067280bfce76e89c9f876180308515942304d2d/py-1.8.1-py2.py3-none-any.whl (83kB)
[K 100% |████████████████████████████████| 92kB 2.8MB/s ta 0:00:011
[?25hCollecting pluggy<1.0,>=0.12 (from pytest)
Downloading http://mirrors.tencentyun.com/pypi/packages/a0/28/85c7aa31b80d150b772fbe4a229487bc6644da9ccb7e427dd8cc60cb8a62/pluggy-0.13.1-py2.py3-none-any.whl
Collecting attrs>=17.4.0 (from pytest)
Downloading http://mirrors.tencentyun.com/pypi/packages/a2/db/4313ab3be961f7a763066401fb77f7748373b6094076ae2bda2806988af6/attrs-19.3.0-py2.py3-none-any.whl
Collecting wcwidth (from pytest)
Downloading http://mirrors.tencentyun.com/pypi/packages/f6/d5/1ecdac957e3ea12c1b319fcdee8b6917ffaff8b4644d673c4d72d2f20b49/wcwidth-0.1.9-py2.py3-none-any.whl
Collecting importlib-metadata>=0.12; python_version < "3.8" (from pytest)
Downloading http://mirrors.tencentyun.com/pypi/packages/ad/e4/891bfcaf868ccabc619942f27940c77a8a4b45fd8367098955bb7e152fb1/importlib_metadata-1.6.0-py2.py3-none-any.whl
Collecting pyparsing>=2.0.2 (from packaging->pytest)
Downloading http://mirrors.tencentyun.com/pypi/packages/8a/bb/488841f56197b13700afd5658fc279a2025a39e22449b7cf29864669b15d/pyparsing-2.4.7-py2.py3-none-any.whl (67kB)
[K 100% |████████████████████████████████| 71kB 2.2MB/s eta 0:00:01
[?25hCollecting six (from packaging->pytest)
Downloading http://mirrors.tencentyun.com/pypi/packages/65/eb/1f97cb97bfc2390a276969c6fae16075da282f5058082d4cb10c6c5c1dba/six-1.14.0-py2.py3-none-any.whl
Collecting zipp>=0.5 (from importlib-metadata>=0.12; python_version < "3.8"->pytest)
Downloading http://mirrors.tencentyun.com/pypi/packages/b2/34/bfcb43cc0ba81f527bc4f40ef41ba2ff4080e047acb0586b56b3d017ace4/zipp-3.1.0-py3-none-any.whl
Installing collected packages: more-itertools, pyparsing, six, packaging, py, zipp, importlib-metadata, pluggy, attrs, wcwidth, pytest
Successfully installed attrs-19.3.0 importlib-metadata-1.6.0 more-itertools-8.2.0 packaging-20.3 pluggy-0.13.1 py-1.8.1 pyparsing-2.4.7 pytest-5.4.1 six-1.14.0 wcwidth-0.1.9 zipp-3.1.0
# 查看 pytest 是否安装成功
# !pytest --version
This is pytest version 5.4.1, imported from /home/ubuntu/.local/lib/python3.6/site-packages/pytest/__init__.py
# 显示可用的内置参数
# !pytest --fixtures
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 0 items [0m[1m
[32mcache[0m
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.
[32mcapsys[0m
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.
[32mcapsysbinary[0m
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.
[32mcapfd[0m
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.
[32mcapfdbinary[0m
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.
[32mdoctest_namespace[0m[36m [session scope][0m
Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests.
[32mpytestconfig[0m[36m [session scope][0m
Session-scoped fixture that returns the :class:`_pytest.config.Config` object.
Example::
def test_foo(pytestconfig):
if pytestconfig.getoption("verbose") > 0:
...
[32mrecord_property[0m
Add an extra properties 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)``, with value being automatically
xml-encoded.
Example::
def test_function(record_property):
record_property("example_key", 1)
[32mrecord_xml_attribute[0m
Add extra xml attributes to the tag for the calling test.
The fixture is callable with ``(name, value)``, with value being
automatically xml-encoded
[32mrecord_testsuite_property[0m[36m [session scope][0m
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.
[32mcaplog[0m
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
[32mmonkeypatch[0m
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.
[32mrecwarn[0m
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.
[32mtmpdir_factory[0m[36m [session scope][0m
Return a :class:`_pytest.tmpdir.TempdirFactory` instance for the test session.
[32mtmp_path_factory[0m[36m [session scope][0m
Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session.
[32mtmpdir[0m
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
[32mtmp_path[0m
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
[33m============================ [33mno tests ran[0m[33m in 0.03s[0m[33m =============================[0m
执行用例
执行退出码
0 -- 成功地收集并传递了所有测试
1 -- 测试被收集和运行但一些测试失败
2 -- 测试执行被用户中断
3 -- 执行测试时发生内部错误
4 -- pytest 命令行使用错误
5 -- 未收集任何测试
执行测试文件
"""
示例文件
@FileName: test_start.py
"""
def func(x):
return x + 1
def test_func():
assert func(3) == 5
class TestClass:
def test_one(self):
x = "This"
assert "h" in x
def test_two(self):
x = "hello"
assert hasattr(x, "check")
执行某个目录下所有的用例
!pytest
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 3 items [0m
test_start.py [31mF[0m[32m.[0m[31mF[0m[31m [100%][0m
=================================== FAILURES ===================================
[31m[1m__________________________________ test_func ___________________________________[0m
[94mdef[39;49;00m [92mtest_func[39;49;00m():
> [94massert[39;49;00m func([94m3[39;49;00m) == [94m5[39;49;00m
[1m[31mE assert 4 == 5[0m
[1m[31mE + where 4 = func(3)[0m
[1m[31mtest_start.py[0m:8: AssertionError
[31m[1m______________________________ TestClass.test_two ______________________________[0m
self = <test_start.TestClass object at 0x7f384557fc18>
[94mdef[39;49;00m [92mtest_two[39;49;00m([96mself[39;49;00m):
x = [33m"[39;49;00m[33mhello[39;49;00m[33m"[39;49;00m
> [94massert[39;49;00m [96mhasattr[39;49;00m(x, [33m"[39;49;00m[33mcheck[39;49;00m[33m"[39;49;00m)
[1m[31mE AssertionError: assert False[0m
[1m[31mE + where False = hasattr('hello', 'check')[0m
[1m[31mtest_start.py[0m:17: AssertionError
=========================== short test summary info ============================
FAILED test_start.py::test_func - assert 4 == 5
FAILED test_start.py::TestClass::test_two - AssertionError: assert False
[31m========================= [31m[1m2 failed[0m, [32m1 passed[0m[31m in 0.12s[0m[31m ==========================[0m
执行某个 py 文件下用例
!pytest start.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 3 items [0m
start.py [31mF[0m[32m.[0m[31mF[0m[31m [100%][0m
=================================== FAILURES ===================================
[31m[1m__________________________________ test_func ___________________________________[0m
[94mdef[39;49;00m [92mtest_func[39;49;00m():
> [94massert[39;49;00m func([94m3[39;49;00m) == [94m5[39;49;00m
[1m[31mE assert 4 == 5[0m
[1m[31mE + where 4 = func(3)[0m
[1m[31mstart.py[0m:8: AssertionError
[31m[1m______________________________ TestClass.test_two ______________________________[0m
self = <start.TestClass object at 0x7f0d00651278>
[94mdef[39;49;00m [92mtest_two[39;49;00m([96mself[39;49;00m):
x = [33m"[39;49;00m[33mhello[39;49;00m[33m"[39;49;00m
> [94massert[39;49;00m [96mhasattr[39;49;00m(x, [33m"[39;49;00m[33mcheck[39;49;00m[33m"[39;49;00m)
[1m[31mE AssertionError: assert False[0m
[1m[31mE + where False = hasattr('hello', 'check')[0m
[1m[31mstart.py[0m:17: AssertionError
=========================== short test summary info ============================
FAILED start.py::test_func - assert 4 == 5
FAILED start.py::TestClass::test_two - AssertionError: assert False
[31m========================= [31m[1m2 failed[0m, [32m1 passed[0m[31m in 0.12s[0m[31m ==========================[0m
执行 py 文件中的某个函数
!pytest start.py::test_func
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 1 item [0m
start.py [31mF[0m[31m [100%][0m
=================================== FAILURES ===================================
[31m[1m__________________________________ test_func ___________________________________[0m
[94mdef[39;49;00m [92mtest_func[39;49;00m():
> [94massert[39;49;00m func([94m3[39;49;00m) == [94m5[39;49;00m
[1m[31mE assert 4 == 5[0m
[1m[31mE + where 4 = func(3)[0m
[1m[31mstart.py[0m:8: AssertionError
=========================== short test summary info ============================
FAILED start.py::test_func - assert 4 == 5
[31m============================== [31m[1m1 failed[0m[31m in 0.10s[0m[31m ===============================[0m
执行 py 文件中的某个类
!pytest start.py::TestClass
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 2 items [0m
start.py [32m.[0m[31mF[0m[31m [100%][0m
=================================== FAILURES ===================================
[31m[1m______________________________ TestClass.test_two ______________________________[0m
self = <start.TestClass object at 0x7f5fc75cb160>
[94mdef[39;49;00m [92mtest_two[39;49;00m([96mself[39;49;00m):
x = [33m"[39;49;00m[33mhello[39;49;00m[33m"[39;49;00m
> [94massert[39;49;00m [96mhasattr[39;49;00m(x, [33m"[39;49;00m[33mcheck[39;49;00m[33m"[39;49;00m)
[1m[31mE AssertionError: assert False[0m
[1m[31mE + where False = hasattr('hello', 'check')[0m
[1m[31mstart.py[0m:17: AssertionError
=========================== short test summary info ============================
FAILED start.py::TestClass::test_two - AssertionError: assert False
[31m========================= [31m[1m1 failed[0m, [32m1 passed[0m[31m in 0.13s[0m[31m ==========================[0m
执行 py 文件中类里的某个方法
!pytest start.py::TestClass::test_one
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 1 item [0m
start.py [32m.[0m[32m [100%][0m
[32m============================== [32m[1m1 passed[0m[32m in 0.01s[0m[32m ===============================[0m
-v 显示每个测试函数结果
"""显示每一个测试函数的执行结果"""
!pytest -v start.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 3 items [0m
start.py::test_func [31mFAILED[0m[31m [ 33%][0m
start.py::TestClass::test_one [32mPASSED[0m[31m [ 66%][0m
start.py::TestClass::test_two [31mFAILED[0m[31m [100%][0m
=================================== FAILURES ===================================
[31m[1m__________________________________ test_func ___________________________________[0m
[94mdef[39;49;00m [92mtest_func[39;49;00m():
> [94massert[39;49;00m func([94m3[39;49;00m) == [94m5[39;49;00m
[1m[31mE assert 4 == 5[0m
[1m[31mE +4[0m
[1m[31mE -5[0m
[1m[31mstart.py[0m:8: AssertionError
[31m[1m______________________________ TestClass.test_two ______________________________[0m
self = <start.TestClass object at 0x7fc060a955c0>
[94mdef[39;49;00m [92mtest_two[39;49;00m([96mself[39;49;00m):
x = [33m"[39;49;00m[33mhello[39;49;00m[33m"[39;49;00m
> [94massert[39;49;00m [96mhasattr[39;49;00m(x, [33m"[39;49;00m[33mcheck[39;49;00m[33m"[39;49;00m)
[1m[31mE AssertionError: assert False[0m
[1m[31mE + where False = hasattr('hello', 'check')[0m
[1m[31mstart.py[0m:17: AssertionError
=========================== short test summary info ============================
FAILED start.py::test_func - assert 4 == 5
FAILED start.py::TestClass::test_two - AssertionError: assert False
[31m========================= [31m[1m2 failed[0m, [32m1 passed[0m[31m in 0.16s[0m[31m ==========================[0m
-m 标记表达式
# 将运行用 @pytest.mark.login 装饰器修饰的所有测试
!pytest -m login
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 4 items / 4 deselected [0m
[33m============================ [33m[1m4 deselected[0m[33m in 0.04s[0m[33m =============================[0m
-q 简单但因,只打印测试用例的执行结果
!pytest -q start.py
[31mF[0m[32m.[0m[31mF[0m[31m [100%][0m
=================================== FAILURES ===================================
[31m[1m__________________________________ test_func ___________________________________[0m
[94mdef[39;49;00m [92mtest_func[39;49;00m():
> [94massert[39;49;00m func([94m3[39;49;00m) == [94m5[39;49;00m
[1m[31mE assert 4 == 5[0m
[1m[31mE + where 4 = func(3)[0m
[1m[31mstart.py[0m:8: AssertionError
[31m[1m______________________________ TestClass.test_two ______________________________[0m
self = <start.TestClass object at 0x7fa6e310a4a8>
[94mdef[39;49;00m [92mtest_two[39;49;00m([96mself[39;49;00m):
x = [33m"[39;49;00m[33mhello[39;49;00m[33m"[39;49;00m
> [94massert[39;49;00m [96mhasattr[39;49;00m(x, [33m"[39;49;00m[33mcheck[39;49;00m[33m"[39;49;00m)
[1m[31mE AssertionError: assert False[0m
[1m[31mE + where False = hasattr('hello', 'check')[0m
[1m[31mstart.py[0m:17: AssertionError
=========================== short test summary info ============================
FAILED start.py::test_func - assert 4 == 5
FAILED start.py::TestClass::test_two - AssertionError: assert False
[31m[31m[1m2 failed[0m, [32m1 passed[0m[31m in 0.16s[0m[0m
-s 详细打印
!pytest -s start.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 3 items [0m
start.py [31mF[0m[32m.[0m[31mF[0m
=================================== FAILURES ===================================
[31m[1m__________________________________ test_func ___________________________________[0m
[94mdef[39;49;00m [92mtest_func[39;49;00m():
> [94massert[39;49;00m func([94m3[39;49;00m) == [94m5[39;49;00m
[1m[31mE assert 4 == 5[0m
[1m[31mE + where 4 = func(3)[0m
[1m[31mstart.py[0m:8: AssertionError
[31m[1m______________________________ TestClass.test_two ______________________________[0m
self = <start.TestClass object at 0x7f087090bd30>
[94mdef[39;49;00m [92mtest_two[39;49;00m([96mself[39;49;00m):
x = [33m"[39;49;00m[33mhello[39;49;00m[33m"[39;49;00m
> [94massert[39;49;00m [96mhasattr[39;49;00m(x, [33m"[39;49;00m[33mcheck[39;49;00m[33m"[39;49;00m)
[1m[31mE AssertionError: assert False[0m
[1m[31mE + where False = hasattr('hello', 'check')[0m
[1m[31mstart.py[0m:17: AssertionError
=========================== short test summary info ============================
FAILED start.py::test_func - assert 4 == 5
FAILED start.py::TestClass::test_two - AssertionError: assert False
[31m========================= [31m[1m2 failed[0m, [32m1 passed[0m[31m in 0.12s[0m[31m ==========================[0m
-x 遇到错误时停止测试
!pytest start.py -x
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 3 items [0m
start.py [31mF[0m
=================================== FAILURES ===================================
[31m[1m__________________________________ test_func ___________________________________[0m
[94mdef[39;49;00m [92mtest_func[39;49;00m():
> [94massert[39;49;00m func([94m3[39;49;00m) == [94m5[39;49;00m
[1m[31mE assert 4 == 5[0m
[1m[31mE + where 4 = func(3)[0m
[1m[31mstart.py[0m:8: AssertionError
=========================== short test summary info ============================
FAILED start.py::test_func - assert 4 == 5
[31m!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!![0m
[31m============================== [31m[1m1 failed[0m[31m in 0.11s[0m[31m ===============================[0m
--maxfail=num,当用例错误个数达到指定数量是,停止测试
!pytest start.py --maxfail=1
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 3 items [0m
start.py [31mF[0m
=================================== FAILURES ===================================
[31m[1m__________________________________ test_func ___________________________________[0m
[94mdef[39;49;00m [92mtest_func[39;49;00m():
> [94massert[39;49;00m func([94m3[39;49;00m) == [94m5[39;49;00m
[1m[31mE assert 4 == 5[0m
[1m[31mE + where 4 = func(3)[0m
[1m[31mstart.py[0m:8: AssertionError
=========================== short test summary info ============================
FAILED start.py::test_func - assert 4 == 5
[31m!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!![0m
[31m============================== [31m[1m1 failed[0m[31m in 0.12s[0m[31m ===============================[0m
-k 匹配用例名称
# 执行测试用例名称包含http的所有用例
!pytest -s -k http start.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 3 items / 3 deselected [0m
[33m============================ [33m[1m3 deselected[0m[33m in 0.01s[0m[33m =============================[0m
-k 根据用例名称排除某些用例
!pytest -s -k "not http" start.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 3 items [0m
start.py [31mF[0m[32m.[0m[31mF[0m
=================================== FAILURES ===================================
[31m[1m__________________________________ test_func ___________________________________[0m
[94mdef[39;49;00m [92mtest_func[39;49;00m():
> [94massert[39;49;00m func([94m3[39;49;00m) == [94m5[39;49;00m
[1m[31mE assert 4 == 5[0m
[1m[31mE + where 4 = func(3)[0m
[1m[31mstart.py[0m:8: AssertionError
[31m[1m______________________________ TestClass.test_two ______________________________[0m
self = <start.TestClass object at 0x7f946a3cfe80>
[94mdef[39;49;00m [92mtest_two[39;49;00m([96mself[39;49;00m):
x = [33m"[39;49;00m[33mhello[39;49;00m[33m"[39;49;00m
> [94massert[39;49;00m [96mhasattr[39;49;00m(x, [33m"[39;49;00m[33mcheck[39;49;00m[33m"[39;49;00m)
[1m[31mE AssertionError: assert False[0m
[1m[31mE + where False = hasattr('hello', 'check')[0m
[1m[31mstart.py[0m:17: AssertionError
=========================== short test summary info ============================
FAILED start.py::test_func - assert 4 == 5
FAILED start.py::TestClass::test_two - AssertionError: assert False
[31m========================= [31m[1m2 failed[0m, [32m1 passed[0m[31m in 0.12s[0m[31m ==========================[0m
-k 同时匹配不同的用例名称
!pytest -s -k "method or weibo" start.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 3 items / 3 deselected [0m
[33m============================ [33m[1m3 deselected[0m[33m in 0.01s[0m[33m =============================[0m
断言 assert
常用断言
-
与unittest不同,pytest使用的是python自带的assert关键字来进行断言
-
assert关键字后面可以接一个表达式,只要表达式的最终结果为True,那么断言通过,用例执行成功,否则用例执行失败
常用断言方法:
- assert xx --- 判断 xx 为真
- assert not xx --- 判断 xx 不为真
- assert a in b --- 判断 b 包含 a
- assert a == b --- 判断 a 等于 b
- assert a != b --- 判断 a 不等于 b
import pytest
def f():
return 3
def test_function():
a = f()
assert a % 2 == 0, "判断 a 为偶数,当前 a 的值为:%s" % a
def test_zero_division():
with pytest.raises(ZeroDivisionError):
1 / 0
!pytest assert.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 2 items [0m
assert.py [31mF[0m[32m.[0m[31m [100%][0m
=================================== FAILURES ===================================
[31m[1m________________________________ test_function _________________________________[0m
[94mdef[39;49;00m [92mtest_function[39;49;00m():
a = f()
> [94massert[39;49;00m a % [94m2[39;49;00m == [94m0[39;49;00m, [33m"[39;49;00m[33m判断 a 为偶数,当前 a 的值为:[39;49;00m[33m%s[39;49;00m[33m"[39;49;00m % a
[1m[31mE AssertionError: 判断 a 为偶数,当前 a 的值为:3[0m
[1m[31mE assert (3 % 2) == 0[0m
[1m[31massert.py[0m:8: AssertionError
=========================== short test summary info ============================
FAILED assert.py::test_function - AssertionError: 判断 a 为偶数,当前 a 的值...
[31m========================= [31m[1m1 failed[0m, [32m1 passed[0m[31m in 0.11s[0m[31m ==========================[0m
异常断言
可以使用 pytest.raises 作为上下文管理器,当抛出异常时可以获取到对应的异常实例
- excinfo :是一个异常信息实例
- 主要属性: .type 、 .value 、 .traceback
- 注意:断言 type 的时候,异常类型是不需要加引号的,断言 value值的时候需转 str
"""异常断言"""
import pytest
def test_zero_division():
with pytest.raises(ZeroDivisionError):
1 / 0
"""详细断言异常"""
def test_zero_division_long_info():
with pytest.raises(ZeroDivisionError) as excinfo:
1 / 0
"""断言异常类型"""
assert excinfo.type == ZeroDivisionError
"""断言异常 value 的值"""
assert "Division by Zero" in str(excinfo.value)
!pytest assert_except.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 2 items [0m
assert_except.py [32m.[0m[31mF[0m[31m [100%][0m
=================================== FAILURES ===================================
[31m[1m_________________________ test_zero_division_long_info _________________________[0m
[94mdef[39;49;00m [92mtest_zero_division_long_info[39;49;00m():
[94mwith[39;49;00m pytest.raises([96mZeroDivisionError[39;49;00m) [94mas[39;49;00m excinfo:
[94m1[39;49;00m / [94m0[39;49;00m
[33m"""断言异常类型"""[39;49;00m
[94massert[39;49;00m excinfo.type == [96mZeroDivisionError[39;49;00m
[33m"""断言异常 value 的值"""[39;49;00m
> [94massert[39;49;00m [33m"[39;49;00m[33mDivision by Zero[39;49;00m[33m"[39;49;00m [95min[39;49;00m [96mstr[39;49;00m(excinfo.value)
[1m[31mE AssertionError: assert 'Division by Zero' in 'division by zero'[0m
[1m[31mE + where 'division by zero' = str(ZeroDivisionError('division by zero',))[0m
[1m[31mE + where ZeroDivisionError('division by zero',) = <ExceptionInfo ZeroDivisionError('division by zero',) tblen=1>.value[0m
[1m[31massert_except.py[0m:17: AssertionError
=========================== short test summary info ============================
FAILED assert_except.py::test_zero_division_long_info - AssertionError: asser...
[31m========================= [31m[1m1 failed[0m, [32m1 passed[0m[31m in 0.11s[0m[31m ==========================[0m
match
- 可以将 match 关键字参数传递给上下文管理器,以测试正则表达式与异常的字符串表示形式是否匹配
- 注意:这种方法只能断言value,不能断言type
"""自定义消息"""
def test_zero_division_custom_incinfo():
with pytest.raises(ZeroDivisionError, match="*.zero.*") as excinfo:
1 / 0
!pytest assert_except.py::test_zero_division_custom_incinfo
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 1 item [0m
assert_except.py [31mF[0m[31m [100%][0m
=================================== FAILURES ===================================
[31m[1m______________________ test_zero_division_custom_incinfo _______________________[0m
[94mdef[39;49;00m [92mtest_zero_division_custom_incinfo[39;49;00m():
[94mwith[39;49;00m pytest.raises([96mZeroDivisionError[39;49;00m, match=[33m"[39;49;00m[33m*.zero.*[39;49;00m[33m"[39;49;00m) [94mas[39;49;00m excinfo:
> [94m1[39;49;00m / [94m0[39;49;00m
[1m[31mE ZeroDivisionError: division by zero[0m
[1m[31massert_except.py[0m:23: ZeroDivisionError
[33mDuring handling of the above exception, another exception occurred:[0m
[94mdef[39;49;00m [92mtest_zero_division_custom_incinfo[39;49;00m():
[94mwith[39;49;00m pytest.raises([96mZeroDivisionError[39;49;00m, match=[33m"[39;49;00m[33m*.zero.*[39;49;00m[33m"[39;49;00m) [94mas[39;49;00m excinfo:
> [94m1[39;49;00m / [94m0[39;49;00m
[1m[31massert_except.py[0m:23:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
[1m[31m/usr/lib/python3.6/re.py[0m:182: in search
[94mreturn[39;49;00m _compile(pattern, flags).search(string)
[1m[31m/usr/lib/python3.6/re.py[0m:301: in _compile
p = sre_compile.compile(pattern, flags)
[1m[31m/usr/lib/python3.6/sre_compile.py[0m:562: in compile
p = sre_parse.parse(p, flags)
[1m[31m/usr/lib/python3.6/sre_parse.py[0m:855: in parse
p = _parse_sub(source, pattern, flags & SRE_FLAG_VERBOSE, [94m0[39;49;00m)
[1m[31m/usr/lib/python3.6/sre_parse.py[0m:416: in _parse_sub
[95mnot[39;49;00m nested [95mand[39;49;00m [95mnot[39;49;00m items))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
source = <sre_parse.Tokenizer object at 0x7f7aac6d9630>
state = <sre_parse.Pattern object at 0x7f7aac663390>, verbose = 0, nested = 1
first = True
[94mdef[39;49;00m [92m_parse[39;49;00m(source, state, verbose, nested, first=[94mFalse[39;49;00m):
[90m# parse a simple pattern[39;49;00m
subpattern = SubPattern(state)
[90m# precompute constants into local variables[39;49;00m
subpatternappend = subpattern.append
sourceget = source.get
sourcematch = source.match
_len = [96mlen[39;49;00m
_ord = [96mord[39;49;00m
[94mwhile[39;49;00m [94mTrue[39;49;00m:
this = source.next
[94mif[39;49;00m this [95mis[39;49;00m [94mNone[39;49;00m:
[94mbreak[39;49;00m [90m# end of pattern[39;49;00m
[94mif[39;49;00m this [95min[39;49;00m [33m"[39;49;00m[33m|)[39;49;00m[33m"[39;49;00m:
[94mbreak[39;49;00m [90m# end of subpattern[39;49;00m
sourceget()
[94mif[39;49;00m verbose:
[90m# skip whitespace and comments[39;49;00m
[94mif[39;49;00m this [95min[39;49;00m WHITESPACE:
[94mcontinue[39;49;00m
[94mif[39;49;00m this == [33m"[39;49;00m[33m#[39;49;00m[33m"[39;49;00m:
[94mwhile[39;49;00m [94mTrue[39;49;00m:
this = sourceget()
[94mif[39;49;00m this [95mis[39;49;00m [94mNone[39;49;00m [95mor[39;49;00m this == [33m"[39;49;00m[33m\n[39;49;00m[33m"[39;49;00m:
[94mbreak[39;49;00m
[94mcontinue[39;49;00m
[94mif[39;49;00m this[[94m0[39;49;00m] == [33m"[39;49;00m[33m\\[39;49;00m[33m"[39;49;00m:
code = _escape(source, this, state)
subpatternappend(code)
[94melif[39;49;00m this [95mnot[39;49;00m [95min[39;49;00m SPECIAL_CHARS:
subpatternappend((LITERAL, _ord(this)))
[94melif[39;49;00m this == [33m"[39;49;00m[33m[[39;49;00m[33m"[39;49;00m:
here = source.tell() - [94m1[39;49;00m
[90m# character set[39;49;00m
[96mset[39;49;00m = []
setappend = [96mset[39;49;00m.append
[90m## if sourcematch(":"):[39;49;00m
[90m## pass # handle character classes[39;49;00m
[94mif[39;49;00m sourcematch([33m"[39;49;00m[33m^[39;49;00m[33m"[39;49;00m):
setappend((NEGATE, [94mNone[39;49;00m))
[90m# check remaining characters[39;49;00m
start = [96mset[39;49;00m[:]
[94mwhile[39;49;00m [94mTrue[39;49;00m:
this = sourceget()
[94mif[39;49;00m this [95mis[39;49;00m [94mNone[39;49;00m:
[94mraise[39;49;00m source.error([33m"[39;49;00m[33munterminated character set[39;49;00m[33m"[39;49;00m,
source.tell() - here)
[94mif[39;49;00m this == [33m"[39;49;00m[33m][39;49;00m[33m"[39;49;00m [95mand[39;49;00m [96mset[39;49;00m != start:
[94mbreak[39;49;00m
[94melif[39;49;00m this[[94m0[39;49;00m] == [33m"[39;49;00m[33m\\[39;49;00m[33m"[39;49;00m:
code1 = _class_escape(source, this)
[94melse[39;49;00m:
code1 = LITERAL, _ord(this)
[94mif[39;49;00m sourcematch([33m"[39;49;00m[33m-[39;49;00m[33m"[39;49;00m):
[90m# potential range[39;49;00m
that = sourceget()
[94mif[39;49;00m that [95mis[39;49;00m [94mNone[39;49;00m:
[94mraise[39;49;00m source.error([33m"[39;49;00m[33munterminated character set[39;49;00m[33m"[39;49;00m,
source.tell() - here)
[94mif[39;49;00m that == [33m"[39;49;00m[33m][39;49;00m[33m"[39;49;00m:
[94mif[39;49;00m code1[[94m0[39;49;00m] [95mis[39;49;00m IN:
code1 = code1[[94m1[39;49;00m][[94m0[39;49;00m]
setappend(code1)
setappend((LITERAL, _ord([33m"[39;49;00m[33m-[39;49;00m[33m"[39;49;00m)))
[94mbreak[39;49;00m
[94mif[39;49;00m that[[94m0[39;49;00m] == [33m"[39;49;00m[33m\\[39;49;00m[33m"[39;49;00m:
code2 = _class_escape(source, that)
[94melse[39;49;00m:
code2 = LITERAL, _ord(that)
[94mif[39;49;00m code1[[94m0[39;49;00m] != LITERAL [95mor[39;49;00m code2[[94m0[39;49;00m] != LITERAL:
msg = [33m"[39;49;00m[33mbad character range [39;49;00m[33m%s[39;49;00m[33m-[39;49;00m[33m%s[39;49;00m[33m"[39;49;00m % (this, that)
[94mraise[39;49;00m source.error(msg, [96mlen[39;49;00m(this) + [94m1[39;49;00m + [96mlen[39;49;00m(that))
lo = code1[[94m1[39;49;00m]
hi = code2[[94m1[39;49;00m]
[94mif[39;49;00m hi < lo:
msg = [33m"[39;49;00m[33mbad character range [39;49;00m[33m%s[39;49;00m[33m-[39;49;00m[33m%s[39;49;00m[33m"[39;49;00m % (this, that)
[94mraise[39;49;00m source.error(msg, [96mlen[39;49;00m(this) + [94m1[39;49;00m + [96mlen[39;49;00m(that))
setappend((RANGE, (lo, hi)))
[94melse[39;49;00m:
[94mif[39;49;00m code1[[94m0[39;49;00m] [95mis[39;49;00m IN:
code1 = code1[[94m1[39;49;00m][[94m0[39;49;00m]
setappend(code1)
[90m# XXX: <fl> should move set optimization to compiler![39;49;00m
[94mif[39;49;00m _len([96mset[39;49;00m)==[94m1[39;49;00m [95mand[39;49;00m [96mset[39;49;00m[[94m0[39;49;00m][[94m0[39;49;00m] [95mis[39;49;00m LITERAL:
subpatternappend([96mset[39;49;00m[[94m0[39;49;00m]) [90m# optimization[39;49;00m
[94melif[39;49;00m _len([96mset[39;49;00m)==[94m2[39;49;00m [95mand[39;49;00m [96mset[39;49;00m[[94m0[39;49;00m][[94m0[39;49;00m] [95mis[39;49;00m NEGATE [95mand[39;49;00m [96mset[39;49;00m[[94m1[39;49;00m][[94m0[39;49;00m] [95mis[39;49;00m LITERAL:
subpatternappend((NOT_LITERAL, [96mset[39;49;00m[[94m1[39;49;00m][[94m1[39;49;00m])) [90m# optimization[39;49;00m
[94melse[39;49;00m:
[90m# XXX: <fl> should add charmap optimization here[39;49;00m
subpatternappend((IN, [96mset[39;49;00m))
[94melif[39;49;00m this [95min[39;49;00m REPEAT_CHARS:
[90m# repeat previous item[39;49;00m
here = source.tell()
[94mif[39;49;00m this == [33m"[39;49;00m[33m?[39;49;00m[33m"[39;49;00m:
[96mmin[39;49;00m, [96mmax[39;49;00m = [94m0[39;49;00m, [94m1[39;49;00m
[94melif[39;49;00m this == [33m"[39;49;00m[33m*[39;49;00m[33m"[39;49;00m:
[96mmin[39;49;00m, [96mmax[39;49;00m = [94m0[39;49;00m, MAXREPEAT
[94melif[39;49;00m this == [33m"[39;49;00m[33m+[39;49;00m[33m"[39;49;00m:
[96mmin[39;49;00m, [96mmax[39;49;00m = [94m1[39;49;00m, MAXREPEAT
[94melif[39;49;00m this == [33m"[39;49;00m[33m{[39;49;00m[33m"[39;49;00m:
[94mif[39;49;00m source.next == [33m"[39;49;00m[33m}[39;49;00m[33m"[39;49;00m:
subpatternappend((LITERAL, _ord(this)))
[94mcontinue[39;49;00m
[96mmin[39;49;00m, [96mmax[39;49;00m = [94m0[39;49;00m, MAXREPEAT
lo = hi = [33m"[39;49;00m[33m"[39;49;00m
[94mwhile[39;49;00m source.next [95min[39;49;00m DIGITS:
lo += sourceget()
[94mif[39;49;00m sourcematch([33m"[39;49;00m[33m,[39;49;00m[33m"[39;49;00m):
[94mwhile[39;49;00m source.next [95min[39;49;00m DIGITS:
hi += sourceget()
[94melse[39;49;00m:
hi = lo
[94mif[39;49;00m [95mnot[39;49;00m sourcematch([33m"[39;49;00m[33m}[39;49;00m[33m"[39;49;00m):
subpatternappend((LITERAL, _ord(this)))
source.seek(here)
[94mcontinue[39;49;00m
[94mif[39;49;00m lo:
[96mmin[39;49;00m = [96mint[39;49;00m(lo)
[94mif[39;49;00m [96mmin[39;49;00m >= MAXREPEAT:
[94mraise[39;49;00m [96mOverflowError[39;49;00m([33m"[39;49;00m[33mthe repetition number is too large[39;49;00m[33m"[39;49;00m)
[94mif[39;49;00m hi:
[96mmax[39;49;00m = [96mint[39;49;00m(hi)
[94mif[39;49;00m [96mmax[39;49;00m >= MAXREPEAT:
[94mraise[39;49;00m [96mOverflowError[39;49;00m([33m"[39;49;00m[33mthe repetition number is too large[39;49;00m[33m"[39;49;00m)
[94mif[39;49;00m [96mmax[39;49;00m < [96mmin[39;49;00m:
[94mraise[39;49;00m source.error([33m"[39;49;00m[33mmin repeat greater than max repeat[39;49;00m[33m"[39;49;00m,
source.tell() - here)
[94melse[39;49;00m:
[94mraise[39;49;00m [96mAssertionError[39;49;00m([33m"[39;49;00m[33munsupported quantifier [39;49;00m[33m%r[39;49;00m[33m"[39;49;00m % (char,))
[90m# figure out which item to repeat[39;49;00m
[94mif[39;49;00m subpattern:
item = subpattern[-[94m1[39;49;00m:]
[94melse[39;49;00m:
item = [94mNone[39;49;00m
[94mif[39;49;00m [95mnot[39;49;00m item [95mor[39;49;00m (_len(item) == [94m1[39;49;00m [95mand[39;49;00m item[[94m0[39;49;00m][[94m0[39;49;00m] [95mis[39;49;00m AT):
[94mraise[39;49;00m source.error([33m"[39;49;00m[33mnothing to repeat[39;49;00m[33m"[39;49;00m,
> source.tell() - here + [96mlen[39;49;00m(this))
[1m[31mE sre_constants.error: nothing to repeat at position 0[0m
[1m[31m/usr/lib/python3.6/sre_parse.py[0m:616: error
=========================== short test summary info ============================
FAILED assert_except.py::test_zero_division_custom_incinfo - sre_constants.er...
[31m============================== [31m[1m1 failed[0m[31m in 0.26s[0m[31m ===============================[0m
检查断言装饰器
- 代码抛出异常,但是和raises指定的异常类相匹配,所以不会断言失败
- 它相当于一个检查异常装饰器,功能:检查是否有异常,不确定是否有异常
- with pytest.raise(ZeroDivisionError) 对于故意测试异常代码的情况,使用可能会更好
- 而@pytest.mark.xfail(raises=ZeroDivisionError) 对于检查未修复的错误(即,可能会发生异常),使用检查断言可能会更好
"""异常装饰器"""
@pytest.mark.xfail(raises=ZeroDivisionError)
def test_except_decorate():
1 / 0
!pytest assert_except.py::test_except_decorate
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 1 item [0m
assert_except.py [33mx[0m[33m [100%][0m
[33m============================== [33m[1m1 xfailed[0m[33m in 0.03s[0m[33m ==============================[0m
setup 和 teardown
-
unittest的前置后置
- 每个测试方法前后都会执行:setup() teardown()
- 代码运行前后执行:setupClass() teardownClass()
-
pytest的前置后置
- 模块级别:setup_module、teardown_module
- 函数级别:setup_function、teardown_function,不在类中的方法
- 类级别:setup_class、teardown_class
- 方法级别:setup_method、teardown_method
- 方法细化级别:setup、teardown
注意:setup、teardown可以实现在执行用例前或结束后加入一些操作,但这种都是针对整个脚本全局生效的
"""前置后置条件"""
import pytest
def setup_module():
print("=====整个.py模块开始前只执行一次:打开浏览器=====")
def teardown_module():
print("=====整个.py模块结束后只执行一次:关闭浏览器=====")
def setup_function():
print("===每个函数级别用例开始前都执行setup_function===")
def teardown_function():
print("===每个函数级别用例结束后都执行teardown_function====")
def test_one():
print("one")
def test_two():
print("two")
class TestCase():
def setup_class(self):
print("====整个测试类开始前只执行一次setup_class====")
def teardown_class(self):
print("====整个测试类结束后只执行一次teardown_class====")
def setup_method(self):
print("==类里面每个用例执行前都会执行setup_method==")
def teardown_method(self):
print("==类里面每个用例结束后都会执行teardown_method==")
def setup(self):
print("=类里面每个用例执行前都会执行setup=")
def teardown(self):
print("=类里面每个用例结束后都会执行teardown=")
def test_three(self):
print("three")
def test_four(self):
print("four")
if __name__ == '__main__':
pytest.main(["-q", "-s", "-ra", "setup_teardown.py"])
!pytest setup_teardown.py
=====整个.py模块开始前只执行一次:打开浏览器=====
===每个函数级别用例开始前都执行setup_function===
one
.===每个函数级别用例结束后都执行teardown_function====
===每个函数级别用例开始前都执行setup_function===
two
.===每个函数级别用例结束后都执行teardown_function====
====整个测试类开始前只执行一次setup_class====
==类里面每个用例执行前都会执行setup_method==
=类里面每个用例执行前都会执行setup=
three
.=类里面每个用例结束后都会执行teardown=
==类里面每个用例结束后都会执行teardown_method==
====整个测试类结束后只执行一次teardown_class====
===每个函数级别用例开始前都执行setup_function===
E===每个函数级别用例结束后都执行teardown_function====
=====整个.py模块结束后只执行一次:关闭浏览器=====
==================================== ERRORS ====================================
_________________________ ERROR at setup of test_four __________________________
file /home/ubuntu/MySpace/Python/pytest/setup_teardown.py, line 48
def test_four(self):
E fixture 'self' not found
> available fixtures: _Module__pytest_setup_function, _Module__pytest_setup_module, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
> use 'pytest --fixtures [testpath]' for help on them.
/home/ubuntu/MySpace/Python/pytest/setup_teardown.py:48
=========================== short test summary info ============================
ERROR setup_teardown.py::test_four
3 passed, 1 error in 0.05s
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 4 items [0m
setup_teardown.py [32m.[0m[32m.[0m[32m.[0m[31mE[0m[31m [100%][0m
==================================== ERRORS ====================================
[31m[1m_________________________ ERROR at setup of test_four __________________________[0m
file /home/ubuntu/MySpace/Python/pytest/setup_teardown.py, line 48
def test_four(self):
[31mE fixture 'self' not found[0m
[31m> available fixtures: _Module__pytest_setup_function, _Module__pytest_setup_module, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory[0m
[31m> use 'pytest --fixtures [testpath]' for help on them.[0m
/home/ubuntu/MySpace/Python/pytest/setup_teardown.py:48
---------------------------- Captured stdout setup -----------------------------
===每个函数级别用例开始前都执行setup_function===
=========================== short test summary info ============================
ERROR setup_teardown.py::test_four
[31m========================== [32m3 passed[0m, [31m[1m1 error[0m[31m in 0.04s[0m[31m ==========================[0m
fixture
如果有以下场景:用例 1 需要先登录,用例 2 不需要登录,用例 3 需要先登录。很显然无法用 setup 和 teardown 来实现了
fixture可以让我们自定义测试用例的前置条件
fixture基础
fixture的优势
- 命名方式灵活,不局限于
setup和teardown这几个命名 conftest.py配置里可以实现数据共享,不需要import就能自动找到fixturescope="module"可以实现多个.py跨文件共享前置scope="session"以实现多个.py跨文件使用一个session来完成多个用例
fixture参数列表
@pytest.fixture(scope="function", params=None, autouse=False, ids=None, name=None)
def test():
print("fixture初始化的参数列表")
-
参数列表
- scope:可以理解成fixture的作用域,默认:function,还有class、module、package、session四个【常用】
- autouse:默认:False,需要用例手动调用该fixture;如果是True,所有作用域内的测试用例都会自动调用该fixture
- name:默认:装饰器的名称,同一模块的fixture相互调用建议写个不同的name
-
注意
- session的作用域:是整个测试会话,即开始执行pytest到结束测试
调用 fixture
方法一:将fixture名称作为测试用例函数的输入参数
import pytest
@pytest.fixture
def login():
print("请输入帐号和密码")
def test_s1(login):
print("用例1,登录后进行的,操作 111")
"""不穿login"""
def test_s2():
print("用例2,不需要登录,操作 222")
!pytest fixture.py::test_s1
!pytest fixture.py::test_s2
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 1 item [0m
fixture.py [32m.[0m[32m [100%][0m
[32m============================== [32m[1m1 passed[0m[32m in 0.01s[0m[32m ===============================[0m
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 1 item [0m
fixture.py [32m.[0m[32m [100%][0m
[32m============================== [32m[1m1 passed[0m[32m in 0.01s[0m[32m ===============================[0m
方法二:测试用例加上装饰器:@pytest.mark.usefixtures(fixture_name)
import pytest
@pytest.fixture
def login():
print("请输入帐号和密码")
@pytest.fixture
def login2():
print("please输入账号,密码先登录")
@pytest.mark.usefixtures("login2", "login")
def test_s11():
print("用例 11:登录之后其它动作 111")
!pytest fixture.py::test_s11
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 1 item [0m
fixture.py [32m.[0m[32m [100%][0m
[32m============================== [32m[1m1 passed[0m[32m in 0.01s[0m[32m ===============================[0m
方法三:fixture设置autouse=True
- 在类声明上面加 @pytest.mark.usefixtures() ,代表这个类里面所有测试用例都会调用该fixture
- 可以叠加多个 @pytest.mark.usefixtures() ,先执行的放底层,后执行的放上层
- 可以传多个fixture参数,先执行的放前面,后执行的放后面
- 如果fixture有返回值,用 @pytest.mark.usefixtures() 是无法获取到返回值的,必须用传参的方式
@pytest.fixture(autouse=True)
def login3():
print("====auto===")
"""不是test开头,加了装饰器也不会执行fixture"""
@pytest.mark.usefixtures("login2")
def test_loginss():
print(123)
!pytest fixture.py::test_loginss
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 1 item [0m
fixture.py [32m.[0m[32m [100%][0m
[32m============================== [32m[1m1 passed[0m[32m in 0.01s[0m[32m ===============================[0m
fixture实例化顺序
- 实例化【session > package > module > class > function】
- 具有相同作用域的fixture遵循测试函数中声明的顺序,并遵循fixture之间的依赖关系【在fixture_A里面依赖的fixture_B优先实例化,然后到fixture_A实例化】
- 自动使用(autouse=True)的fixture将在显式使用(传参或装饰器)的fixture之前实例化
import pytest
order = []
@pytest.fixture(scope="session")
def s1():
order.append("s1")
@pytest.fixture(scope="module")
def m1():
order.append("m1")
@pytest.fixture
def f1(f3, a1):
# 先实例化f3, 再实例化a1, 最后实例化f1
order.append("f1")
assert f3 == 123
@pytest.fixture
def f3():
order.append("f3")
a = 123
yield a
@pytest.fixture
def a1():
order.append("a1")
@pytest.fixture
def f2():
order.append("f2")
def test_order(f1, m1, f2, s1):
# m1、s1在f1后,但因为scope范围大,所以会优先实例化
assert order == ["s1", "m1", "f3", "a1", "f1", "f2"]
!pytest fixture_order.py::test_order
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 1 item [0m
fixture_order.py [32m.[0m[32m [100%][0m
[32m============================== [32m[1m1 passed[0m[32m in 0.01s[0m[32m ===============================[0m
fixture注意点
添加了 @pytest.fixture ,如果fixture还想依赖其他fixture,需要用函数传参的方式,不能用 @pytest.mark.usefixtures() 的方式,否则会不生效
import pytest
@pytest.fixture(scope="session")
def open():
print("===打开浏览器===")
@pytest.fixture
# @pytest.mark.usefixtures("open") 不可取!!!不生效!!!
def test_login(open):
# 方法级别前置操作setup
print(f"输入账号,密码先登录{open}")
!pytest fixture_attention.py::test_login
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 0 items [0m
[33m============================ [33mno tests ran[0m[33m in 0.01s[0m[33m =============================[0m
[31mERROR: not found: /home/ubuntu/MySpace/Python/pytest/fixture_attention.py::test_login
(no name '/home/ubuntu/MySpace/Python/pytest/fixture_attention.py::test_login' in any of [<Module fixture_attention.py>])
[0m
fixture实现前置和后置
fixture之yield实现teardown
用fixture实现teardown并不是一个独立的函数,而是用 yield 关键字来开启teardown操作
- yield注意事项
- 如果yield前面的代码,即setup部分已经抛出异常了,则不会执行yield后面的teardown内容
- 如果测试用例抛出异常,yield后面的teardown内容还是会正常执行
import pytest
@pytest.fixture(scope="session")
def open():
# 会话前置操作setup
print("===打开浏览器===")
test = "测试变量是否返回"
yield test
# 会话后置操作teardown
print("==关闭浏览器==")
@pytest.fixture
def login(open):
# 方法级别前置操作setup
print(f"输入账号,密码先登录{open}")
name = "==我是账号=="
pwd = "==我是密码=="
age = "==我是年龄=="
# 返回变量
yield name, pwd, age
# 方法级别后置操作teardown
print("登录成功")
def test_s1(login):
print("==用例1==")
# 返回的是一个元组
print(login)
# 分别赋值给不同变量
name, pwd, age = login
print(name, pwd, age)
assert "账号" in name
assert "密码" in pwd
assert "年龄" in age
def test_s2(login):
print("==用例2==")
print(login)
!pytest fixture_teardown.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 2 items [0m
fixture_teardown.py [32m.[0m[32m.[0m[32m [100%][0m
[32m============================== [32m[1m2 passed[0m[32m in 0.01s[0m[32m ===============================[0m
with和yeild
import pytest
@pytest.fixture(scope="module")
def smtp_connection():
with smtp.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection:
yield smtp_connection # provide the fixture value
!pytest fixture_yeild_with.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 0 items [0m
[33m============================ [33mno tests ran[0m[33m in 0.00s[0m[33m =============================[0m
addfinalizer函数
- 如果 request.addfinalizer() 前面的代码,即setup部分已经抛出异常了,则不会执行 request.addfinalizer() 的teardown内容(和yield相似,应该是最近新版本改成一致了)
- 可以声明多个终结函数并调用
@pytest.fixture(scope="module")
def test_addfinalizer(request):
# 前置操作setup
print("==再次打开浏览器==")
test = "test_addfinalizer"
def fin():
# 后置操作teardown
print("==再次关闭浏览器==")
request.addfinalizer(fin)
# 返回前置操作的变量
return test
def test_anthor(test_addfinalizer):
print("==最新用例==", test_addfinalizer)
conftest.py
-
conftest.py --- 一个专门存放 fixture 的配置文件,多个测试用例文件(test_*.py)的所有用例都需要用登录功能来作为前置操作,那就不能把登录功能写到某个用例文件中去了
-
conftest.py的出现,就是为了解决上述问题,单独管理一些全局的fixture
-
conftest.py配置fixture注意事项
- pytest会默认读取conftest.py里面的所有fixture
- conftest.py 文件名称是固定的,不能改动
- conftest.py只对同一个package下的所有测试用例生效
- 不同目录可以有自己的conftest.py,一个项目中可以有多个conftest.py
- 测试用例文件中不需要手动import conftest.py,pytest会自动查找
!tree ./conftest/
!pytest ./conftest/run.py
[01;34m./conftest/[00m
├── confest.py
├── [01;34m__pycache__[00m
│ └── test_1.cpython-36-pytest-5.4.1.pyc
├── run.py
└── test_1.py
1 directory, 4 files
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 0 items [0m
[33m============================ [33mno tests ran[0m[33m in 0.00s[0m[33m =============================[0m
fixture传参request
- 为了提高复用性,我们在写测试用例的时候,会用到不同的fixture,比如:最常见的登录操作,大部分的用例的前置条件都是登录
- 假设不同的用例想登录不同的测试账号,那么登录fixture就不能把账号写死,需要通过传参的方式来完成登录操作
一个参数
import pytest
@pytest.fixture()
def login(request):
name = request.param
print(f"== 账号是:{name} ==")
return name
data = ["pyy1", "polo"]
ids = [f"login_test_name is:{name}" for name in data]
"""
(1)添加 indirect=True 参数是为了把 login 当成一个函数去执行,而不是一个参数,并且将data当做参数传入函数
(2)def test_name(login) ,这里的login是获取fixture返回的值
"""
@pytest.mark.parametrize("login", data, ids=ids, indirect=True)
def test_name(login):
print(f" 测试用例的登录账号是:{login} ")
!pytest -s fixture/fixture_request_one.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 2 items [0m
fixture/fixture_request_one.py == 账号是:pyy1 ==
测试用例的登录账号是:pyy1
[32m.[0m== 账号是:polo ==
测试用例的登录账号是:polo
[32m.[0m
[32m============================== [32m[1m2 passed[0m[32m in 0.01s[0m[32m ===============================[0m
多个参数
需要传多个参数,需要通过字典去传
"""多个参数"""
import pytest
@pytest.fixture()
def login(request):
param = request.param
print(f"账号是:{param['username']}, 密码是:{param['passwd']}")
return param
data = [
{"username": "crisimple1", "passwd": "123456"},
{"username": "crisimple2", "passwd": "654321"},
]
@pytest.mark.parametrize("login", data, indirect=True)
def test_login(login):
print(f"账号是:{login['username']},密码是:{login['passwd']}")
!pytest -v -s fixture/fixture_request_more.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 2 items [0m
fixture/fixture_request_more.py::test_login[login0] 账号是:crisimple1, 密码是:123456
账号是:crisimple1,密码是:123456
[32mPASSED[0m
fixture/fixture_request_more.py::test_login[login1] 账号是:crisimple2, 密码是:654321
账号是:crisimple2,密码是:654321
[32mPASSED[0m
[32m============================== [32m[1m2 passed[0m[32m in 0.01s[0m[32m ===============================[0m
多个fixture(只加一个装饰器)
"""多个fixture,只加一个装饰器"""
import pytest
@pytest.fixture(scope="function")
def input_user(request):
user = request.param
print("登录账号为:%s" % user)
return user
@pytest.fixture(scope="function")
def input_pwd(request):
pwd = request.param
print("登录密码为:%s" % pwd)
return pwd
data = [
("name1", "pwd1"),
("name2", "pwd2")
]
@pytest.mark.parametrize("input_user, input_pwd", data, indirect=True)
def test_more_fixture(input_user, input_pwd):
print("fixture返回的内容:", input_user, input_pwd)
!pytest -v -s fixture/fixture_one_fixture.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 2 items [0m
fixture/fixture_one_fixture.py::test_more_fixture[name1-pwd1] 登录账号为:name1
登录密码为:pwd1
fixture返回的内容: name1 pwd1
[32mPASSED[0m
fixture/fixture_one_fixture.py::test_more_fixture[name2-pwd2] 登录账号为:name2
登录密码为:pwd2
fixture返回的内容: name2 pwd2
[32mPASSED[0m
[32m============================== [32m[1m2 passed[0m[32m in 0.01s[0m[32m ===============================[0m
多个fixture(叠加装饰器)
"""多个fixture"""
import pytest
@pytest.fixture(scope="function")
def input_user(request):
user = request.param
print("登录账号为:%s" % user)
return user
@pytest.fixture(scope="function")
def input_pwd(request):
pwd = request.param
print("登录密码为:%s" % pwd)
return pwd
name = ["name1", "name2"]
passwd = ["pwd1", "pwd2"]
@pytest.mark.parametrize("input_user", name, indirect=True)
@pytest.mark.parametrize("input_pwd", passwd, indirect=True)
def test_more_fixture(input_user, input_pwd):
print("fixture返回的内容:", input_user, input_pwd)
!pytest -v -s fixture/fixture_more_fixture.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 4 items [0m
fixture/fixture_more_fixture.py::test_more_fixture[pwd1-name1] 登录账号为:name1
登录密码为:pwd1
fixture返回的内容: name1 pwd1
[32mPASSED[0m
fixture/fixture_more_fixture.py::test_more_fixture[pwd1-name2] 登录账号为:name2
登录密码为:pwd1
fixture返回的内容: name2 pwd1
[32mPASSED[0m
fixture/fixture_more_fixture.py::test_more_fixture[pwd2-name1] 登录账号为:name1
登录密码为:pwd2
fixture返回的内容: name1 pwd2
[32mPASSED[0m
fixture/fixture_more_fixture.py::test_more_fixture[pwd2-name2] 登录账号为:name2
登录密码为:pwd2
fixture返回的内容: name2 pwd2
[32mPASSED[0m
[32m============================== [32m[1m4 passed[0m[32m in 0.02s[0m[32m ===============================[0m
skip、skipif跳过用例
- pytest.mark.skip 开标记那些用例可以运行
- 希望满足某些条件才执行某些测试用例,否则 pytest 会跳过运行该测试用例
pytest.mark.skip
跳过执行测试用例,有可选参数 reason:跳过的原因,会在执行结果中打印
- @pytest.mark.skip 可以加在函数上、类上、方法上
- 如果加在类上,类里面的 所有测试用例都不会执行
import pytest
@pytest.fixture(autouse=True)
def login():
print("====登录====")
def test_case01():
print("我是测试用例11111")
@pytest.mark.skip(reason="不执行该用例!!因为没写好!!")
def test_case02():
print("我是测试用例22222")
class Test1:
def test_1(self):
print("%% 我是类测试用例1111 %%")
@pytest.mark.skip(reason="不想执行")
def test_2(self):
print("%% 我是类测试用例2222 %%")
@pytest.mark.skip(reason="类也可以跳过不执行")
class TestSkip:
def test_1(self):
print("%% 不会执行 %%")
!pytest skip_skipif.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 5 items [0m
skip_skipif.py [32m.[0m[33ms[0m[32m.[0m[33ms[0m[33ms[0m[32m [100%][0m
[32m========================= [32m[1m2 passed[0m, [33m3 skipped[0m[32m in 0.02s[0m[32m =========================[0m
pytest.mark()
- 作用:在测试用例执行期间强制跳过不再执行剩余内容
- 类似于在python循环中,满足某些条件则 break 跳出循环
def test_function():
n = 1
while True:
print(f"这是我第{n}条用例")
n += 1
if n == 5:
pytest.skip("我跑五次了不跑了")
test_function()
这是我第1条用例
这是我第2条用例
这是我第3条用例
这是我第4条用例
---------------------------------------------------------------------------
Skipped Traceback (most recent call last)
<ipython-input-76-4408d6b29e56> in <module>
7 pytest.skip("我跑五次了不跑了")
8
----> 9 test_function()
<ipython-input-76-4408d6b29e56> in test_function()
5 n += 1
6 if n == 5:
----> 7 pytest.skip("我跑五次了不跑了")
8
9 test_function()
~/.local/lib/python3.6/site-packages/_pytest/outcomes.py in skip(msg, allow_module_level)
143 """
144 __tracebackhide__ = True
--> 145 raise Skipped(msg=msg, allow_module_level=allow_module_level)
146
147
Skipped: 我跑五次了不跑了
pytest.skip(msg="", allow_module_level=False)
当 allow_module_level=True,可以设置在模块级别跳过整个模块
import sys
import pytest
if sys.platform.startswith("win"):
pytest.skip("skipping windows-only tests", allow_module_level=True)
@pytest.fixture(autouse=True)
def login():
print("====登录====")
def test_case01():
print("我是测试用例11111")
!pytest skip_allow_module_level.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 1 item [0m
skip_allow_module_level.py [32m.[0m[32m [100%][0m
[32m============================== [32m[1m1 passed[0m[32m in 0.01s[0m[32m ===============================[0m
pytest.mark.skipif(condition, reason="")
- 作用:希望有条件地跳过某些测试用例
- condition返回True 才会跳过
@pytest.mark.skipif(sys.platform == 'linux', reason="does not run on windows")
class TestSkipIf(object):
def test_function(self):
print("不能在window上运行")
!pytest skip_makr_skipif.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 1 item [0m
skip_makr_skipif.py [32m.[0m[32m [100%][0m
[32m============================== [32m[1m1 passed[0m[32m in 0.01s[0m[32m ===============================[0m
跳过标记
- 可以将 pytest.mark.skip 和 pytest.mark.skipif 赋值给一个标记变量
- 在不同模块之间共享这个标记变量
- 若有多个模块的测试用例需要用到相同的 skip 或 skipif ,可以用一个单独的文件去管理这些通用标记,然后适用于整个测试用例集
# 标记
skipmark = pytest.mark.skip(reason="不能在window上运行=====")
skipifmark = pytest.mark.skipif(sys.platform == 'win32', reason="不能在window上运行啦啦啦=====")
@skipmark
class TestSkip_Mark(object):
@skipifmark
def test_function(self):
print("测试标记")
def test_def(self):
print("测试标记")
@skipmark
def test_skip():
print("测试标记")
!pytest skip_variable.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
collected 3 items [0m
skip_variable.py [33ms[0m[33ms[0m[33ms[0m[33m [100%][0m
[33m============================== [33m[1m3 skipped[0m[33m in 0.01s[0m[33m ==============================[0m
pytest.importtoskip(modname:str, minversion:Optional[str]=None,reason:Optional[str]=None)
-
作用:如果缺少某些导入,则跳过模块中的所有测试
-
参数列表
- modname:模块名
- minversion:版本号
- reasone:跳过原因,默认不给也行
pexpect = pytest.importorskip("pexpect", minversion="0.3")
@pexpect
def test_import():
print("test")
!pytest skip_importtoskip.py
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-88-05d178e742c2> in <module>
2
3
----> 4 @pexpect
5 def test_import():
6 print("test")
TypeError: 'module' object is not callable
自定义标记mark
pytest 可以支持自定义标记,自定义标记可以把一个 web 项目划分多个模块,然后指定模块名称执行
mark的基本用法
- 执行特定标记的用例
- 执行不包含某一类标记的用例
- 解决warning问题
import pytest
@pytest.mark.toutiao
def test_toutiao():
print("测试头条")
@pytest.mark.weibo
def test_weibo():
print("测试微博")
@pytest.mark.toutiao
def test_toutiao1():
print("再次测试头条")
@pytest.mark.xinlang
class TestClass:
def test_method(self):
print("测试新浪")
"""没有标记测试"""
def test_nomark():
print("没有标记测试")
!pytest -s -m weibo mark/mark.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest/mark, inifile: pytest.ini
collected 5 items / 4 deselected / 1 selected [0m
mark/mark.py 测试微博
[32m.[0m
[33m=============================== warnings summary ===============================[0m
mark/mark.py:4
/home/ubuntu/MySpace/Python/pytest/mark/mark.py:4: PytestUnknownMarkWarning: Unknown pytest.mark.toutiao - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html
@pytest.mark.toutiao
mark/mark.py:12
/home/ubuntu/MySpace/Python/pytest/mark/mark.py:12: PytestUnknownMarkWarning: Unknown pytest.mark.toutiao - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html
@pytest.mark.toutiao
mark/mark.py:16
/home/ubuntu/MySpace/Python/pytest/mark/mark.py:16: PytestUnknownMarkWarning: Unknown pytest.mark.xinlang - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html
@pytest.mark.xinlang
-- Docs: https://docs.pytest.org/en/latest/warnings.html
[33m================= [32m1 passed[0m, [33m[1m4 deselected[0m, [33m[1m3 warnings[0m[33m in 0.02s[0m[33m ==================[0m
"""
问题:上面的执行结果是有 warning 的
解决方案:在 同级目录 下添加 pytest.ini 配置文件,加上自定义mark
注意:pytest.ini需要和运行的测试用例同一个目录,或在根目录下作用于全局
"""
!pytest -s -m weibo mark/mark.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest/mark, inifile: pytest.ini
collected 5 items / 4 deselected / 1 selected [0m
mark/mark.py 测试微博
[32m.[0m
[33m=============================== warnings summary ===============================[0m
mark/mark.py:4
/home/ubuntu/MySpace/Python/pytest/mark/mark.py:4: PytestUnknownMarkWarning: Unknown pytest.mark.toutiao - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html
@pytest.mark.toutiao
mark/mark.py:12
/home/ubuntu/MySpace/Python/pytest/mark/mark.py:12: PytestUnknownMarkWarning: Unknown pytest.mark.toutiao - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html
@pytest.mark.toutiao
mark/mark.py:16
/home/ubuntu/MySpace/Python/pytest/mark/mark.py:16: PytestUnknownMarkWarning: Unknown pytest.mark.xinlang - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html
@pytest.mark.xinlang
-- Docs: https://docs.pytest.org/en/latest/warnings.html
[33m================= [32m1 passed[0m, [33m[1m4 deselected[0m, [33m[1m3 warnings[0m[33m in 0.01s[0m[33m ==================[0m
"""如果不想执行标记 weibo 的用例,取反即可"""
!pytest -s -m "no weibo" mark/mark.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest/mark, inifile: pytest.ini
collected 5 items [0m
INTERNALERROR> Traceback (most recent call last):
INTERNALERROR> File "/home/ubuntu/.local/lib/python3.6/site-packages/_pytest/mark/legacy.py", line 86, in matchmark
INTERNALERROR> return eval(markexpr, {}, MarkMapping.from_item(colitem))
INTERNALERROR> File "<string>", line 1
INTERNALERROR> no weibo
INTERNALERROR> ^
INTERNALERROR> SyntaxError: unexpected EOF while parsing
INTERNALERROR>
INTERNALERROR> During handling of the above exception, another exception occurred:
INTERNALERROR>
INTERNALERROR> Traceback (most recent call last):
INTERNALERROR> File "/home/ubuntu/.local/lib/python3.6/site-packages/_pytest/main.py", line 191, in wrap_session
INTERNALERROR> session.exitstatus = doit(config, session) or 0
INTERNALERROR> File "/home/ubuntu/.local/lib/python3.6/site-packages/_pytest/main.py", line 246, in _main
INTERNALERROR> config.hook.pytest_collection(session=session)
INTERNALERROR> File "/home/ubuntu/.local/lib/python3.6/site-packages/pluggy/hooks.py", line 286, in __call__
INTERNALERROR> return self._hookexec(self, self.get_hookimpls(), kwargs)
INTERNALERROR> File "/home/ubuntu/.local/lib/python3.6/site-packages/pluggy/manager.py", line 93, in _hookexec
INTERNALERROR> return self._inner_hookexec(hook, methods, kwargs)
INTERNALERROR> File "/home/ubuntu/.local/lib/python3.6/site-packages/pluggy/manager.py", line 87, in <lambda>
INTERNALERROR> firstresult=hook.spec.opts.get("firstresult") if hook.spec else False,
INTERNALERROR> File "/home/ubuntu/.local/lib/python3.6/site-packages/pluggy/callers.py", line 208, in _multicall
INTERNALERROR> return outcome.get_result()
INTERNALERROR> File "/home/ubuntu/.local/lib/python3.6/site-packages/pluggy/callers.py", line 80, in get_result
INTERNALERROR> raise ex[1].with_traceback(ex[2])
INTERNALERROR> File "/home/ubuntu/.local/lib/python3.6/site-packages/pluggy/callers.py", line 187, in _multicall
INTERNALERROR> res = hook_impl.function(*args)
INTERNALERROR> File "/home/ubuntu/.local/lib/python3.6/site-packages/_pytest/main.py", line 257, in pytest_collection
INTERNALERROR> return session.perform_collect()
INTERNALERROR> File "/home/ubuntu/.local/lib/python3.6/site-packages/_pytest/main.py", line 455, in perform_collect
INTERNALERROR> session=self, config=self.config, items=items
INTERNALERROR> File "/home/ubuntu/.local/lib/python3.6/site-packages/pluggy/hooks.py", line 286, in __call__
INTERNALERROR> return self._hookexec(self, self.get_hookimpls(), kwargs)
INTERNALERROR> File "/home/ubuntu/.local/lib/python3.6/site-packages/pluggy/manager.py", line 93, in _hookexec
INTERNALERROR> return self._inner_hookexec(hook, methods, kwargs)
INTERNALERROR> File "/home/ubuntu/.local/lib/python3.6/site-packages/pluggy/manager.py", line 87, in <lambda>
INTERNALERROR> firstresult=hook.spec.opts.get("firstresult") if hook.spec else False,
INTERNALERROR> File "/home/ubuntu/.local/lib/python3.6/site-packages/pluggy/callers.py", line 208, in _multicall
INTERNALERROR> return outcome.get_result()
INTERNALERROR> File "/home/ubuntu/.local/lib/python3.6/site-packages/pluggy/callers.py", line 80, in get_result
INTERNALERROR> raise ex[1].with_traceback(ex[2])
INTERNALERROR> File "/home/ubuntu/.local/lib/python3.6/site-packages/pluggy/callers.py", line 187, in _multicall
INTERNALERROR> res = hook_impl.function(*args)
INTERNALERROR> File "/home/ubuntu/.local/lib/python3.6/site-packages/_pytest/mark/__init__.py", line 151, in pytest_collection_modifyitems
INTERNALERROR> deselect_by_mark(items, config)
INTERNALERROR> File "/home/ubuntu/.local/lib/python3.6/site-packages/_pytest/mark/__init__.py", line 139, in deselect_by_mark
INTERNALERROR> if matchmark(item, matchexpr):
INTERNALERROR> File "/home/ubuntu/.local/lib/python3.6/site-packages/_pytest/mark/legacy.py", line 88, in matchmark
INTERNALERROR> raise SyntaxError(str(e) + "\nMarker expression must be valid Python!")
INTERNALERROR> File "<string>", line None
INTERNALERROR> SyntaxError: unexpected EOF while parsing (<string>, line 1)
INTERNALERROR> Marker expression must be valid Python!
[33m============================= [33m[1m3 warnings[0m[33m in 0.02s[0m[33m ==============================[0m
"""执行多个自定义标记的用例"""
!pytest -s -m "toutiao or weibo" mark/mark.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest/mark, inifile: pytest.ini
collected 5 items / 2 deselected / 3 selected [0m
mark/mark.py 测试头条
[32m.[0m测试微博
[32m.[0m再次测试头条
[32m.[0m
[33m=============================== warnings summary ===============================[0m
mark/mark.py:4
/home/ubuntu/MySpace/Python/pytest/mark/mark.py:4: PytestUnknownMarkWarning: Unknown pytest.mark.toutiao - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html
@pytest.mark.toutiao
mark/mark.py:12
/home/ubuntu/MySpace/Python/pytest/mark/mark.py:12: PytestUnknownMarkWarning: Unknown pytest.mark.toutiao - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html
@pytest.mark.toutiao
mark/mark.py:16
/home/ubuntu/MySpace/Python/pytest/mark/mark.py:16: PytestUnknownMarkWarning: Unknown pytest.mark.xinlang - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html
@pytest.mark.xinlang
-- Docs: https://docs.pytest.org/en/latest/warnings.html
[33m================= [32m3 passed[0m, [33m[1m2 deselected[0m, [33m[1m3 warnings[0m[33m in 0.02s[0m[33m ==================[0m
参数化@pytest.mark.parametrize
-
pytest允许在多个级别启用测试参数化:
- pytest.fixture() 允许fixture有参数化功能(后面讲解)
- @pytest.mark.parametrize 允许在测试函数或类中定义多组参数和fixtures
- pytest_generate_tests 允许定义自定义参数化方案或扩展(拓展)
-
那么什么情况下可以使用参数化呢?
——只有测试数据和期望结果不一样,但操作步骤是一样的测试用例可以用上参数化,例如下面的例子
def add_sample(a, b):
return a + b
def test_1():
assert 3 + 5 == 9
def test_2():
assert 2 + 4 == 6
def test_3():
assert 6 * 9 == 42
实际应用场景:实际Web UI自动化中的开发场景,比如是一个登录框
- 你肯定需要测试账号空、密码空、账号密码都为空、账号不存在、密码错误、账号密码正确等情况
- 这些用例的区别就在于输入的测试数据和对应的交互结果
- 所以我们可以只写一条登录测试用例,然后把多组测试数据和期望结果参数化,节省很多代码量
"""将上面重复的代码参数化"""
import pytest
@pytest.mark.parametrize("test_input, excepted", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, excepted):
print(f"测试数据{test_input}, 期望结果{excepted}")
assert eval(test_input) == excepted
!pytest mark/mark_parametrize.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest/mark, inifile: pytest.ini
collected 3 items [0m
mark/mark_parametrize.py [32m.[0m[32m.[0m[31mF[0m[31m [100%][0m
=================================== FAILURES ===================================
[31m[1m______________________________ test_eval[6*9-42] _______________________________[0m
test_input = '6*9', excepted = 42
[37m@pytest[39;49;00m.mark.parametrize([33m"[39;49;00m[33mtest_input, excepted[39;49;00m[33m"[39;49;00m, [([33m"[39;49;00m[33m3+5[39;49;00m[33m"[39;49;00m, [94m8[39;49;00m), ([33m"[39;49;00m[33m2+4[39;49;00m[33m"[39;49;00m, [94m6[39;49;00m), ([33m"[39;49;00m[33m6*9[39;49;00m[33m"[39;49;00m, [94m42[39;49;00m)])
[94mdef[39;49;00m [92mtest_eval[39;49;00m(test_input, excepted):
[96mprint[39;49;00m([33mf[39;49;00m[33m"[39;49;00m[33m测试数据[39;49;00m[33m{[39;49;00mtest_input[33m}[39;49;00m[33m, 期望结果[39;49;00m[33m{[39;49;00mexcepted[33m}[39;49;00m[33m"[39;49;00m)
> [94massert[39;49;00m [96meval[39;49;00m(test_input) == excepted
[1m[31mE AssertionError: assert 54 == 42[0m
[1m[31mE + where 54 = eval('6*9')[0m
[1m[31mmark/mark_parametrize.py[0m:6: AssertionError
----------------------------- Captured stdout call -----------------------------
测试数据6*9, 期望结果42
=========================== short test summary info ============================
FAILED mark/mark_parametrize.py::test_eval[6*9-42] - AssertionError: assert 5...
[31m========================= [31m[1m1 failed[0m, [32m2 passed[0m[31m in 0.11s[0m[31m ==========================[0m
源码解读
def parametrize(self,argnames, argvalues, indirect=False, ids=None, scope=None):
pass
- argnames
- 源码解析:a comma-separated string denoting one or more argument names, or a list/tuple of argument strings.
- 含义:参数名字
- 格式:字符串"arg1,arg2,arg3"【需要用逗号分隔】
- 备注:源码中写了可以是参数字符串的list或者tuple
@pytest.mark.parametrize(["name", "pwd"], [("yy1", "123"), ("yy2", "123")]) # 错的
@pytest.mark.parametrize(("name", "pwd"), [("yy1", "123"), ("yy2", "123")]) # 错的
@pytest.mark.parametrize("name,pwd", [("yy1", "123"), ("yy2", "123")])
-
argvalues
- 源码解析:
- The list of argvalues determines how often a test is invoked with different argument values.
- If only one argname was specified argvalues is a list of values.【只有一个参数,则是值列表】
- If N argnames were specified, argvalues must be a list of N-tuples, where each tuple-element specifies a value for its respective argname.【如果有多个参数,则用元组来存每一组值】
- 含义:参数值列表
- 格式:必须是列表,如:[ val1,val2,val3 ]
- 如果只有一个参数,里面则是值的列表如:@pytest.mark.parametrize("username", ["yy", "yy2", "yy3"])
- 如果有多个参数例,则需要用元组来存放值,一个元组对应一组参数的值,如:@pytest.mark.parametrize("name,pwd", [("yy1", "123"), ("yy2", "123"), ("yy3", "123")])
- 备注:虽然源码说需要list包含tuple,但我试了下,tuple包含list,list包含list也是可以的........
- 源码解析:
-
ids
- 含义:用例的ID
- 格式:传一个字符串列表
- 作用:可以标识每一个测试用例,自定义测试数据结果的显示,为了增加可读性
- 强调:ids的长度需要与测试数据列表的长度一致
-
indirect
- 作用:如果设置成True,则把传进来的参数当函数执行,而不是一个参数(下一篇博文即讲解)
装饰测试类
当装饰器 @pytest.mark.parametrize 装饰测试类时,会将数据集合传递给类的所有测试用例方法
import pytest
data_1 = [1, 2, 3]
@pytest.mark.parametrize('a, b, expect', data_1)
class TestParametrize:
def test_parametrize_1(self, a, b, expect):
print('\n测试函数11111 测试数据为\n{}-{}'.format(a, b))
assert a + b == expect
def test_parametrize_2(self, a, b, expect):
print('\n测试函数22222 测试数据为\n{}-{}'.format(a, b))
assert a + b == expect
!pytest -v mark/mark_class_parametrize.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /home/ubuntu/MySpace/Python/pytest/mark, inifile: pytest.ini
collected 0 items / 1 error [0m
==================================== ERRORS ====================================
[31m[1m__________________ ERROR collecting mark_class_parametrize.py __________________[0m
[1m[31m../../../.local/lib/python3.6/site-packages/pluggy/hooks.py[0m:286: in __call__
[94mreturn[39;49;00m [96mself[39;49;00m._hookexec([96mself[39;49;00m, [96mself[39;49;00m.get_hookimpls(), kwargs)
[1m[31m../../../.local/lib/python3.6/site-packages/pluggy/manager.py[0m:93: in _hookexec
[94mreturn[39;49;00m [96mself[39;49;00m._inner_hookexec(hook, methods, kwargs)
[1m[31m../../../.local/lib/python3.6/site-packages/pluggy/manager.py[0m:87: in <lambda>
firstresult=hook.spec.opts.get([33m"[39;49;00m[33mfirstresult[39;49;00m[33m"[39;49;00m) [94mif[39;49;00m hook.spec [94melse[39;49;00m [94mFalse[39;49;00m,
[1m[31m../../../.local/lib/python3.6/site-packages/_pytest/python.py[0m:248: in pytest_pycollect_makeitem
res = [96mlist[39;49;00m(collector._genfunctions(name, obj))
[1m[31m../../../.local/lib/python3.6/site-packages/_pytest/python.py[0m:415: in _genfunctions
[96mself[39;49;00m.ihook.pytest_generate_tests.call_extra(methods, [96mdict[39;49;00m(metafunc=metafunc))
[1m[31m../../../.local/lib/python3.6/site-packages/pluggy/hooks.py[0m:324: in call_extra
[94mreturn[39;49;00m [96mself[39;49;00m(**kwargs)
[1m[31m../../../.local/lib/python3.6/site-packages/pluggy/hooks.py[0m:286: in __call__
[94mreturn[39;49;00m [96mself[39;49;00m._hookexec([96mself[39;49;00m, [96mself[39;49;00m.get_hookimpls(), kwargs)
[1m[31m../../../.local/lib/python3.6/site-packages/pluggy/manager.py[0m:93: in _hookexec
[94mreturn[39;49;00m [96mself[39;49;00m._inner_hookexec(hook, methods, kwargs)
[1m[31m../../../.local/lib/python3.6/site-packages/pluggy/manager.py[0m:87: in <lambda>
firstresult=hook.spec.opts.get([33m"[39;49;00m[33mfirstresult[39;49;00m[33m"[39;49;00m) [94mif[39;49;00m hook.spec [94melse[39;49;00m [94mFalse[39;49;00m,
[1m[31m../../../.local/lib/python3.6/site-packages/_pytest/python.py[0m:139: in pytest_generate_tests
metafunc.parametrize(*marker.args, **marker.kwargs, _param_mark=marker) [90m# type: ignore[misc] # noqa: F821[39;49;00m
[1m[31m../../../.local/lib/python3.6/site-packages/_pytest/python.py[0m:922: in parametrize
function_definition=[96mself[39;49;00m.definition,
[1m[31m../../../.local/lib/python3.6/site-packages/_pytest/mark/structures.py[0m:114: in _for_parametrize
[94mif[39;49;00m [96mlen[39;49;00m(param.values) != [96mlen[39;49;00m(argnames):
[1m[31mE TypeError: object of type 'int' has no len()[0m
=========================== short test summary info ============================
ERROR mark/mark_class_parametrize.py::TestParametrize - TypeError: object of ...
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
[31m=============================== [31m[1m1 error[0m[31m in 0.37s[0m[31m ===============================[0m
"笛卡尔积"-多个参数化装饰器
- 重点知识
- 一个函数或一个类可以装饰多个 @pytest.mark.parametrize
- 这种方式,最终生成的用例数是n×m,比如上面的代码就是:参数a的数据有3个,参数b的数据有2个,所以最终的用例数有3*2=6条
- 当参数化装饰器有很多个的时候,用例数都等于n×n×n×n×....
"""笛卡尔积,组合数据"""
import pytest
data_1 = [1, 2, 3]
data_2 = ['a', 'b']
@pytest.mark.parametrize('a', data_1)
@pytest.mark.parametrize('b', data_2)
def test_parametrize_1(a, b):
print(f'笛卡尔积 测试数据为 : {a},{b}')
!pytest -v mark/mark_many_parametrize.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /home/ubuntu/MySpace/Python/pytest/mark, inifile: pytest.ini
collected 6 items [0m
mark/mark_many_parametrize.py::test_parametrize_1[a-1] [32mPASSED[0m[32m [ 16%][0m
mark/mark_many_parametrize.py::test_parametrize_1[a-2] [32mPASSED[0m[32m [ 33%][0m
mark/mark_many_parametrize.py::test_parametrize_1[a-3] [32mPASSED[0m[32m [ 50%][0m
mark/mark_many_parametrize.py::test_parametrize_1[b-1] [32mPASSED[0m[32m [ 66%][0m
mark/mark_many_parametrize.py::test_parametrize_1[b-2] [32mPASSED[0m[32m [ 83%][0m
mark/mark_many_parametrize.py::test_parametrize_1[b-3] [32mPASSED[0m[32m [100%][0m
[32m============================== [32m[1m6 passed[0m[32m in 0.03s[0m[32m ===============================[0m
参数化-传入字典数据
"""字典"""
import pytest
data_1 = (
{
'user': 1,
'pwd': 2
},
{
'user': 3,
'pwd': 4
}
)
@pytest.mark.parametrize('dic', data_1)
def test_parametrize_1(dic):
print(f'测试数据为\n{dic}')
print(f'user:{dic["user"]},pwd{dic["pwd"]}')
!pytest -s mark/mark_dict_parametrize.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest/mark, inifile: pytest.ini
collected 2 items [0m
mark/mark_dict_parametrize.py 测试数据为
{'user': 1, 'pwd': 2}
user:1,pwd2
[32m.[0m测试数据为
{'user': 3, 'pwd': 4}
user:3,pwd4
[32m.[0m
[32m============================== [32m[1m2 passed[0m[32m in 0.01s[0m[32m ===============================[0m
参数化-标记数据
"""标记参数化"""
import pytest
@pytest.mark.parametrize("test_input,expected", [
("3+5", 8),
("2+4", 6),
pytest.param("6 * 9", 42, marks=pytest.mark.xfail),
pytest.param("6*6", 42, marks=pytest.mark.skip)
])
def test_mark(test_input, expected):
assert eval(test_input) == expected
!pytest -v mark/mark_mark_data.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /home/ubuntu/MySpace/Python/pytest/mark, inifile: pytest.ini
plugins: rerunfailures-9.0, repeat-0.8.0
collected 4 items [0m
mark/mark_mark_data.py::test_mark[3+5-8] [32mPASSED[0m[32m [ 25%][0m
mark/mark_mark_data.py::test_mark[2+4-6] [32mPASSED[0m[32m [ 50%][0m
mark/mark_mark_data.py::test_mark[6 * 9-42] [33mXFAIL[0m[32m [ 75%][0m
mark/mark_mark_data.py::test_mark[6*6-42] [33mSKIPPED[0m[32m [100%][0m
[32m=================== [32m[1m2 passed[0m, [33m1 skipped[0m, [33m1 xfailed[0m[32m in 0.04s[0m[32m ====================[0m
"""增加可读性"""
import pytest
data_1 = [
(1, 2, 3),
(4, 5, 9)
]
ids = ["a:{} + b:{} = expect:{}".format(a, b, expect) for a, b, expect in data_1]
@pytest.mark.parametrize('a, b, expect', data_1, ids=ids)
class TestParametrize(object):
def test_parametrize_1(self, a, b, expect):
print('测试函数1测试数据为{}-{}'.format(a, b))
assert a + b == expect
def test_parametrize_2(self, a, b, expect):
print('测试函数2数据为{}-{}'.format(a, b))
assert a + b == expect
!pytest -v mark/mark_read.py
参数化-增加可读性
多少组数据,就要有多少个id,然后组成一个id的列表
作用:主要是为了更加清晰看到用例的含义
pytest.ini
pytest配置文件可以改变pytest的运行方式,它是一个固定的文件pytest.ini文件,读取配置信息,按指定的方式去运行
非test文件
pytest里面有些文件是非test文件
- pytest.ini:pytest的主配置文件,可以改变pytest的默认行为
- conftest.py:测试用例的一些fixture配置
- init.py:识别该文件夹为python的package包
"""查看pytest.ini的配置选项"""
!pytest --help
usage: pytest [options] [file_or_dir] [file_or_dir] [...]
positional arguments:
file_or_dir
general:
-k EXPRESSION only run tests which match the given substring
expression. An expression is a python evaluatable
expression where all names are substring-matched against
test names and their parent classes. Example: -k
'test_method or test_other' matches all test functions
and classes whose name contains 'test_method' or
'test_other', while -k 'not test_method' matches those
that don't contain 'test_method' in their names. -k 'not
test_method and not test_other' will eliminate the
matches. Additionally keywords are matched to classes
and functions containing extra names in their
'extra_keyword_matches' set, as well as functions which
have names assigned directly to them. The matching is
case-insensitive.
-m MARKEXPR only run tests matching given mark expression. example:
-m 'mark1 and not mark2'.
--markers show markers (builtin, plugin and per-project ones).
-x, --exitfirst exit instantly on first error or failed test.
--maxfail=num exit after first num failures or errors.
--strict-markers, --strict
markers not registered in the `markers` section of the
configuration file raise errors.
-c file load configuration from `file` instead of trying to
locate one of the implicit configuration files.
--continue-on-collection-errors
Force test execution even if collection errors occur.
--rootdir=ROOTDIR Define root directory for tests. Can be relative path:
'root_dir', './root_dir', 'root_dir/another_dir/';
absolute path: '/home/user/root_dir'; path with
variables: '$HOME/root_dir'.
--fixtures, --funcargs
show available fixtures, sorted by plugin appearance
(fixtures with leading '_' are only shown with '-v')
--fixtures-per-test show fixtures per test
--import-mode={prepend,append}
prepend/append to sys.path when importing test modules,
default is to prepend.
--pdb start the interactive Python debugger on errors or
KeyboardInterrupt.
--pdbcls=modulename:classname
start a custom interactive Python debugger on errors.
For example:
--pdbcls=IPython.terminal.debugger:TerminalPdb
--trace Immediately break when running each test.
--capture=method per-test capturing method: one of fd|sys|no|tee-sys.
-s shortcut for --capture=no.
--runxfail report the results of xfail tests as if they were not
marked
--lf, --last-failed rerun only the tests that failed at the last run (or all
if none failed)
--ff, --failed-first run all tests but run the last failures first. This may
re-order tests and thus lead to repeated fixture
setup/teardown
--nf, --new-first run tests from new files first, then the rest of the
tests sorted by file mtime
--cache-show=[CACHESHOW]
show cache contents, don't perform collection or tests.
Optional argument: glob (default: '*').
--cache-clear remove all cache contents at start of test run.
--lfnf={all,none}, --last-failed-no-failures={all,none}
which tests to run with no previously (known) failures.
--sw, --stepwise exit on test failure and continue from last failing test
next time
--stepwise-skip ignore the first failing test but stop on the next
failing test
reporting:
--durations=N show N slowest setup/test durations (N=0 for all).
-v, --verbose increase verbosity.
-q, --quiet decrease verbosity.
--verbosity=VERBOSE set verbosity. Default is 0.
-r chars show extra test summary info as specified by chars:
(f)ailed, (E)rror, (s)kipped, (x)failed, (X)passed,
(p)assed, (P)assed with output, (a)ll except passed
(p/P), or (A)ll. (w)arnings are enabled by default (see
--disable-warnings), 'N' can be used to reset the list.
(default: 'fE').
--disable-warnings, --disable-pytest-warnings
disable warnings summary
-l, --showlocals show locals in tracebacks (disabled by default).
--tb=style traceback print mode (auto/long/short/line/native/no).
--show-capture={no,stdout,stderr,log,all}
Controls how captured stdout/stderr/log is shown on
failed tests. Default is 'all'.
--full-trace don't cut any tracebacks (default is to cut).
--color=color color terminal output (yes/no/auto).
--pastebin=mode send failed|all info to bpaste.net pastebin service.
--junit-xml=path create junit-xml style report file at given path.
--junit-prefix=str prepend prefix to classnames in junit-xml output
--result-log=path DEPRECATED path for machine-readable result log.
collection:
--collect-only, --co only collect tests, don't execute them.
--pyargs try to interpret all arguments as python packages.
--ignore=path ignore path during collection (multi-allowed).
--ignore-glob=path ignore path pattern during collection (multi-allowed).
--deselect=nodeid_prefix
deselect item (via node id prefix) during collection
(multi-allowed).
--confcutdir=dir only load conftest.py's relative to specified dir.
--noconftest Don't load any conftest.py files.
--keep-duplicates Keep duplicate tests.
--collect-in-virtualenv
Don't ignore tests in a local virtualenv directory
--doctest-modules run doctests in all .py modules
--doctest-report={none,cdiff,ndiff,udiff,only_first_failure}
choose another output format for diffs on doctest
failure
--doctest-glob=pat doctests file matching pattern, default: test*.txt
--doctest-ignore-import-errors
ignore doctest ImportErrors
--doctest-continue-on-failure
for a given doctest, continue to run after the first
failure
test session debugging and configuration:
--basetemp=dir base temporary directory for this test run.(warning:
this directory is removed if it exists)
-V, --version display pytest version and information about plugins.
-h, --help show help message and configuration info
-p name early-load given plugin module name or entry point
(multi-allowed). To avoid loading of plugins, use the
`no:` prefix, e.g. `no:doctest`.
--trace-config trace considerations of conftest.py files.
--debug store internal tracing debug information in
'pytestdebug.log'.
-o OVERRIDE_INI, --override-ini=OVERRIDE_INI
override ini option with "option=value" style, e.g. `-o
xfail_strict=True -o cache_dir=cache`.
--assert=MODE Control assertion debugging tools. 'plain' performs no
assertion debugging. 'rewrite' (the default) rewrites
assert statements in test modules on import to provide
assert expression information.
--setup-only only setup fixtures, do not execute tests.
--setup-show show setup of fixtures while executing tests.
--setup-plan show what fixtures and tests would be executed but don't
execute anything.
pytest-warnings:
-W PYTHONWARNINGS, --pythonwarnings=PYTHONWARNINGS
set which warnings to report, see -W option of python
itself.
logging:
--no-print-logs disable printing caught logs on failed tests.
--log-level=LEVEL level of messages to catch/display. Not set by default,
so it depends on the root/parent log handler's effective
level, where it is "WARNING" by default.
--log-format=LOG_FORMAT
log format as used by the logging module.
--log-date-format=LOG_DATE_FORMAT
log date format as used by the logging module.
--log-cli-level=LOG_CLI_LEVEL
cli logging level.
--log-cli-format=LOG_CLI_FORMAT
log format as used by the logging module.
--log-cli-date-format=LOG_CLI_DATE_FORMAT
log date format as used by the logging module.
--log-file=LOG_FILE path to a file when logging will be written to.
--log-file-level=LOG_FILE_LEVEL
log file logging level.
--log-file-format=LOG_FILE_FORMAT
log format as used by the logging module.
--log-file-date-format=LOG_FILE_DATE_FORMAT
log date format as used by the logging module.
--log-auto-indent=LOG_AUTO_INDENT
Auto-indent multiline messages passed to the logging
module. Accepts true|on, false|off or an integer.
re-run failing tests to eliminate flaky failures:
--reruns=RERUNS number of times to re-run failed tests. defaults to 0.
--reruns-delay=RERUNS_DELAY
add time (seconds) delay between reruns.
custom options:
--count=COUNT Number of times to repeat each test
--repeat-scope={function,class,module,session}
Scope for repeating tests
[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:
markers (linelist): markers for test functions
empty_parameter_set_mark (string):
default marker for empty parametersets
norecursedirs (args): directory patterns to avoid for recursion
testpaths (args): directories to search for tests when no files or
directories are given in the command line.
usefixtures (args): list of default fixtures to be used with this project
python_files (args): glob-style file patterns for Python test module
discovery
python_classes (args):
prefixes or glob names for Python test class discovery
python_functions (args):
prefixes or glob names for Python test function and
method discovery
disable_test_id_escaping_and_forfeit_all_rights_to_community_support (bool):
disable string escape non-ascii characters, might cause
unwanted side effects(use at your own risk)
console_output_style (string):
console output: "classic", or with additional progress
information ("progress" (percentage) | "count").
xfail_strict (bool): default for the strict parameter of xfail markers when
not given explicitly (default: False)
enable_assertion_pass_hook (bool):
Enables the pytest_assertion_pass hook.Make sure to
delete any previously generated pyc cache files.
junit_suite_name (string):
Test suite name for JUnit report
junit_logging (string):
Write captured log messages to JUnit report: one of
no|log|system-out|system-err|out-err|all
junit_log_passing_tests (bool):
Capture log information for passing tests to JUnit
report:
junit_duration_report (string):
Duration time to report: one of total|call
junit_family (string):
Emit XML for schema: one of legacy|xunit1|xunit2
doctest_optionflags (args):
option flags for doctests
doctest_encoding (string):
encoding used for doctest files
cache_dir (string): cache directory path.
filterwarnings (linelist):
Each line specifies a pattern for
warnings.filterwarnings. Processed after
-W/--pythonwarnings.
log_print (bool): default value for --no-print-logs
log_level (string): default value for --log-level
log_format (string): default value for --log-format
log_date_format (string):
default value for --log-date-format
log_cli (bool): enable log display during test run (also known as "live
logging").
log_cli_level (string):
default value for --log-cli-level
log_cli_format (string):
default value for --log-cli-format
log_cli_date_format (string):
default value for --log-cli-date-format
log_file (string): default value for --log-file
log_file_level (string):
default value for --log-file-level
log_file_format (string):
default value for --log-file-format
log_file_date_format (string):
default value for --log-file-date-format
log_auto_indent (string):
default value for --log-auto-indent
faulthandler_timeout (string):
Dump the traceback of all threads if a test takes more
than TIMEOUT seconds to finish. Not available on
Windows.
addopts (args): extra command line options
minversion (string): minimally required pytest version
environment variables:
PYTEST_ADDOPTS extra command line options
PYTEST_PLUGINS comma-separated plugins to load during startup
PYTEST_DISABLE_PLUGIN_AUTOLOAD set to disable plugin auto-loading
PYTEST_DEBUG set to enable debug tracing of pytest's internals
to see available markers type: pytest --markers
to see available fixtures type: pytest --fixtures
(shown according to specified file_or_dir or current dir if not specified; fixtures with leading '_' are only shown with the '-v' option
pytest.ini常用配置
marks
- 作用:测试用例中添加了 @pytest.mark.webtest 装饰器,如果不添加marks选项的话,就会报warnings
- 格式:list列表类型
- 写法:
[pytest]
markers =
weibo: this is weibo page
toutiao: toutiao
xinlang: xinlang
xfail_strict
- 作用:设置xfail_strict = True可以让那些标记为@pytest.mark.xfail但实际通过显示XPASS的测试用例被报告为失败
- 格式:True 、False(默认),1、0
- 写法:
[pytest]
# mark标记说明
markers =
weibo: this is weibo page
toutiao: toutiao
xinlang: xinlang
xfail_strict = True
addopts
- 作用:addopts参数可以更改默认命令行选项,这个当我们在cmd输入一堆指令去执行用例的时候,就可以用该参数代替了,省去重复性的敲命令工作
- 比如:想测试完生成报告,失败重跑两次,一共运行两次,通过分布式去测试,如果在cmd中写的话,命令会很长
pytest -v --rerun=2 --count=2 --html=report.html --self-contained-html -n=auto
[pytest]
# mark
markers =
weibo: this is weibo page
toutiao: toutiao
xinlang: xinlang
xfail_strict = True
# 命令行参数
addopts = -v --reruns=1 --count=2 --html=reports.html --self-contained-html -n=auto
加了addopts之后,我们在cmd中只需要敲pytest就可以生效了!!
log_cli
-
作用:控制台实时输出日志
-
格式:log_cli=True 或False(默认),或者log_cli=1 或 0
log_cli=0的运行结果
log_cli=1的运行结果
很明显,加了log_cli=1之后,可以清晰看到哪个package下的哪个module下的哪个测试用例是否passed还是failed;
所以平时测试代码是否有问题的情况下推荐加!!!但如果拿去批量跑测试用例的话不建议加,谁知道会不会影响运行性能呢?
norecursedirs
- 作用:pytest 收集测试用例时,会递归遍历所有子目录,包括某些你明知道没必要遍历的目录,遇到这种情况,可以使用 norecursedirs 参数简化 pytest 的搜索工作【还是挺有用的!!!】
- 默认设置: norecursedirs = .* build dist CVS _darcs {arch} *.egg
- 正确写法:多个路径用空格隔开
[pytest]
norecursedirs = .* build dist CVS _darcs {arch} *.egg venv src resources log report util
更改测试用例收集规则
pytest默认的测试用例收集规则
- 文件名以 test_*.py 文件和 *_test.py
- 以 test_ 开头的函数
- 以 Test 开头的类,不能包含 _init_ 方法
- 以 test_ 开头的类里面的方法
我们是可以修改或者添加这个用例收集规则的;当然啦,是建议在原有的规则上添加的,如下配置
[pytest]
python_files = test_* *_test test*
python_classes = Test* test*
python_functions = test_* test*
pytest高频插件
失败重跑pytest-rerunfailures
环境配置
环境要求:
- Python 3.5, 最高 3.8, or PyPy3
- pytest 5.0或更高版本
安装插件
#!pip3 install pytest-rerunfailures -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
Collecting pytest-rerunfailures
Downloading http://pypi.doubanio.com/packages/25/91/a0d1ff828e6da1915e4972d76ea2b5f9a1b520f078b4197ef93eb8427b65/pytest_rerunfailures-9.0-py3-none-any.whl
Collecting pytest>=5.0 (from pytest-rerunfailures)
Downloading http://pypi.doubanio.com/packages/c7/e2/c19c667f42f72716a7d03e8dd4d6f63f47d39feadd44cc1ee7ca3089862c/pytest-5.4.1-py3-none-any.whl (246kB)
[K 100% |████████████████████████████████| 256kB 23.5MB/s ta 0:00:01
[?25hCollecting setuptools>=40.0 (from pytest-rerunfailures)
Downloading http://pypi.doubanio.com/packages/a0/df/635cdb901ee4a8a42ec68e480c49f85f4c59e8816effbf57d9e6ee8b3588/setuptools-46.1.3-py3-none-any.whl (582kB)
[K 100% |████████████████████████████████| 583kB 18.0MB/s ta 0:00:01
[?25hCollecting attrs>=17.4.0 (from pytest>=5.0->pytest-rerunfailures)
Downloading http://pypi.doubanio.com/packages/a2/db/4313ab3be961f7a763066401fb77f7748373b6094076ae2bda2806988af6/attrs-19.3.0-py2.py3-none-any.whl
Collecting pluggy<1.0,>=0.12 (from pytest>=5.0->pytest-rerunfailures)
Downloading http://pypi.doubanio.com/packages/a0/28/85c7aa31b80d150b772fbe4a229487bc6644da9ccb7e427dd8cc60cb8a62/pluggy-0.13.1-py2.py3-none-any.whl
Collecting packaging (from pytest>=5.0->pytest-rerunfailures)
Downloading http://pypi.doubanio.com/packages/62/0a/34641d2bf5c917c96db0ded85ae4da25b6cd922d6b794648d4e7e07c88e5/packaging-20.3-py2.py3-none-any.whl
Collecting py>=1.5.0 (from pytest>=5.0->pytest-rerunfailures)
Downloading http://pypi.doubanio.com/packages/99/8d/21e1767c009211a62a8e3067280bfce76e89c9f876180308515942304d2d/py-1.8.1-py2.py3-none-any.whl (83kB)
[K 100% |████████████████████████████████| 92kB 48.5MB/s ta 0:00:01
[?25hCollecting more-itertools>=4.0.0 (from pytest>=5.0->pytest-rerunfailures)
Downloading http://pypi.doubanio.com/packages/72/96/4297306cc270eef1e3461da034a3bebe7c84eff052326b130824e98fc3fb/more_itertools-8.2.0-py3-none-any.whl (43kB)
[K 100% |████████████████████████████████| 51kB 46.9MB/s ta 0:00:01
[?25hCollecting wcwidth (from pytest>=5.0->pytest-rerunfailures)
Downloading http://pypi.doubanio.com/packages/f6/d5/1ecdac957e3ea12c1b319fcdee8b6917ffaff8b4644d673c4d72d2f20b49/wcwidth-0.1.9-py2.py3-none-any.whl
Collecting importlib-metadata>=0.12; python_version < "3.8" (from pytest>=5.0->pytest-rerunfailures)
Downloading http://pypi.doubanio.com/packages/ad/e4/891bfcaf868ccabc619942f27940c77a8a4b45fd8367098955bb7e152fb1/importlib_metadata-1.6.0-py2.py3-none-any.whl
Collecting pyparsing>=2.0.2 (from packaging->pytest>=5.0->pytest-rerunfailures)
Downloading http://pypi.doubanio.com/packages/8a/bb/488841f56197b13700afd5658fc279a2025a39e22449b7cf29864669b15d/pyparsing-2.4.7-py2.py3-none-any.whl (67kB)
[K 100% |████████████████████████████████| 71kB 49.6MB/s ta 0:00:01
[?25hCollecting six (from packaging->pytest>=5.0->pytest-rerunfailures)
Downloading http://pypi.doubanio.com/packages/65/eb/1f97cb97bfc2390a276969c6fae16075da282f5058082d4cb10c6c5c1dba/six-1.14.0-py2.py3-none-any.whl
Collecting zipp>=0.5 (from importlib-metadata>=0.12; python_version < "3.8"->pytest>=5.0->pytest-rerunfailures)
Downloading http://pypi.doubanio.com/packages/b2/34/bfcb43cc0ba81f527bc4f40ef41ba2ff4080e047acb0586b56b3d017ace4/zipp-3.1.0-py3-none-any.whl
Installing collected packages: attrs, zipp, importlib-metadata, pluggy, pyparsing, six, packaging, py, more-itertools, wcwidth, pytest, setuptools, pytest-rerunfailures
Successfully installed attrs-19.3.0 importlib-metadata-1.6.0 more-itertools-8.2.0 packaging-20.3 pluggy-0.13.1 py-1.8.1 pyparsing-2.4.7 pytest-5.4.1 pytest-rerunfailures-9.0 setuptools-46.1.3 six-1.14.0 wcwidth-0.1.9 zipp-3.1.0
配置参数
命令行参数:--reruns n(重新运行次数),--reruns-delay m(等待运行秒数)
装饰器参数:reruns=n(重新运行次数),reruns_delay=m(等待运行秒数)
重新运行所有失败用例
要重新运行所有测试失败,使用 --reruns 命令行选项,并指定要运行测试的最大次数:pytest --rerun 5 -s
运行失败的fixture或setup_class也将重新执行
要在两次重试之间增加延迟时间,使用 --reruns-delay 命令行选项,指定下次测试重新开始之前等待的秒数:pytest --reruns 5 --reruns-delay 10 -s
重新运行指定的测试用例
要将单个测试用例添加flaky装饰器 @pytest.mark.flaky(reruns=5) ,并在测试失败时自动重新运行,需要指定最大重新运行的次数
"""重新运行指定的测试用例"""
import pytest
import random
@pytest.mark.flasky(reruns=5, reruns_delay=2)
def test_example():
assert random.choice([True, False, False])
!pytest -v -s plug/pytest_rerunfailures.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /home/ubuntu/MySpace/Python/pytest
plugins: rerunfailures-9.0
collected 0 items / 1 error [0m
==================================== ERRORS ====================================
[31m[1m________________ ERROR collecting plug/pytest_rerunfailures.py _________________[0m
[31mimport file mismatch:
imported module 'pytest_rerunfailures' has this __file__ attribute:
/home/ubuntu/.local/lib/python3.6/site-packages/pytest_rerunfailures.py
which is not the same as the test file we want to collect:
/home/ubuntu/MySpace/Python/pytest/plug/pytest_rerunfailures.py
HINT: remove __pycache__ / .pyc files and/or use a unique basename for your test file modules[0m
=========================== short test summary info ============================
ERROR plug/pytest_rerunfailures.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
[31m=============================== [31m[1m1 error[0m[31m in 0.09s[0m[31m ===============================[0m
注意事项
- 如果指定了用例的重新运行次数,则在命令行添加--reruns对这些用例是不会生效的
- 不可以和fixture装饰器一起使用: @pytest.fixture()
- 该插件与pytest-xdist的 --looponfail 标志不兼容
- 该插件与核心--pdb标志不兼容
重复执行pytest-repeat
- 平常在做功能测试的时候,经常会遇到某个模块不稳定,偶然会出现一些bug,对于这种问题我们会针对此用例反复执行多次,最终复现出问题来
- 自动化运行用例时候,也会出现偶然的bug,可以针对单个用例,或者针对某个模块的用例重复执行多次
环境配置
环境要求:
- Python 2.7、3.4+或PyPy
- py.test 2.8或更高版本
插件安装:
# !pip3 install pytest-repeat -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
Collecting pytest-repeat
Downloading http://pypi.doubanio.com/packages/2e/de/c1d69002db74a99b3df0463e95066c03d82d9d2a53be738c140207134e0f/pytest_repeat-0.8.0-py2.py3-none-any.whl
Collecting pytest>=3.6 (from pytest-repeat)
Downloading http://pypi.doubanio.com/packages/c7/e2/c19c667f42f72716a7d03e8dd4d6f63f47d39feadd44cc1ee7ca3089862c/pytest-5.4.1-py3-none-any.whl (246kB)
[K 100% |████████████████████████████████| 256kB 3.8MB/s ta 0:00:011
[?25hCollecting more-itertools>=4.0.0 (from pytest>=3.6->pytest-repeat)
Downloading http://pypi.doubanio.com/packages/72/96/4297306cc270eef1e3461da034a3bebe7c84eff052326b130824e98fc3fb/more_itertools-8.2.0-py3-none-any.whl (43kB)
[K 100% |████████████████████████████████| 51kB 1.8MB/s ta 0:00:011
[?25hCollecting pluggy<1.0,>=0.12 (from pytest>=3.6->pytest-repeat)
Downloading http://pypi.doubanio.com/packages/a0/28/85c7aa31b80d150b772fbe4a229487bc6644da9ccb7e427dd8cc60cb8a62/pluggy-0.13.1-py2.py3-none-any.whl
Collecting attrs>=17.4.0 (from pytest>=3.6->pytest-repeat)
Downloading http://pypi.doubanio.com/packages/a2/db/4313ab3be961f7a763066401fb77f7748373b6094076ae2bda2806988af6/attrs-19.3.0-py2.py3-none-any.whl
Collecting importlib-metadata>=0.12; python_version < "3.8" (from pytest>=3.6->pytest-repeat)
Downloading http://pypi.doubanio.com/packages/ad/e4/891bfcaf868ccabc619942f27940c77a8a4b45fd8367098955bb7e152fb1/importlib_metadata-1.6.0-py2.py3-none-any.whl
Collecting py>=1.5.0 (from pytest>=3.6->pytest-repeat)
Downloading http://pypi.doubanio.com/packages/99/8d/21e1767c009211a62a8e3067280bfce76e89c9f876180308515942304d2d/py-1.8.1-py2.py3-none-any.whl (83kB)
[K 100% |████████████████████████████████| 92kB 843kB/s ta 0:00:011
[?25hCollecting wcwidth (from pytest>=3.6->pytest-repeat)
Downloading http://pypi.doubanio.com/packages/f6/d5/1ecdac957e3ea12c1b319fcdee8b6917ffaff8b4644d673c4d72d2f20b49/wcwidth-0.1.9-py2.py3-none-any.whl
Collecting packaging (from pytest>=3.6->pytest-repeat)
Downloading http://pypi.doubanio.com/packages/62/0a/34641d2bf5c917c96db0ded85ae4da25b6cd922d6b794648d4e7e07c88e5/packaging-20.3-py2.py3-none-any.whl
Collecting zipp>=0.5 (from importlib-metadata>=0.12; python_version < "3.8"->pytest>=3.6->pytest-repeat)
Downloading http://pypi.doubanio.com/packages/b2/34/bfcb43cc0ba81f527bc4f40ef41ba2ff4080e047acb0586b56b3d017ace4/zipp-3.1.0-py3-none-any.whl
Collecting six (from packaging->pytest>=3.6->pytest-repeat)
Downloading http://pypi.doubanio.com/packages/65/eb/1f97cb97bfc2390a276969c6fae16075da282f5058082d4cb10c6c5c1dba/six-1.14.0-py2.py3-none-any.whl
Collecting pyparsing>=2.0.2 (from packaging->pytest>=3.6->pytest-repeat)
Downloading http://pypi.doubanio.com/packages/8a/bb/488841f56197b13700afd5658fc279a2025a39e22449b7cf29864669b15d/pyparsing-2.4.7-py2.py3-none-any.whl (67kB)
[K 100% |████████████████████████████████| 71kB 40.3MB/s ta 0:00:01
[?25hInstalling collected packages: more-itertools, zipp, importlib-metadata, pluggy, attrs, py, wcwidth, six, pyparsing, packaging, pytest, pytest-repeat
Successfully installed attrs-19.3.0 importlib-metadata-1.6.0 more-itertools-8.2.0 packaging-20.3 pluggy-0.13.1 py-1.8.1 pyparsing-2.4.7 pytest-5.4.1 pytest-repeat-0.8.0 six-1.14.0 wcwidth-0.1.9 zipp-3.1.0
重复测试直到失败
- 如果需要验证偶现问题,可以一次又一次地运行相同的测试直到失败,这个插件将很有用
- 可以将pytest的 -x 选项与pytest-repeat结合使用,以强制测试运行程序在第一次失败时停止
import pytest
import random
def test_flag():
flag = random.choice([True, False])
print(flag)
assert flag
!pytest -s --count 5 -x plug/test_pytest_repeat.py
[31mERROR: /home/ubuntu/MySpace/Python/pytest/plug/pytest.ini:1: no section header defined
[0m
ptytest.mark.repeat(count)
如果要在代码中将某些测试用例标记为执行重复多次,可以使用 @pytest.mark.repeat(count)
@pytest.mark.repeat(5)
def test_repeat():
print("测试用例执行")
!pytest plug/test_pytest_repeat.py::test_repeat
[31mERROR: /home/ubuntu/MySpace/Python/pytest/plug/pytest.ini:1: no section header defined
[0m
--repeat-scope
作用:可以覆盖默认的测试用例执行顺序,类似fixture的scope参数
- function:默认,范围针对每个用例重复执行,再执行下一个用例
- class:以class为用例集合单位,重复执行class里面的用例,再执行下一个
- module:以模块为单位,重复执行模块里面的用例,再执行下一个
- session:重复整个测试会话,即所有测试用例的执行一次,然后再执行第二次
!cat plug/test_pytest_repeat_scope.py
import pytest
class Test_repeat:
def test_repeat3(self):
print("测试用例执行333")
class Test_repeat2:
def test_repeat3(self):
print("测试用例执行444")
def test_repeat1():
print("测试用例执行111")
def test_repeat2():
print("测试用例执行222")
class Test_repeat:
def test_repeat3(self):
print("测试用例执行333")
!pytest -s --count=2 --repeat-scope=class plug/test_pytest_repeat_scope.py
[31mERROR: /home/ubuntu/MySpace/Python/pytest/plug/pytest.ini:1: no section header defined
[0m
!pytest -s --count=2 --repeat-scope=module plug/test_pytest_repeat_scope.py
[31mERROR: /home/ubuntu/MySpace/Python/pytest/plug/pytest.ini:1: no section header defined
[0m
注意事项
pytest-repeat不能与unittest.TestCase测试类一起使用。无论--count设置多少,这些测试始终仅运行一次,并显示警告
多重校验pytest-assume
pytest中可以用python的assert断言,也可以写多个断言,但一个失败,后面的断言将不再执行
而 pytest-assume即使中间的断言失败了,还是会执行后面的断言:
- 即使断言失败,后面的断言还是会继续执行
- 有助于我们分析和查看到底一共有哪些断言是失败的
- 最后的代码也还会正常执行,比直接用assert更高效
环境配置
# !pip3 install pytest-assume -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
Collecting pytest-assume
Downloading http://pypi.doubanio.com/packages/9a/a7/bd0f0289c2978082296936c3899c77b3e738c89fa56ecbaaafd826ec2f52/pytest_assume-2.2.1-py3-none-any.whl
Collecting pytest>=2.7 (from pytest-assume)
Downloading http://pypi.doubanio.com/packages/c7/e2/c19c667f42f72716a7d03e8dd4d6f63f47d39feadd44cc1ee7ca3089862c/pytest-5.4.1-py3-none-any.whl (246kB)
[K 100% |████████████████████████████████| 256kB 56.4MB/s ta 0:00:01
[?25hCollecting more-itertools>=4.0.0 (from pytest>=2.7->pytest-assume)
Downloading http://pypi.doubanio.com/packages/72/96/4297306cc270eef1e3461da034a3bebe7c84eff052326b130824e98fc3fb/more_itertools-8.2.0-py3-none-any.whl (43kB)
[K 100% |████████████████████████████████| 51kB 43.4MB/s ta 0:00:01
[?25hCollecting wcwidth (from pytest>=2.7->pytest-assume)
Downloading http://pypi.doubanio.com/packages/f6/d5/1ecdac957e3ea12c1b319fcdee8b6917ffaff8b4644d673c4d72d2f20b49/wcwidth-0.1.9-py2.py3-none-any.whl
Collecting py>=1.5.0 (from pytest>=2.7->pytest-assume)
Downloading http://pypi.doubanio.com/packages/99/8d/21e1767c009211a62a8e3067280bfce76e89c9f876180308515942304d2d/py-1.8.1-py2.py3-none-any.whl (83kB)
[K 100% |████████████████████████████████| 92kB 48.0MB/s ta 0:00:01
[?25hCollecting importlib-metadata>=0.12; python_version < "3.8" (from pytest>=2.7->pytest-assume)
Downloading http://pypi.doubanio.com/packages/ad/e4/891bfcaf868ccabc619942f27940c77a8a4b45fd8367098955bb7e152fb1/importlib_metadata-1.6.0-py2.py3-none-any.whl
Collecting attrs>=17.4.0 (from pytest>=2.7->pytest-assume)
Downloading http://pypi.doubanio.com/packages/a2/db/4313ab3be961f7a763066401fb77f7748373b6094076ae2bda2806988af6/attrs-19.3.0-py2.py3-none-any.whl
Collecting packaging (from pytest>=2.7->pytest-assume)
Downloading http://pypi.doubanio.com/packages/62/0a/34641d2bf5c917c96db0ded85ae4da25b6cd922d6b794648d4e7e07c88e5/packaging-20.3-py2.py3-none-any.whl
Collecting pluggy<1.0,>=0.12 (from pytest>=2.7->pytest-assume)
Downloading http://pypi.doubanio.com/packages/a0/28/85c7aa31b80d150b772fbe4a229487bc6644da9ccb7e427dd8cc60cb8a62/pluggy-0.13.1-py2.py3-none-any.whl
Collecting zipp>=0.5 (from importlib-metadata>=0.12; python_version < "3.8"->pytest>=2.7->pytest-assume)
Downloading http://pypi.doubanio.com/packages/b2/34/bfcb43cc0ba81f527bc4f40ef41ba2ff4080e047acb0586b56b3d017ace4/zipp-3.1.0-py3-none-any.whl
Collecting pyparsing>=2.0.2 (from packaging->pytest>=2.7->pytest-assume)
Downloading http://pypi.doubanio.com/packages/8a/bb/488841f56197b13700afd5658fc279a2025a39e22449b7cf29864669b15d/pyparsing-2.4.7-py2.py3-none-any.whl (67kB)
[K 100% |████████████████████████████████| 71kB 35.7MB/s ta 0:00:01
[?25hCollecting six (from packaging->pytest>=2.7->pytest-assume)
Downloading http://pypi.doubanio.com/packages/65/eb/1f97cb97bfc2390a276969c6fae16075da282f5058082d4cb10c6c5c1dba/six-1.14.0-py2.py3-none-any.whl
Installing collected packages: more-itertools, wcwidth, py, zipp, importlib-metadata, attrs, pyparsing, six, packaging, pluggy, pytest, pytest-assume
Successfully installed attrs-19.3.0 importlib-metadata-1.6.0 more-itertools-8.2.0 packaging-20.3 pluggy-0.13.1 py-1.8.1 pyparsing-2.4.7 pytest-5.4.1 pytest-assume-2.2.1 six-1.14.0 wcwidth-0.1.9 zipp-3.1.0
assert 多重断言
def test_add1():
assert 1 + 4 == 5
assert 1 + 3 == 3
assert 2 + 5 == 7
assert 2 + 5 == 9
print("测试完成")
!pytest -v -s plug/more_assert.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /home/ubuntu/MySpace/Python/pytest
plugins: rerunfailures-9.0, assume-2.2.1, repeat-0.8.0
collected 1 item [0m
plug/more_assert.py::test_add1 [31mFAILED[0m
=================================== FAILURES ===================================
[31m[1m__________________________________ test_add1 ___________________________________[0m
[94mdef[39;49;00m [92mtest_add1[39;49;00m():
[94massert[39;49;00m [94m1[39;49;00m + [94m4[39;49;00m == [94m5[39;49;00m
> [94massert[39;49;00m [94m1[39;49;00m + [94m3[39;49;00m == [94m3[39;49;00m
[1m[31mE assert 4 == 3[0m
[1m[31mE +4[0m
[1m[31mE -3[0m
[1m[31mplug/more_assert.py[0m:3: AssertionError
=========================== short test summary info ============================
FAILED plug/more_assert.py::test_add1 - assert 4 == 3
[31m============================== [31m[1m1 failed[0m[31m in 0.11s[0m[31m ===============================[0m
pytest.assume多重断言
import pytest
def test_add2():
pytest.assume(1 + 4 == 5)
pytest.assume(1 + 3 == 3)
pytest.assume(2 + 5 == 7)
pytest.assume(2 + 5 == 9)
print("测试完成")
!pytest -v -s plug/more_pytest_assume.py
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /home/ubuntu/MySpace/Python/pytest
plugins: rerunfailures-9.0, assume-2.2.1, repeat-0.8.0
collected 1 item [0m
plug/more_pytest_assume.py::test_add2 测试完成
[31mFAILED[0m
=================================== FAILURES ===================================
[31m[1m__________________________________ test_add2 ___________________________________[0m
tp = <class 'pytest_assume.plugin.FailedAssumption'>, value = None, tb = None
[94mdef[39;49;00m [92mreraise[39;49;00m(tp, value, tb=[94mNone[39;49;00m):
[94mtry[39;49;00m:
[94mif[39;49;00m value [95mis[39;49;00m [94mNone[39;49;00m:
value = tp()
[94mif[39;49;00m value.__traceback__ [95mis[39;49;00m [95mnot[39;49;00m tb:
> [94mraise[39;49;00m value.with_traceback(tb)
[1m[31mE pytest_assume.plugin.FailedAssumption: [0m
[1m[31mE 2 Failed Assumptions:[0m
[1m[31mE [0m
[1m[31mE plug/more_pytest_assume.py:5: AssumptionFailure[0m
[1m[31mE >> pytest.assume(1 + 3 == 3)[0m
[1m[31mE AssertionError: assert False[0m
[1m[31mE [0m
[1m[31mE plug/more_pytest_assume.py:7: AssumptionFailure[0m
[1m[31mE >> pytest.assume(2 + 5 == 9)[0m
[1m[31mE AssertionError: assert False[0m
[1m[31m../../../.local/lib/python3.6/site-packages/six.py[0m:702: FailedAssumption
=========================== short test summary info ============================
FAILED plug/more_pytest_assume.py::test_add2 - pytest_assume.plugin.FailedAss...
[31m============================== [31m[1m1 failed[0m[31m in 0.14s[0m[31m ===============================[0m
分布式测试pytest-xdist
应用场景
- 平常我们功能测试用例非常多时,比如有1千条用例,假设每个用例执行需要1分钟,如果单个测试人员执行需要1000分钟才能跑完
- 当项目非常紧急时,会需要协调多个测试资源来把任务分成两部分,于是执行时间缩短一半,如果有10个小伙伴,那么执行时间就会变成十分之一,大大节省了测试时间
- 为了节省项目测试时间,10个测试同时并行测试,这就是一种分布式场景
- 同样道理,当我们自动化测试用例排常多的时候, 一条条按顺序执行会非常慢,pytest-xdist的出现就是为了让自动化测试用例可以分布式执行,从而节省自动化测试时间
- pytest-xdist是属于进程级别的并发
分布式执行用例设计原则
- 用例之间是独立的,用例之间没有依赖关系,用例可以完全独立运行【独立运行】
- 用例执行没有顺序,随机顺序都能正常执行【随机执行】
- 每个用例都能重复运行,运行结果不会影响其他用例【不影响其他用例】
环境配置
# !pip3 install pytest-xdist -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
Collecting pytest-xdist
Downloading http://pypi.doubanio.com/packages/7c/8c/7f93c1d82f25a69a1c6e68189b9cf5ddce08dcaefdbd913d328b0234e13b/pytest_xdist-1.31.0-py2.py3-none-any.whl
Collecting pytest>=4.4.0 (from pytest-xdist)
Downloading http://pypi.doubanio.com/packages/c7/e2/c19c667f42f72716a7d03e8dd4d6f63f47d39feadd44cc1ee7ca3089862c/pytest-5.4.1-py3-none-any.whl (246kB)
[K 100% |████████████████████████████████| 256kB 25.6MB/s ta 0:00:01
[?25hCollecting execnet>=1.1 (from pytest-xdist)
Downloading http://pypi.doubanio.com/packages/d3/2e/c63af07fa471e0a02d05793c7a56a9f7d274a8489442a5dc4fb3b2b3c705/execnet-1.7.1-py2.py3-none-any.whl
Collecting pytest-forked (from pytest-xdist)
Downloading http://pypi.doubanio.com/packages/03/1e/81235e1fcfed57a4e679d34794d60c01a1e9a29ef5b9844d797716111d80/pytest_forked-1.1.3-py2.py3-none-any.whl
Collecting six (from pytest-xdist)
Downloading http://pypi.doubanio.com/packages/65/eb/1f97cb97bfc2390a276969c6fae16075da282f5058082d4cb10c6c5c1dba/six-1.14.0-py2.py3-none-any.whl
Collecting more-itertools>=4.0.0 (from pytest>=4.4.0->pytest-xdist)
Downloading http://pypi.doubanio.com/packages/72/96/4297306cc270eef1e3461da034a3bebe7c84eff052326b130824e98fc3fb/more_itertools-8.2.0-py3-none-any.whl (43kB)
[K 100% |████████████████████████████████| 51kB 49.0MB/s ta 0:00:01
[?25hCollecting pluggy<1.0,>=0.12 (from pytest>=4.4.0->pytest-xdist)
Downloading http://pypi.doubanio.com/packages/a0/28/85c7aa31b80d150b772fbe4a229487bc6644da9ccb7e427dd8cc60cb8a62/pluggy-0.13.1-py2.py3-none-any.whl
Collecting wcwidth (from pytest>=4.4.0->pytest-xdist)
Downloading http://pypi.doubanio.com/packages/f6/d5/1ecdac957e3ea12c1b319fcdee8b6917ffaff8b4644d673c4d72d2f20b49/wcwidth-0.1.9-py2.py3-none-any.whl
Collecting importlib-metadata>=0.12; python_version < "3.8" (from pytest>=4.4.0->pytest-xdist)
Downloading http://pypi.doubanio.com/packages/ad/e4/891bfcaf868ccabc619942f27940c77a8a4b45fd8367098955bb7e152fb1/importlib_metadata-1.6.0-py2.py3-none-any.whl
Collecting packaging (from pytest>=4.4.0->pytest-xdist)
Downloading http://pypi.doubanio.com/packages/62/0a/34641d2bf5c917c96db0ded85ae4da25b6cd922d6b794648d4e7e07c88e5/packaging-20.3-py2.py3-none-any.whl
Collecting py>=1.5.0 (from pytest>=4.4.0->pytest-xdist)
Downloading http://pypi.doubanio.com/packages/99/8d/21e1767c009211a62a8e3067280bfce76e89c9f876180308515942304d2d/py-1.8.1-py2.py3-none-any.whl (83kB)
[K 100% |████████████████████████████████| 92kB 15.7MB/s ta 0:00:01 73% |███████████████████████▌ | 61kB 30.9MB/s eta 0:00:01
[?25hCollecting attrs>=17.4.0 (from pytest>=4.4.0->pytest-xdist)
Downloading http://pypi.doubanio.com/packages/a2/db/4313ab3be961f7a763066401fb77f7748373b6094076ae2bda2806988af6/attrs-19.3.0-py2.py3-none-any.whl
Collecting apipkg>=1.4 (from execnet>=1.1->pytest-xdist)
Downloading http://pypi.doubanio.com/packages/67/08/4815a09603fc800209431bec5b8bd2acf2f95abdfb558a44a42507fb94da/apipkg-1.5-py2.py3-none-any.whl
Collecting zipp>=0.5 (from importlib-metadata>=0.12; python_version < "3.8"->pytest>=4.4.0->pytest-xdist)
Downloading http://pypi.doubanio.com/packages/b2/34/bfcb43cc0ba81f527bc4f40ef41ba2ff4080e047acb0586b56b3d017ace4/zipp-3.1.0-py3-none-any.whl
Collecting pyparsing>=2.0.2 (from packaging->pytest>=4.4.0->pytest-xdist)
Downloading http://pypi.doubanio.com/packages/8a/bb/488841f56197b13700afd5658fc279a2025a39e22449b7cf29864669b15d/pyparsing-2.4.7-py2.py3-none-any.whl (67kB)
[K 100% |████████████████████████████████| 71kB 33.0MB/s ta 0:00:01
[?25hInstalling collected packages: more-itertools, zipp, importlib-metadata, pluggy, wcwidth, pyparsing, six, packaging, py, attrs, pytest, apipkg, execnet, pytest-forked, pytest-xdist
Successfully installed apipkg-1.5 attrs-19.3.0 execnet-1.7.1 importlib-metadata-1.6.0 more-itertools-8.2.0 packaging-20.3 pluggy-0.13.1 py-1.8.1 pyparsing-2.4.7 pytest-5.4.1 pytest-forked-1.1.3 pytest-xdist-1.31.0 six-1.14.0 wcwidth-0.1.9 zipp-3.1.0
pytest-xdist分布式测试原理
xdist的分布式类似于一主多从的结构,master机负责下发命令,控制slave机;slave机根据master机的命令执行特定测试任务
在xdist中,主是master,从是workers
大致原理:
- xdist会产生一个或多个workers,workers都通过master来控制
- 每个worker负责执行完整的测试用例集,然后按照master的要求运行测试,而master机不执行测试任务
pytest-xdist分布式测试流程
创建worker
- master会在总测试会话(test session)开始前产生一个或多个worker
- master和worker之间是通过execnet和网关来通信的
- 实际编译执行测试代码的worker可能是本地机器也可能是远程机器
收集测试项用例
- 每个worker类似一个迷你型的pytest执行器
- worker会执行一个完整的test collection过程【收集所有测试用例的过程】
- 然后把测试用例的ids返回给master
- master是不会执行任何测试用例集的
注意:所以为什么上面通过分布式测试的结果截图是没有输出用例的print内容,因为主机并不执行测试用例,pycharm相当于一个master
master 检查 workers 收集到的测试用例集
- master接收到所有worker收集的测试用例集之后,master会进行一些完整性检查,以确保所有worker都收集到一样的测试用例集(包括顺序)
- 如果检查通过,会将测试用例的ids列表转换成简单的索引列表,每个索引对应一个测试用例的在原来测试集中的位置
- 这个方案可行的原因是:所有的节点都保存着相同的测试用例集
- 并且使用这种方式可以节省带宽,因为master只需要告知workers需要执行的测试用例对应的索引,而不用告知完整的测试用例信息
测试用例分发
--dist-mode选项
-
each:master将完整的测试索引列表分发到每个worker
-
load:master将大约25%的测试用例以轮询的方式分发到各个worker,剩余的测试用例则会等待workers执行完测试用例以后再分发
注意:可以使用 pytest_xdist_make_scheduler 这个hook来实现自定义测试分发逻辑。
测试用例的执行
- workers 重写了 pytest_runtestloop :pytest的默认实现是循环执行所有在test session这个对象里面收集到的测试用例
- 但是在xdist里, workers实际上是等待master为其发送需要执行的测试用例
- 当worker收到测试任务, 就顺序执行 pytest_runtest_protocol
- 值得注意的一个细节是:workers 必须始终保持至少一个测试用例在的任务队列里, 以兼容 pytest_runtest_protocol(item, nextitem) hook的参数要求,为了将 nextitem传给hook
- worker会在执行最后一个测试项前等待master的更多指令
- 如果它收到了更多测试项, 那么就可以安全的执行 pytest_runtest_protocol , 因为这时nextitem参数已经可以确定
- 如果它收到一个 "shutdown"信号, 那么就将 nextitem 参数设为 None, 然后执行 pytest_runtest_protocol
测试用例再分发(--dist-mode=load)
- 当workers开始/结束执行时,会把测试结果返回给master,这样其他pytest hook比如: pytest_runtest_protocol 和 pytest_runtest_protocol 就可以正常执行
- master在worker执行完一个测试后,基于测试执行时长以及每个work剩余测试用例综合决定是否向这个worker发送更多的测试用例
测试结束
- 当master没有更多执行测试任务时,它会发送一个“shutdown”信号给所有worker
- 当worker将剩余测试用例执行完后退出进程
- master等待所有worker全部退出
- 然此时仍需要处理诸如 pytest_runtest_logreport 等事件
pytest-xdist通过独特的测试模式扩展pytest
- 测试运行并行化:如果有多个CPU或主机,则可以将它们用于组合的测试运行。 这样可以加快开发速度或使用远程计算机的特殊资源。
- --looponfail:在子进程中重复运行测试。 每次运行之后,pytest都会等到项目中的文件更改后再运行之前失败的测试。 重复此过程,直到所有测试通过,然后再次执行完整运行。
- 跨平台覆盖:您可以指定不同的Python解释程序或不同的平台,并在所有这些平台上并行运行测试。
pytest-xdist按照一定的顺序执行
pytest-xdist默认是无序执行的,可以通过 --dist 参数来控制顺序
--dist=loadscope
- 将按照同一个模块module下的函数和同一个测试类class下的方法来分组,然后将每个测试组发给可以执行的worker,确保同一个组的测试用例在同一个进程中执行
- 目前无法自定义分组,按类class分组优先于按模块module分组
--dist=loadfile
按照同一个文件名来分组,然后将每个测试组发给可以执行的worker,确保同一个组的测试用例在同一个进程中执行
如何让scope=session的fixture在test session中仅仅执行一次
pytest-xdist是让每个worker进程执行属于自己的测试用例集下的所有测试用例
这意味着在不同进程中,不同的测试用例可能会调用同一个scope范围级别较高(例如session)的fixture,该fixture则会被执行多次,这不符合scope=session的预期
解决方案:虽然pytest-xdist没有内置的支持来确保会话范围的夹具仅执行一次,但是可以通过使用锁定文件进行进程间通信来实现。
import pytest
from filelock import FileLock
@pytest.fixture(scope="session")
def login():
print("====登录功能,返回账号,token===")
with FileLock("session.lock"):
name = "testyy"
token = "npoi213bn4"
# web ui自动化
# 声明一个driver,再返回
# 接口自动化
# 发起一个登录请求,将token返回都可以这样写
yield name, token
print("====退出登录!!!====")
- 下面的示例只需要执行一次login(因为它是只需要执行一次来定义配置选项,等等)
- 当第一次请求这个fixture时,则会利用FileLock仅产生一次fixture数据
- 当其他进程再次请求这个fixture时,则会从文件中读取数据
实例
"""测试项目架构"""
!tree plug/pytest_xdist/
[01;34mplug/pytest_xdist/[00m
├── conftest.py
├── test_1.py
├── [01;34mtest_job[00m
│ ├── conftest.py
│ └── test_case1.py
├── [01;34mtest_toutiao[00m
│ └── test_case2.py
├── [01;34mtest_weibo[00m
│ ├── conftest.py
│ └── test_case3.py
└── untitled.txt
3 directories, 8 files
"""不使用分布式执行测试用例"""
!pytest -s plug/pytest_xdist/
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
plugins: xdist-1.31.0, rerunfailures-9.0, assume-2.2.1, forked-1.1.3, repeat-0.8.0
collected 30 items [0m
plug/pytest_xdist/test_1.py ====登录功能,返回账号,token===
[31mF[0m[31mF[0m[31mF[0m[31mF[0m[31mF[0m
plug/pytest_xdist/test_job/test_case1.py [31mE[0m[31mE[0m[31mE[0m[31mE[0m[31mE[0m[31mE[0m[31mE[0m[31mE[0m[31mE[0m[31mE[0m
plug/pytest_xdist/test_toutiao/test_case2.py ==没有__init__测试用例,我进入头条了== ('testyy', 'npoi213bn4')
[32m.[0m==没有__init__测试用例,我进入头条了== ('testyy', 'npoi213bn4')
[32m.[0m==没有__init__测试用例,我进入头条了== ('testyy', 'npoi213bn4')
[32m.[0m==没有__init__测试用例,我进入头条了== ('testyy', 'npoi213bn4')
[32m.[0m==没有__init__测试用例,我进入头条了== ('testyy', 'npoi213bn4')
[32m.[0m
plug/pytest_xdist/test_weibo/test_case3.py &&& 用户 testyy 返回微博首页 &&&
查看微博热搜 0
[32m.[0m&&& 用户 testyy 返回微博首页 &&&
查看微博热搜 1
[32m.[0m&&& 用户 testyy 返回微博首页 &&&
查看微博热搜 2
[32m.[0m&&& 用户 testyy 返回微博首页 &&&
查看微博热搜 3
[32m.[0m&&& 用户 testyy 返回微博首页 &&&
查看微博热搜 4
[32m.[0m&&& 用户 testyy 返回微博首页 &&&
查看微博范冰冰 0
[32m.[0m&&& 用户 testyy 返回微博首页 &&&
查看微博范冰冰 1
[32m.[0m&&& 用户 testyy 返回微博首页 &&&
查看微博范冰冰 2
[32m.[0m&&& 用户 testyy 返回微博首页 &&&
查看微博范冰冰 3
[32m.[0m&&& 用户 testyy 返回微博首页 &&&
查看微博范冰冰 4
[32m.[0m====退出登录!!!====
==================================== ERRORS ====================================
[31m[1m______________________ ERROR at setup of test_case2_01[0] ______________________[0m
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 6
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_01(open_51, n):
[31mE fixture 'open_51' not found[0m
[31m> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id[0m
[31m> use 'pytest --fixtures [testpath]' for help on them.[0m
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:6
[31m[1m______________________ ERROR at setup of test_case2_01[1] ______________________[0m
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 6
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_01(open_51, n):
[31mE fixture 'open_51' not found[0m
[31m> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id[0m
[31m> use 'pytest --fixtures [testpath]' for help on them.[0m
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:6
[31m[1m______________________ ERROR at setup of test_case2_01[2] ______________________[0m
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 6
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_01(open_51, n):
[31mE fixture 'open_51' not found[0m
[31m> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id[0m
[31m> use 'pytest --fixtures [testpath]' for help on them.[0m
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:6
[31m[1m______________________ ERROR at setup of test_case2_01[3] ______________________[0m
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 6
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_01(open_51, n):
[31mE fixture 'open_51' not found[0m
[31m> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id[0m
[31m> use 'pytest --fixtures [testpath]' for help on them.[0m
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:6
[31m[1m______________________ ERROR at setup of test_case2_01[4] ______________________[0m
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 6
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_01(open_51, n):
[31mE fixture 'open_51' not found[0m
[31m> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id[0m
[31m> use 'pytest --fixtures [testpath]' for help on them.[0m
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:6
[31m[1m______________________ ERROR at setup of test_case2_02[0] ______________________[0m
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 12
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_02(open_51, n):
[31mE fixture 'open_51' not found[0m
[31m> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id[0m
[31m> use 'pytest --fixtures [testpath]' for help on them.[0m
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:12
[31m[1m______________________ ERROR at setup of test_case2_02[1] ______________________[0m
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 12
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_02(open_51, n):
[31mE fixture 'open_51' not found[0m
[31m> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id[0m
[31m> use 'pytest --fixtures [testpath]' for help on them.[0m
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:12
[31m[1m______________________ ERROR at setup of test_case2_02[2] ______________________[0m
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 12
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_02(open_51, n):
[31mE fixture 'open_51' not found[0m
[31m> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id[0m
[31m> use 'pytest --fixtures [testpath]' for help on them.[0m
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:12
[31m[1m______________________ ERROR at setup of test_case2_02[3] ______________________[0m
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 12
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_02(open_51, n):
[31mE fixture 'open_51' not found[0m
[31m> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id[0m
[31m> use 'pytest --fixtures [testpath]' for help on them.[0m
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:12
[31m[1m______________________ ERROR at setup of test_case2_02[4] ______________________[0m
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 12
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_02(open_51, n):
[31mE fixture 'open_51' not found[0m
[31m> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id[0m
[31m> use 'pytest --fixtures [testpath]' for help on them.[0m
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:12
=================================== FAILURES ===================================
[31m[1m_______________________________ test_get_info[0] _______________________________[0m
login = ('testyy', 'npoi213bn4'), n = 0
[37m@pytest[39;49;00m.mark.parametrize([33m"[39;49;00m[33mn[39;49;00m[33m"[39;49;00m, [96mlist[39;49;00m([96mrange[39;49;00m([94m5[39;49;00m)))
[94mdef[39;49;00m [92mtest_get_info[39;49;00m(login, n):
> sleep([94m1[39;49;00m)
[1m[31mE NameError: name 'sleep' is not defined[0m
[1m[31mplug/pytest_xdist/test_1.py[0m:6: NameError
[31m[1m_______________________________ test_get_info[1] _______________________________[0m
login = ('testyy', 'npoi213bn4'), n = 1
[37m@pytest[39;49;00m.mark.parametrize([33m"[39;49;00m[33mn[39;49;00m[33m"[39;49;00m, [96mlist[39;49;00m([96mrange[39;49;00m([94m5[39;49;00m)))
[94mdef[39;49;00m [92mtest_get_info[39;49;00m(login, n):
> sleep([94m1[39;49;00m)
[1m[31mE NameError: name 'sleep' is not defined[0m
[1m[31mplug/pytest_xdist/test_1.py[0m:6: NameError
[31m[1m_______________________________ test_get_info[2] _______________________________[0m
login = ('testyy', 'npoi213bn4'), n = 2
[37m@pytest[39;49;00m.mark.parametrize([33m"[39;49;00m[33mn[39;49;00m[33m"[39;49;00m, [96mlist[39;49;00m([96mrange[39;49;00m([94m5[39;49;00m)))
[94mdef[39;49;00m [92mtest_get_info[39;49;00m(login, n):
> sleep([94m1[39;49;00m)
[1m[31mE NameError: name 'sleep' is not defined[0m
[1m[31mplug/pytest_xdist/test_1.py[0m:6: NameError
[31m[1m_______________________________ test_get_info[3] _______________________________[0m
login = ('testyy', 'npoi213bn4'), n = 3
[37m@pytest[39;49;00m.mark.parametrize([33m"[39;49;00m[33mn[39;49;00m[33m"[39;49;00m, [96mlist[39;49;00m([96mrange[39;49;00m([94m5[39;49;00m)))
[94mdef[39;49;00m [92mtest_get_info[39;49;00m(login, n):
> sleep([94m1[39;49;00m)
[1m[31mE NameError: name 'sleep' is not defined[0m
[1m[31mplug/pytest_xdist/test_1.py[0m:6: NameError
[31m[1m_______________________________ test_get_info[4] _______________________________[0m
login = ('testyy', 'npoi213bn4'), n = 4
[37m@pytest[39;49;00m.mark.parametrize([33m"[39;49;00m[33mn[39;49;00m[33m"[39;49;00m, [96mlist[39;49;00m([96mrange[39;49;00m([94m5[39;49;00m)))
[94mdef[39;49;00m [92mtest_get_info[39;49;00m(login, n):
> sleep([94m1[39;49;00m)
[1m[31mE NameError: name 'sleep' is not defined[0m
[1m[31mplug/pytest_xdist/test_1.py[0m:6: NameError
=========================== short test summary info ============================
FAILED plug/pytest_xdist/test_1.py::test_get_info[0] - NameError: name 'sleep...
FAILED plug/pytest_xdist/test_1.py::test_get_info[1] - NameError: name 'sleep...
FAILED plug/pytest_xdist/test_1.py::test_get_info[2] - NameError: name 'sleep...
FAILED plug/pytest_xdist/test_1.py::test_get_info[3] - NameError: name 'sleep...
FAILED plug/pytest_xdist/test_1.py::test_get_info[4] - NameError: name 'sleep...
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_01[0]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_01[1]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_01[2]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_01[3]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_01[4]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_02[0]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_02[1]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_02[2]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_02[3]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_02[4]
[31m=================== [31m[1m5 failed[0m, [32m15 passed[0m, [31m[1m10 errors[0m[31m in 15.27s[0m[31m ===================[0m
"""分布式用例执行"""
!pytest -s -n auto plug/pytest_xdist/
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
plugins: xdist-1.31.0, rerunfailures-9.0, assume-2.2.1, forked-1.1.3, repeat-0.8.0
gw0 [30][0mm[1m[1m
[31mF[0m[31mF[0m[31mF[0m[31mF[0m[31mF[0m[31mE[0m[31mE[0m[31mE[0m[31mE[0m[31mE[0m[31mE[0m[31mE[0m[31mE[0m[31mE[0m[31mE[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m
==================================== ERRORS ====================================
[31m[1m______________________ ERROR at setup of test_case2_01[0] ______________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 6
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_01(open_51, n):
E fixture 'open_51' not found
> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:6
[31m[1m______________________ ERROR at setup of test_case2_01[1] ______________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 6
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_01(open_51, n):
E fixture 'open_51' not found
> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:6
[31m[1m______________________ ERROR at setup of test_case2_01[2] ______________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 6
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_01(open_51, n):
E fixture 'open_51' not found
> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:6
[31m[1m______________________ ERROR at setup of test_case2_01[3] ______________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 6
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_01(open_51, n):
E fixture 'open_51' not found
> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:6
[31m[1m______________________ ERROR at setup of test_case2_01[4] ______________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 6
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_01(open_51, n):
E fixture 'open_51' not found
> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:6
[31m[1m______________________ ERROR at setup of test_case2_02[0] ______________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 12
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_02(open_51, n):
E fixture 'open_51' not found
> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:12
[31m[1m______________________ ERROR at setup of test_case2_02[1] ______________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 12
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_02(open_51, n):
E fixture 'open_51' not found
> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:12
[31m[1m______________________ ERROR at setup of test_case2_02[2] ______________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 12
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_02(open_51, n):
E fixture 'open_51' not found
> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:12
[31m[1m______________________ ERROR at setup of test_case2_02[3] ______________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 12
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_02(open_51, n):
E fixture 'open_51' not found
> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:12
[31m[1m______________________ ERROR at setup of test_case2_02[4] ______________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 12
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_02(open_51, n):
E fixture 'open_51' not found
> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:12
=================================== FAILURES ===================================
[31m[1m_______________________________ test_get_info[0] _______________________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
login = ('testyy', 'npoi213bn4'), n = 0
[37m@pytest[39;49;00m.mark.parametrize([33m"[39;49;00m[33mn[39;49;00m[33m"[39;49;00m, [96mlist[39;49;00m([96mrange[39;49;00m([94m5[39;49;00m)))
[94mdef[39;49;00m [92mtest_get_info[39;49;00m(login, n):
> sleep([94m1[39;49;00m)
[1m[31mE NameError: name 'sleep' is not defined[0m
[1m[31mplug/pytest_xdist/test_1.py[0m:6: NameError
[31m[1m_______________________________ test_get_info[1] _______________________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
login = ('testyy', 'npoi213bn4'), n = 1
[37m@pytest[39;49;00m.mark.parametrize([33m"[39;49;00m[33mn[39;49;00m[33m"[39;49;00m, [96mlist[39;49;00m([96mrange[39;49;00m([94m5[39;49;00m)))
[94mdef[39;49;00m [92mtest_get_info[39;49;00m(login, n):
> sleep([94m1[39;49;00m)
[1m[31mE NameError: name 'sleep' is not defined[0m
[1m[31mplug/pytest_xdist/test_1.py[0m:6: NameError
[31m[1m_______________________________ test_get_info[2] _______________________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
login = ('testyy', 'npoi213bn4'), n = 2
[37m@pytest[39;49;00m.mark.parametrize([33m"[39;49;00m[33mn[39;49;00m[33m"[39;49;00m, [96mlist[39;49;00m([96mrange[39;49;00m([94m5[39;49;00m)))
[94mdef[39;49;00m [92mtest_get_info[39;49;00m(login, n):
> sleep([94m1[39;49;00m)
[1m[31mE NameError: name 'sleep' is not defined[0m
[1m[31mplug/pytest_xdist/test_1.py[0m:6: NameError
[31m[1m_______________________________ test_get_info[3] _______________________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
login = ('testyy', 'npoi213bn4'), n = 3
[37m@pytest[39;49;00m.mark.parametrize([33m"[39;49;00m[33mn[39;49;00m[33m"[39;49;00m, [96mlist[39;49;00m([96mrange[39;49;00m([94m5[39;49;00m)))
[94mdef[39;49;00m [92mtest_get_info[39;49;00m(login, n):
> sleep([94m1[39;49;00m)
[1m[31mE NameError: name 'sleep' is not defined[0m
[1m[31mplug/pytest_xdist/test_1.py[0m:6: NameError
[31m[1m_______________________________ test_get_info[4] _______________________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
login = ('testyy', 'npoi213bn4'), n = 4
[37m@pytest[39;49;00m.mark.parametrize([33m"[39;49;00m[33mn[39;49;00m[33m"[39;49;00m, [96mlist[39;49;00m([96mrange[39;49;00m([94m5[39;49;00m)))
[94mdef[39;49;00m [92mtest_get_info[39;49;00m(login, n):
> sleep([94m1[39;49;00m)
[1m[31mE NameError: name 'sleep' is not defined[0m
[1m[31mplug/pytest_xdist/test_1.py[0m:6: NameError
=========================== short test summary info ============================
FAILED plug/pytest_xdist/test_1.py::test_get_info[0] - NameError: name 'sleep...
FAILED plug/pytest_xdist/test_1.py::test_get_info[1] - NameError: name 'sleep...
FAILED plug/pytest_xdist/test_1.py::test_get_info[2] - NameError: name 'sleep...
FAILED plug/pytest_xdist/test_1.py::test_get_info[3] - NameError: name 'sleep...
FAILED plug/pytest_xdist/test_1.py::test_get_info[4] - NameError: name 'sleep...
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_01[0]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_01[1]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_01[2]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_01[3]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_01[4]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_02[0]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_02[1]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_02[2]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_02[3]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_02[4]
[31m=================== [31m[1m5 failed[0m, [32m15 passed[0m, [31m[1m10 errors[0m[31m in 15.96s[0m[31m ===================[0m
"""指定需要多少个CPU来跑用例"""
!pytest -s -n 2 plug/pytest_xdist/
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
plugins: xdist-1.31.0, rerunfailures-9.0, assume-2.2.1, forked-1.1.3, repeat-0.8.0
gw0 [30] / gw1 [30][0m1m[1m[1m[1m
[31mF[0m[31mF[0m[31mF[0m[31mF[0m[31mF[0m[31mE[0m[31mE[0m[31mE[0m[31mE[0m[31mE[0m[31mE[0m[31mE[0m[31mE[0m[31mE[0m[31mE[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m
==================================== ERRORS ====================================
[31m[1m______________________ ERROR at setup of test_case2_01[0] ______________________[0m
[gw1] linux -- Python 3.6.9 /usr/bin/python3
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 6
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_01(open_51, n):
E fixture 'open_51' not found
> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:6
[31m[1m______________________ ERROR at setup of test_case2_01[1] ______________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 6
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_01(open_51, n):
E fixture 'open_51' not found
> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:6
[31m[1m______________________ ERROR at setup of test_case2_01[2] ______________________[0m
[gw1] linux -- Python 3.6.9 /usr/bin/python3
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 6
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_01(open_51, n):
E fixture 'open_51' not found
> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:6
[31m[1m______________________ ERROR at setup of test_case2_02[1] ______________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 12
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_02(open_51, n):
E fixture 'open_51' not found
> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:12
[31m[1m______________________ ERROR at setup of test_case2_01[3] ______________________[0m
[gw1] linux -- Python 3.6.9 /usr/bin/python3
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 6
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_01(open_51, n):
E fixture 'open_51' not found
> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:6
[31m[1m______________________ ERROR at setup of test_case2_02[2] ______________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 12
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_02(open_51, n):
E fixture 'open_51' not found
> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:12
[31m[1m______________________ ERROR at setup of test_case2_01[4] ______________________[0m
[gw1] linux -- Python 3.6.9 /usr/bin/python3
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 6
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_01(open_51, n):
E fixture 'open_51' not found
> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:6
[31m[1m______________________ ERROR at setup of test_case2_02[3] ______________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 12
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_02(open_51, n):
E fixture 'open_51' not found
> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:12
[31m[1m______________________ ERROR at setup of test_case2_02[4] ______________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 12
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_02(open_51, n):
E fixture 'open_51' not found
> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:12
[31m[1m______________________ ERROR at setup of test_case2_02[0] ______________________[0m
[gw1] linux -- Python 3.6.9 /usr/bin/python3
file /home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py, line 12
@pytest.mark.parametrize("n", list(range(5)))
def test_case2_02(open_51, n):
E fixture 'open_51' not found
> available fixtures: __pytest_repeat_step_number, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, login, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_job/test_case1.py:12
=================================== FAILURES ===================================
[31m[1m_______________________________ test_get_info[0] _______________________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
login = ('testyy', 'npoi213bn4'), n = 0
[37m@pytest[39;49;00m.mark.parametrize([33m"[39;49;00m[33mn[39;49;00m[33m"[39;49;00m, [96mlist[39;49;00m([96mrange[39;49;00m([94m5[39;49;00m)))
[94mdef[39;49;00m [92mtest_get_info[39;49;00m(login, n):
> sleep([94m1[39;49;00m)
[1m[31mE NameError: name 'sleep' is not defined[0m
[1m[31mplug/pytest_xdist/test_1.py[0m:6: NameError
[31m[1m_______________________________ test_get_info[1] _______________________________[0m
[gw1] linux -- Python 3.6.9 /usr/bin/python3
login = ('testyy', 'npoi213bn4'), n = 1
[37m@pytest[39;49;00m.mark.parametrize([33m"[39;49;00m[33mn[39;49;00m[33m"[39;49;00m, [96mlist[39;49;00m([96mrange[39;49;00m([94m5[39;49;00m)))
[94mdef[39;49;00m [92mtest_get_info[39;49;00m(login, n):
> sleep([94m1[39;49;00m)
[1m[31mE NameError: name 'sleep' is not defined[0m
[1m[31mplug/pytest_xdist/test_1.py[0m:6: NameError
[31m[1m_______________________________ test_get_info[2] _______________________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
login = ('testyy', 'npoi213bn4'), n = 2
[37m@pytest[39;49;00m.mark.parametrize([33m"[39;49;00m[33mn[39;49;00m[33m"[39;49;00m, [96mlist[39;49;00m([96mrange[39;49;00m([94m5[39;49;00m)))
[94mdef[39;49;00m [92mtest_get_info[39;49;00m(login, n):
> sleep([94m1[39;49;00m)
[1m[31mE NameError: name 'sleep' is not defined[0m
[1m[31mplug/pytest_xdist/test_1.py[0m:6: NameError
[31m[1m_______________________________ test_get_info[3] _______________________________[0m
[gw1] linux -- Python 3.6.9 /usr/bin/python3
login = ('testyy', 'npoi213bn4'), n = 3
[37m@pytest[39;49;00m.mark.parametrize([33m"[39;49;00m[33mn[39;49;00m[33m"[39;49;00m, [96mlist[39;49;00m([96mrange[39;49;00m([94m5[39;49;00m)))
[94mdef[39;49;00m [92mtest_get_info[39;49;00m(login, n):
> sleep([94m1[39;49;00m)
[1m[31mE NameError: name 'sleep' is not defined[0m
[1m[31mplug/pytest_xdist/test_1.py[0m:6: NameError
[31m[1m_______________________________ test_get_info[4] _______________________________[0m
[gw0] linux -- Python 3.6.9 /usr/bin/python3
login = ('testyy', 'npoi213bn4'), n = 4
[37m@pytest[39;49;00m.mark.parametrize([33m"[39;49;00m[33mn[39;49;00m[33m"[39;49;00m, [96mlist[39;49;00m([96mrange[39;49;00m([94m5[39;49;00m)))
[94mdef[39;49;00m [92mtest_get_info[39;49;00m(login, n):
> sleep([94m1[39;49;00m)
[1m[31mE NameError: name 'sleep' is not defined[0m
[1m[31mplug/pytest_xdist/test_1.py[0m:6: NameError
=========================== short test summary info ============================
FAILED plug/pytest_xdist/test_1.py::test_get_info[0] - NameError: name 'sleep...
FAILED plug/pytest_xdist/test_1.py::test_get_info[1] - NameError: name 'sleep...
FAILED plug/pytest_xdist/test_1.py::test_get_info[2] - NameError: name 'sleep...
FAILED plug/pytest_xdist/test_1.py::test_get_info[3] - NameError: name 'sleep...
FAILED plug/pytest_xdist/test_1.py::test_get_info[4] - NameError: name 'sleep...
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_01[0]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_01[1]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_01[2]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_02[1]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_01[3]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_02[2]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_01[4]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_02[3]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_02[4]
ERROR plug/pytest_xdist/test_job/test_case1.py::test_case2_02[0]
[31m=================== [31m[1m5 failed[0m, [32m15 passed[0m, [31m[1m10 errors[0m[31m in 9.64s[0m[31m ====================[0m
"""pytest-xdist和pytest-html很好的相结合"""
!pytest -s -n auto plug/pytest_xdist/ --html=plug/pytest_xdist/report.html --self-contained-html
[31mERROR: usage: pytest [options] [file_or_dir] [file_or_dir] [...]
pytest: error: unrecognized arguments: --html=plug/pytest_xdist/report.html --self-contained-html
inifile: None
rootdir: /home/ubuntu/MySpace/Python/pytest
[0m
生成HTML报告pytest-html
环境配置
"""安装插件"""
# !pip3 install pytest-html -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
Collecting pytest-html
Downloading http://pypi.doubanio.com/packages/00/a7/34f195c514d39b4453619b3eb284989e5adb09a2a68ac09ce3779f9b9478/pytest_html-2.1.1-py2.py3-none-any.whl
Collecting pytest>=5.0 (from pytest-html)
Downloading http://pypi.doubanio.com/packages/c7/e2/c19c667f42f72716a7d03e8dd4d6f63f47d39feadd44cc1ee7ca3089862c/pytest-5.4.1-py3-none-any.whl (246kB)
[K 100% |████████████████████████████████| 256kB 49.7MB/s ta 0:00:01
[?25hCollecting pytest-metadata (from pytest-html)
Downloading http://pypi.doubanio.com/packages/ce/8f/d0542e1aa0e23d902ce6acce2790736473da94453a36bdc7829f25734199/pytest_metadata-1.8.0-py2.py3-none-any.whl
Collecting py>=1.5.0 (from pytest>=5.0->pytest-html)
Downloading http://pypi.doubanio.com/packages/99/8d/21e1767c009211a62a8e3067280bfce76e89c9f876180308515942304d2d/py-1.8.1-py2.py3-none-any.whl (83kB)
[K 100% |████████████████████████████████| 92kB 51.3MB/s ta 0:00:01
[?25hCollecting attrs>=17.4.0 (from pytest>=5.0->pytest-html)
Downloading http://pypi.doubanio.com/packages/a2/db/4313ab3be961f7a763066401fb77f7748373b6094076ae2bda2806988af6/attrs-19.3.0-py2.py3-none-any.whl
Collecting importlib-metadata>=0.12; python_version < "3.8" (from pytest>=5.0->pytest-html)
Downloading http://pypi.doubanio.com/packages/ad/e4/891bfcaf868ccabc619942f27940c77a8a4b45fd8367098955bb7e152fb1/importlib_metadata-1.6.0-py2.py3-none-any.whl
Collecting wcwidth (from pytest>=5.0->pytest-html)
Downloading http://pypi.doubanio.com/packages/f6/d5/1ecdac957e3ea12c1b319fcdee8b6917ffaff8b4644d673c4d72d2f20b49/wcwidth-0.1.9-py2.py3-none-any.whl
Collecting pluggy<1.0,>=0.12 (from pytest>=5.0->pytest-html)
Downloading http://pypi.doubanio.com/packages/a0/28/85c7aa31b80d150b772fbe4a229487bc6644da9ccb7e427dd8cc60cb8a62/pluggy-0.13.1-py2.py3-none-any.whl
Collecting more-itertools>=4.0.0 (from pytest>=5.0->pytest-html)
Downloading http://pypi.doubanio.com/packages/72/96/4297306cc270eef1e3461da034a3bebe7c84eff052326b130824e98fc3fb/more_itertools-8.2.0-py3-none-any.whl (43kB)
[K 100% |████████████████████████████████| 51kB 40.3MB/s ta 0:00:01
[?25hCollecting packaging (from pytest>=5.0->pytest-html)
Downloading http://pypi.doubanio.com/packages/62/0a/34641d2bf5c917c96db0ded85ae4da25b6cd922d6b794648d4e7e07c88e5/packaging-20.3-py2.py3-none-any.whl
Collecting zipp>=0.5 (from importlib-metadata>=0.12; python_version < "3.8"->pytest>=5.0->pytest-html)
Downloading http://pypi.doubanio.com/packages/b2/34/bfcb43cc0ba81f527bc4f40ef41ba2ff4080e047acb0586b56b3d017ace4/zipp-3.1.0-py3-none-any.whl
Collecting six (from packaging->pytest>=5.0->pytest-html)
Downloading http://pypi.doubanio.com/packages/65/eb/1f97cb97bfc2390a276969c6fae16075da282f5058082d4cb10c6c5c1dba/six-1.14.0-py2.py3-none-any.whl
Collecting pyparsing>=2.0.2 (from packaging->pytest>=5.0->pytest-html)
Downloading http://pypi.doubanio.com/packages/8a/bb/488841f56197b13700afd5658fc279a2025a39e22449b7cf29864669b15d/pyparsing-2.4.7-py2.py3-none-any.whl (67kB)
[K 100% |████████████████████████████████| 71kB 39.9MB/s ta 0:00:01
[?25hInstalling collected packages: py, attrs, zipp, importlib-metadata, wcwidth, pluggy, more-itertools, six, pyparsing, packaging, pytest, pytest-metadata, pytest-html
Successfully installed attrs-19.3.0 importlib-metadata-1.6.0 more-itertools-8.2.0 packaging-20.3 pluggy-0.13.1 py-1.8.1 pyparsing-2.4.7 pytest-5.4.1 pytest-html-2.1.1 pytest-metadata-1.8.0 six-1.14.0 wcwidth-0.1.9 zipp-3.1.0
快速入门
注意事项:
- 在将文件或链接添加到独立报告时,插件会发出warnings;
- 在html测试报告中可能无法按预期显示文件或链接
"""在当前目录下创建一个report.html的测试报告"""
!pytest --html=report.html
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
plugins: xdist-1.31.0, rerunfailures-9.0, html-2.1.1, assume-2.2.1, forked-1.1.3, metadata-1.8.0, repeat-0.8.0
collected 39 items / 1 error / 38 selected [0m
==================================== ERRORS ====================================
[31m[1m_________________ ERROR collecting plug/pytest_xdist/test_1.py _________________[0m
[31mimport file mismatch:
imported module 'test_1' has this __file__ attribute:
/home/ubuntu/MySpace/Python/pytest/conftest/test_1.py
which is not the same as the test file we want to collect:
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_1.py
HINT: remove __pycache__ / .pyc files and/or use a unique basename for your test file modules[0m
-- generated html file: file:///home/ubuntu/MySpace/Python/pytest/report.html --
=========================== short test summary info ============================
ERROR plug/pytest_xdist/test_1.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
[31m=============================== [31m[1m1 error[0m[31m in 0.23s[0m[31m ===============================[0m
"""css是独立的,分享报告的时候样式会丢失,为了更好的分享发邮件展示报告,把css样式合并到html里"""
!pytest --html=report.html --self-contained-html
[1m============================= test session starts ==============================[0m
platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/ubuntu/MySpace/Python/pytest
plugins: xdist-1.31.0, rerunfailures-9.0, html-2.1.1, assume-2.2.1, forked-1.1.3, metadata-1.8.0, repeat-0.8.0
collected 39 items / 1 error / 38 selected [0m
==================================== ERRORS ====================================
[31m[1m_________________ ERROR collecting plug/pytest_xdist/test_1.py _________________[0m
[31mimport file mismatch:
imported module 'test_1' has this __file__ attribute:
/home/ubuntu/MySpace/Python/pytest/conftest/test_1.py
which is not the same as the test file we want to collect:
/home/ubuntu/MySpace/Python/pytest/plug/pytest_xdist/test_1.py
HINT: remove __pycache__ / .pyc files and/or use a unique basename for your test file modules[0m
-- generated html file: file:///home/ubuntu/MySpace/Python/pytest/report.html --
=========================== short test summary info ============================
ERROR plug/pytest_xdist/test_1.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
[31m=============================== [31m[1m1 error[0m[31m in 0.17s[0m[31m ===============================[0m

浙公网安备 33010602011771号