mark 标记是 pytest 的特点之一,功能丰富且灵活。官方不仅内置了一些标记,还允许用户创建自定义标记,使用自定义标记来分类测试并运行指定分类测试用例。

自定义 mark 标记

可以在 pytest.ini 文件中自定义 mark 标记,如下示例:

[pytest]
markers =
    smoke: 标记冒烟测试用例
    process: 标记业务流程测试
	priority: 标记优先级。1代表最高优先级,数字越大优先级越小
	success: 标记成功用例
	failed: 标记失败用例
    character_values_and_boundaries: 标记字符值及边界测试

除了在 pytest.ini 自定义外,还有其它方法,这里就不介绍了,感兴趣可以访问官方文档:Working with custom markers

使用自定义标记

使用自定义标记测试用例

  1. 标记单独测试用例:
# 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]
  1. 标记测试组
# 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]
  1. 标记整个模块
# 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 文件的路径(绝对路径或相对路径)

  1. 运行标记为 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 ======================= 
  1. 运行除了标记为 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 ======================= 
  1. 运行标记为 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 ======================= 
  1. 分别运行标记为 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 ======================= 
  1. 运行同时标记为 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 装饰:

  1. 跳过单个测试用例
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 ============================= 
  1. 跳过测试组
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 ============================= 
  1. 跳过模块文件
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,动态跳过用例无疑更灵活,应用场景更多。

  1. 跳过单独测试用例
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 ============================= 
  1. 跳过测试组
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 ===================================
  1. 跳过模块文件
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 ============================= 
  1. 跳过测试套件

用例目录里的 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