mark 标记是 pytest 的特点之一,功能丰富且灵活。官方不仅内置了一些标记,还允许用户创建自定义标记,使用自定义标记来分类测试并运行指定分类测试用例。
自定义 mark 标记
可以在 pytest.ini 文件中自定义 mark 标记,如下示例:
[pytest]
markers =
smoke: 标记冒烟测试用例
process: 标记业务流程测试
priority: 标记优先级。1代表最高优先级,数字越大优先级越小
success: 标记成功用例
failed: 标记失败用例
character_values_and_boundaries: 标记字符值及边界测试
除了在 pytest.ini 自定义外,还有其它方法,这里就不介绍了,感兴趣可以访问官方文档:Working with custom markers。
使用自定义标记
使用自定义标记测试用例
- 标记单独测试用例:
# test_module1.py
import pytest
@pytest.mark.smoke
def test_case1():
assert [1]
@pytest.mark.smoke
@pytest.mark.process(serial=1) # serial为1代表购物流程
def test_case2():
assert [1]
@pytest.mark.process(serial=1)
def test_case3():
assert [2]
- 标记测试组
# test_module1.py
import pytest
@pytest.mark.success
class testCase:
def test_case4(self):
assert [1]
def test_case5(self):
assert [1]
def test_case6(self):
assert [2]
- 标记整个模块
# test_module2.py
import pytest
# 标记本模块
pytestmark = pytest.mark.failed
def test_case7():
assert [1]
def test_case8():
assert [1]
class testCase:
def test_case9(self):
assert [1]
def test_case10(self):
assert [1]
def test_case11(self):
assert [2]
运行指定标记的测试用例
必须指定 pytest.ini 文件的路径(绝对路径或相对路径)
- 运行标记为 smoke 的测试用例
pytest -v C:\Pythontest\test\ -c "pytest.ini" -m smoke
运行结果:
============================ test session starts =============================
configfile: pytest.ini
collected 11 items / 9 deselected / 2 selected
Config\test_mod1.py::test_case1 PASSED [ 50%]
Config\test_mod1.py::test_case2 PASSED [100%]
====================== 2 passed, 9 deselected in 0.01s =======================
- 运行除了标记为 smoke 外的所有测试用例
pytest -v C:\Pythontest\test\ -c "pytest.ini" -m "not smoke"
运行结果:
============================ test session starts =============================
configfile: pytest.ini
collected 11 items / 2 deselected / 9 selected
Config\test_mod1.py::test_case3 PASSED [ 11%]
Config\test_mod1.py::testCase::test_case4 PASSED [ 22%]
Config\test_mod1.py::testCase::test_case5 PASSED [ 33%]
Config\test_mod1.py::testCase::test_case6 PASSED [ 44%]
Config\test_mod2.py::test_case7 PASSED [ 55%]
Config\test_mod2.py::test_case8 PASSED [ 66%]
Config\test_mod2.py::testCase::test_case9 PASSED [ 77%]
Config\test_mod2.py::testCase::test_case10 PASSED [ 88%]
Config\test_mod2.py::testCase::test_case11 PASSED [100%]
====================== 9 passed, 2 deselected in 0.01s =======================
- 运行标记为 process(serial=1) 的测试用例
pytest -v C:\Pythontest\test\ -c "pytest.ini" -m "process(serial=1)"
运行结果:
============================ test session starts =============================
configfile: pytest.ini
collected 11 items / 9 deselected / 2 selected
Config\test_mod1.py::test_case2 PASSED [ 50%]
Config\test_mod1.py::test_case3 PASSED [100%]
====================== 2 passed, 9 deselected in 0.01s =======================
- 分别运行标记为 smoke 和标记为 success 的测试用例
pytest -v C:\Pythontest\test\ -c "pytest.ini" -m "smoke or success"
运行结果:
============================ test session starts =============================
configfile: pytest.ini
collected 11 items / 6 deselected / 5 selected
Config\test_mod1.py::test_case1 PASSED [ 20%]
Config\test_mod1.py::test_case2 PASSED [ 40%]
Config\test_mod1.py::testCase::test_case4 PASSED [ 60%]
Config\test_mod1.py::testCase::test_case5 PASSED [ 80%]
Config\test_mod1.py::testCase::test_case6 PASSED [100%]
====================== 5 passed, 6 deselected in 0.01s =======================
- 运行同时标记为 smoke 和 process(serial=1) 的测试用例
pytest -v C:\Pythontest\test\ -c "pytest.ini" -m "smoke and process(serial=1)"
运行结果:
============================ test session starts =============================
configfile: pytest.ini
collected 11 items / 10 deselected / 1 selected
Config\test_mod1.py::test_case2 PASSED [100%]
====================== 1 passed, 10 deselected in 0.01s ======================
传递参数给自定义标记
pytest 支持自定义标记接受位置参数和关键字参数,上文的 @pytest.mark.process(serial=1) 中 serial=1 就是关键字参数。
虽然可以传递函数对象等复杂类型,但推荐使用基本数据类型(字符串、数字等),只做标记用。
位置参数
使用方法如下:
import pytest
# 自定义标记
@pytest.mark.smoke(1, 2, 3)
def test_example(request):
marker = request.node.get_closest_marker("smoke")
if marker:
print(marker.args) # 输出: (1, 2, 3)
print(marker.args[0]) # 输出: 1
print(marker.args[1]) # 输出: 2
print(marker.args[2]) # 输出: 3
assert true
关键词参数
import pytest
# 自定义标记
@pytest.mark.smoke(a=1, b=2, c=3)
def test_example(request):
marker = request.node.get_closest_marker("smoke")
if marker:
print(marker.kwargs) # 输出: {'a': 1, 'b': 2, 'c': 3}
assert true
官方标记 mark.skip
@pytest.mark.skip 是 pytest 框架中用于跳过测试用例的官方标记。它允许开发者在特定条件下(如功能未实现、环境不支持等)跳过测试用例的执行,并在测试报告中显示跳过原因。
无条件跳过测试用例
对于暂时不会执行的测试用例,可以使用 @pytest.mark.skip 装饰:
- 跳过单个测试用例
import pytest
@pytest.mark.skip(reason="功能尚未实现")
def test_example():
assert False # 该测试不会执行
运行结果:
============================ test session starts =============================
collected 1 item
test/test_mod1.py::test_example SKIPPED (功能尚未实现) [100%]
============================= 1 skipped in 0.01s =============================
- 跳过测试组
import pytest
@pytest.mark.skip(reason="整个测试组暂不执行")
class testSkipClass:
def test_case1(self):
assert true
def test_case2(self):
assert true
运行结果:
============================ test session starts =============================
collected 2 items
test/test_mod1.py::testSkipClass::test_case1 SKIPPED (整个测试组暂不执行) [ 50%]
test/test_mod1.py::testSkipClass::test_case2 SKIPPED (整个测试组暂不执行) [100%]
============================= 2 skipped in 0.01s =============================
- 跳过模块文件
import pytest
pytestmark = pytest.mark.skip(reason="模块暂不执行")
def test_case1():
assert true
def test_case2():
assert true
运行结果:
============================ test session starts =============================
collected 2 items
test/test_mod1.py::test_case1 SKIPPED (模块暂不执行) [ 50%]
test/test_mod1.py::test_case2 SKIPPED (模块暂不执行) [100%]
============================= 2 skipped in 0.01s =============================
跳过满足特定条件的测试用例
pytest.mark.skipif 常用与跳过特定平台的测试用例,同样可以装饰单独测试用例、测试组、模块文件。不过应用场景比较少。
示例跳过win32环境(针对测试机):
import pytest
import sys
# 定义通用标记:在 Windows 平台跳过
@pytest.mark.skipif(
sys.platform == "win32",
reason="不支持 Windows"
)
def test_case():
assert true
动态跳过用例
相较于 pytest.mark.skipif,动态跳过用例无疑更灵活,应用场景更多。
- 跳过单独测试用例
import pytest
# 定义条件函数
def condition():
return true
def test_dynamic_skip():
if condition(): # 根据条件动态跳过
pytest.skip("不满足条件,跳过此测试")
assert true
运行结果:
============================ test session starts =============================
collected 1 item
test/test_mod1.py::test_dynamic_skip SKIPPED (不满足条件,跳过此测试) [100%]
============================= 1 skipped in 0.01s =============================
- 跳过测试组
import pytest
# 定义条件函数
def condition():
return true # 根据实际需求返回 true 或 False
class testDynamicSkip:
@classmethod
def setup_class(cls):
# 如果条件满足,则跳过整个类
if condition():
pytest.skip("不满足条件,跳过此测试组", allow_module_level=true)
def test_case1(self):
assert true # 此测试不会执行
def test_case2(self):
assert true # 此测试不会执行
运行结果:
================================== test session starts ===================================
collected 2 items
test/test_mod1.py::testDynamicSkip::test_case1 SKIPPED (不满足条件,跳过此测试组) [ 50%]
test/test_mod1.py::testDynamicSkip::test_case2 SKIPPED (不满足条件,跳过此测试组) [100%]
=================================== 2 skipped in 0.01s ===================================
- 跳过模块文件
import pytest
# 定义条件函数
def condition():
return true # 根据实际需求返回 true 或 False
if condition():
pytestmark = pytest.mark.skip(reason="不满足条件,模块暂不执行")
def test_case1():
assert true
def test_case2():
assert False
运行结果:
============================ test session starts =============================
collected 2 items
test/test_mod1.py::test_case1 SKIPPED (不满足条件,模块暂不执行) [ 50%]
test/test_mod1.py::test_case2 SKIPPED (不满足条件,模块暂不执行) [100%]
============================= 2 skipped in 0.01s =============================
- 跳过测试套件
用例目录里的 init.py 文件里定义:
# 定义条件函数
def condition():
return true # 根据实际需求返回 true 或 False
if condition():
pytestmark = pytest.mark.skip(reason="不满足条件,未执行测试")
满足条件后,会跳过这个目录下的所有测试用例。
官方标记 mark.xfail
它可以标记测试用例为预期失败,用于已经知道测试的功能存在问题,进行标记。当断言失败时,不会计入失败;如果断言意外通过成功,可以设置 strict=true 来将其标记为失败。
import pytest
@pytest.mark.xfail(reason="这个测试用例应该不通过", strict=true)
def test_case1():
assert False
@pytest.mark.xfail(reason="这个测试用例应该不通过")
def test_case2():
assert False
@pytest.mark.xfail(reason="这个测试用例应该不通过", , strict=true)
def test_case3():
assert True
运行结果:
============================ test session starts =============================
collected 3 items
test/test_mod1.py::test_case1 XFAIL (这个测试用例应该不通过) [ 33%]
test/test_mod1.py::test_case2 XPASS (这个测试用例应该不通过) [ 66%]
test/test_mod1.py::test_case3 FAILED [100%]
================================== FAILURES ==================================
_________________________________ test_case3 _________________________________
[XPASS(strict)] 这个测试用例应该不通过
========================== short test summary info ===========================
FAILED test/test_mod1.py::test_case3
================== 1 failed, 1 xfailed, 1 xpassed in 0.05s ===================
超时处理标记
本来应该介绍第三方标记 @pytest.mark.timeout,对测试用例设置超时时间。但是此插件在 Windows 平台不会按预期运行,我也不使用 Linux 平台,所以就不介绍它了。读者如果有需求,可以查看pytest-timeout文档。
不过我们可以自己实现一个简单的超时装饰器,后续我会介绍如何实现。这里不再赘述。
第三方标记 mark.order
pytest-order 是 pytest-ordering 的分支,比较新。它可以指定测试用例的执行顺序。
安装 pytest-order :
pip install pytest-order
pytest 框架中,测试用例的定义顺序就是执行顺序(包括模块),比如:
# test_mod1.py
def test_case1():
assert true
def test_case2():
assert true
def test_case3():
assert true
运行结果:
===================================================================== test session starts ======================================================================
collected 3 items
test/test_mod1.py::test_case1 PASSED [ 33%]
test/test_mod1.py::test_case2 PASSED [ 66%]
test/test_mod1.py::test_case3 PASSED [100%]
====================================================================== 3 passed in 0.01s =======================================================================
使用 @pytest.mark.order() 标记,可以改变测试用例的执行顺序,比如:
# test_mod1.py
import pytest
@pytest.mark.order(3)
def test_case1():
assert true
@pytest.mark.order(1)
def test_case2():
assert true
@pytest.mark.order(2)
def test_case3():
assert true
运行结果:
===================================================================== test session starts ======================================================================
collected 3 items
test/test_mod1.py::test_case2 PASSED [ 33%]
test/test_mod1.py::test_case3 PASSED [ 66%]
test/test_mod1.py::test_case1 PASSED [100%]
====================================================================== 3 passed in 0.01s =======================================================================
按索引排序
与 python 索引的规则一致,比如 -1 代表最后一个执行,-2 代表倒数第二个执行,0 代表第一个执行,1 代表第二个执行。
# test_mod1.py
import pytest
@pytest.mark.order(-2)
def test_three():
assert true
@pytest.mark.order(-1)
def test_four():
assert true
@pytest.mark.order(2)
def test_two():
assert true
@pytest.mark.order(1)
def test_one():
assert true
运行结果:
===================================================================== test session starts ======================================================================
collected 4 items
test/test_mod1.py::test_one PASSED [ 25%]
test/test_mod1.py::test_two PASSED [ 50%]
test/test_mod1.py::test_three PASSED [ 75%]
test/test_mod1.py::test_four PASSED [100%]
====================================================================== 4 passed in 0.01s =======================================================================
相对于其他测试用例的顺序
主要是两个参数:after(之后执行) 和 before(之前执行),如下:
# test_mod1.py
import pytest
@pytest.mark.order(after="test_second")
def test_third():
assert true
def test_second():
assert true
@pytest.mark.order(before="test_second")
def test_first():
assert true
运行结果:
===================================================================== test session starts ======================================================================
collected 3 items
test/test_mod1.py::test_first PASSED [ 33%]
test/test_mod1.py::test_second PASSED [ 66%]
test/test_mod1.py::test_third PASSED [100%]
====================================================================== 3 passed in 0.01s =======================================================================
还有其它用法,感兴趣者可以查阅pytest-order文档。
提醒一下,不要滥用 order 标记,非必要不使用。
第三方标记mark.dependency
它可以定义测试用例的依赖项。简单来说,如果被依赖的测试用例未通过(状态不是passed),则设置依赖项的测试用例会被跳过。
安装 pytest-dependency:
pip install pytest-dependency
基本使用方法:
# test_mod1.py
import pytest
# name可以为测试用例起别名,方便后续依赖
@pytest.mark.dependency(name="xfail")
@pytest.mark.xfail(reason="预期失败")
def test_xfail():
assert False
@pytest.mark.dependency(name="skip")
@pytest.mark.skip(reason="预期跳过")
def test_skip():
assert true
@pytest.mark.dependency(name="failed")
def test_failed():
assert False
@pytest.mark.dependency(name="passed")
def test_passed():
assert true
# depends 定义被依赖项,
@pytest.mark.dependency(depends=["xfail"])
def test_a():
assert true
@pytest.mark.dependency(depends=["skip"])
def test_b():
assert true
@pytest.mark.dependency(depends=["failed"])
def test_c():
assert true
# 只有被依赖项状态为passed,才会执行测试用例
@pytest.mark.dependency(depends=["passed"])
def test_d():
assert true
# 只有多个被依赖项状态同时为passed,才会执行测试用例
@pytest.mark.dependency(depends=["passed", "failed", "xfail", "skip"])
def test_e():
assert true
运行结果:
===================================================================== test session starts ======================================================================
collected 9 items
test/test_mod1.py::test_xfail XFAIL (预期失败) [ 11%]
test/test_mod1.py::test_skip SKIPPED (预期跳过) [ 22%]
test/test_mod1.py::test_failed FAILED [ 33%]
test/test_mod1.py::test_passed PASSED [ 44%]
test/test_mod1.py::test_a SKIPPED (test_a depends on xfail) [ 55%]
test/test_mod1.py::test_b SKIPPED (test_b depends on skip) [ 66%]
test/test_mod1.py::test_c SKIPPED (test_c depends on failed) [ 77%]
test/test_mod1.py::test_d PASSED [ 88%]
test/test_mod1.py::test_e SKIPPED (test_e depends on failed) [100%]
====================================================== 1 failed, 2 passed, 5 skipped, 1 xfailed in 0.02s =======================================================
还可以在测试组中使用及依赖的同时被依赖:
# test_mod1.py
import pytest
class test:
@staticmethod
@pytest.mark.dependency(name="failed")
def test_failed():
assert False
@staticmethod
@pytest.mark.dependency(name="passed")
def test_passed():
assert true
class test2:
@staticmethod
@pytest.mark.dependency(depends=["failed"])
def test_a():
assert true
@staticmethod
@pytest.mark.dependency(name="b",depends=["passed"])
def test_b():
assert False
@pytest.mark.dependency(depends=["b"])
def test_c():
assert true
运行结果:
===================================================================== test session starts ======================================================================
collected 5 items
test/test_mod1.py::test::test_failed FAILED [ 20%]
test/test_mod1.py::test::test_passed PASSED [ 40%]
test/test_mod1.py::test2::test_a SKIPPED (test_a depends on failed) [ 60%]
test/test_mod1.py::test2::test_b FAILED [ 80%]
test/test_mod1.py::test_c SKIPPED (test_c depends on b) [100%]
============================================================ 2 failed, 1 passed, 2 skipped in 0.02s ============================================================
也可以跨模块使用,但是涉及的模块都要被执行:
# test_mod1.py
import pytest
@pytest.mark.dependency(name="failed")
def test_failed():
assert False
运行结果:
===================================================================== test session starts ======================================================================
collected 2 items
test/test_mod1.py::test_failed FAILED [ 50%]
test/test_mod2.py::test_01 SKIPPED (test_01 depends on failed) [100%]
================================================================= 1 failed, 1 skipped in 0.01s =================================================================
当然还有一些用法,感兴趣者可以查阅pytest-dependency文档。
与 order 一样,不要滥用 dependency 标记,非必要不使用。测试用例的独立性是非常必要的。
除此之外,可以查阅pytest插件列表,有很多有用的插件。
tHEEND
© 转载需要保留原始链接,未经明确许可,禁止商业使用。CC BY-NC-ND 4.0