当前位置: 首页 > 工具软件 > Mark > 使用案例 >

pytest之mark

司寇高峯
2023-12-01

背景

本文总结pytest提供的mark功能。

说明

mark可分为2类:

  • 一类是系统内置的mark,不同的mark标记提供不同的功能。
  • 二类是自定义的mark。该类mark主要用于给测试用例分门别类,使得运行测试时可以指定运行符合哪一类标记的测试用例。

系统内置mark

系统内置mark可通过pytest --markers指令查看。
如下:有一些笔者没弄懂,请了解的读者解惑。

'''
@pytest.mark.flaky(reruns=1, reruns_delay=0):将test标记为重新运行“reruns”的值代表的次数。在重新运行之间添加'reruns_delay'秒的延迟。



@pytest.mark.allure_label: allure label marker(没搞懂)

@pytest.mark.allure_link: allure link marker(没搞懂)

@pytest.mark.allure_display_name: allure test name marker(没搞懂)

@pytest.mark.allure_description: allure description(没搞懂)

@pytest.mark.allure_description_html: allure description html(没搞懂)



@pytest.mark.filterwarnings(warning): 向给定的测试添加一个警告过滤器。参考https://docs.pytest.org/en/latest/warnings.html pytest-mark-filterwarnings(没有模拟出来,warning应该被限制只能传入某个警告的对象名)



@pytest.mark.skip(reason=None):使用可选的原因跳过给定的测试函数。例如:skip(reason="no way of current testing this")跳过测试。



@pytest.mark.skipif(condition,reason):如果eval(条件)结果为真值,则跳过给定的测试函数。评估发生在模块全局上下文中。例如:skipif(“系统。如果我们在win32平台上,平台== "win32"')将跳过测试。参见https://docs.pytest.org/en/latest/skipping.html



@pytest.mark.xfail(condition, reason=None, run=True, raises =None, strict=False):如果eval(condition)的值为真,则将测试函数标记为预期失败。可选地指定更好报告的理由,如果您甚至不想执行测试函数,则run=False。如果只期望特定的异常,您可以在引发中列出它们,如果测试以其他方式失败,则将报告为真正的失败。参见https://docs.pytest.org/en/latest/skipping.html



@pytest.mark.parametrize(argnames, argvalues):调用一个测试函数多次,依次传递不同的参数。如果argnames只指定一个名称,则argvalues通常需要一个值列表;如果argnames指定多个名称,则需要一个值元组列表。例如:@ parametertrize ('arg1',[1,2])将导致对修饰测试函数的两个调用,一个调用arg1=1,另一个调用arg1=2。有关更多信息和示例,请参见https://docs.pytest.org/en/latest/parametertrize.html。



@pytest.mark.usefixtures(fixturename1, fixturename2, ...):将测试标记为会使用所有指定的fixture。参考https://docs.pytest.org/en/latest/fixture.html usefixtures



@pytest.mark.tryfirst:标记一个钩子实现函数,这样插件机制就会尽可能早地调用它。(没搞懂)



@pytest.mark.trylast:标记一个钩子实现函数,使插件机制尽可能晚地调用它。(没搞懂)


'''

依次通过示例总结以上常用的内置mark:

@pytest.mark.flaky(reruns=1, reruns_delay=0)

使用flaky标记,指示如下用例失败会重新运行2次,重新运行的事件间隔为0s。

# ./test_case/test_func.py
import pytest
from func import *

class TestFunc:

	
	def test_add_by_class(self):
		assert add(2,3) == 5

@pytest.mark.flaky(reruns = 2)
def test_add_by_func_aaa():
	a = 4
	b = 6
	assert add(a,b) == 9

# ./run_test.py
import pytest

if __name__ == '__main__':
	pytest.main(['-v'])
	
'''
stdout:
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-5.3.4, py-1.8.1, pluggy-0.13.1 -- D:\Python3.7\python.exe
cachedir: .pytest_cache
rootdir: D:\Python3.7\project\pytest
plugins: allure-pytest-2.8.9, rerunfailures-8.0
collecting ... collected 2 items

test_case/test_func.py::TestFunc::test_add_by_class PASSED               [ 50%]
test_case/test_func.py::test_add_by_func_aaa RERUN                       [100%]
test_case/test_func.py::test_add_by_func_aaa RERUN                       [100%]
test_case/test_func.py::test_add_by_func_aaa FAILED                      [100%]

================================== FAILURES ===================================
____________________________ test_add_by_func_aaa _____________________________

    @pytest.mark.flaky(reruns = 2)
    def test_add_by_func_aaa():
    	a = 4
    	b = 6
>   	assert add(a,b) == 9
E    assert 10 == 9
E      -10
E      +9

test_case\test_func.py:14: AssertionError
==================== 1 failed, 1 passed, 2 rerun in 0.12s =====================
[Finished in 1.5s]
'''

@pytest.mark.skip(reason=None):

使用可选的原因跳过给定的测试函数。例如:skip(reason=“no way of current testing this”)跳过测试。

# ./test_case/test_func.py
import pytest
from func import *

class TestFunc:

	@pytest.mark.skip()
	def test_add_by_class(self):
		assert add(2,3) == 5

@pytest.mark.skip(reason="No No No")
def test_add_by_func_aaa():
	a = 4
	b = 6
	assert add(a,b) == 9


# ./run_test.py
import pytest

if __name__ == '__main__':
	pytest.main(['-v'])

'''
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-5.3.4, py-1.8.1, pluggy-0.13.1 -- D:\Python3.7\python.exe
cachedir: .pytest_cache
rootdir: D:\Python3.7\project\pytest
plugins: allure-pytest-2.8.9, rerunfailures-8.0
collecting ... collected 2 items

test_case/test_func.py::TestFunc::test_add_by_class SKIPPED              [ 50%]
test_case/test_func.py::test_add_by_func_aaa SKIPPED                     [100%]

============================= 2 skipped in 0.04s ==============================
[Finished in 1.4s]
'''
	

不知道把原因说明显示到哪里去了。

@pytest.mark.skipif(condition,reason):

如果eval(条件)结果为真值,则跳过给定的测试函数。评估发生在模块全局上下文中。例如:skipif(“系统。如果我们在win32平台上,平台== “win32”’)将跳过测试。参见https://docs.pytest.org/en/latest/skipping.html

# ./test_case/test_func.py
import pytest
from func import *

class TestFunc:

	@pytest.mark.skipif(1==2,reason="No No No")
	def test_add_by_class(self):
		assert add(2,3) == 5

@pytest.mark.skipif(1==1,reason="Y Y Y")
def test_add_by_func_aaa():
	a = 4
	b = 6
	assert add(a,b) == 10


# ./run_test.py
import pytest

if __name__ == '__main__':
	pytest.main(['-v'])


'''
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-5.3.4, py-1.8.1, pluggy-0.13.1 -- D:\Python3.7\python.exe
cachedir: .pytest_cache
rootdir: D:\Python3.7\project\pytest
plugins: allure-pytest-2.8.9, rerunfailures-8.0
collecting ... collected 2 items

test_case/test_func.py::TestFunc::test_add_by_class PASSED               [ 50%]
test_case/test_func.py::test_add_by_func_aaa SKIPPED                     [100%]

======================== 1 passed, 1 skipped in 0.04s =========================
[Finished in 1.4s]
'''	

这个标记还必须指定reason,不然会报错。但是也没搞懂reason显示到哪里去了。

@pytest.mark.xfail(condition, reason=None, run=True, raises=None, strict=False):

如果eval(condition)的值为真,则将测试函数标记为预期失败。可选地指定更好报告的理由,如果您甚至不想执行测试函数,则run=False。如果只期望特定的异常,您可以在引发中列出它们,如果测试以其他方式失败,则将报告为真正的失败。参见https://docs.pytest.org/en/latest/skipping.html

先说明该标记的几个参数,经过验证,得出如下结论:

  1. condition和resason参数必须同时指定,不然会报错。如果condition参数为真,则该标记生效,如果用例实际运行失败,则是xfail, 如果用例实际运行成功,则是xpass。如果condition参数为假,则相当于该标记不生效,执行效果相当于没有这个标记。
  2. run参数如果指定为False,效果是无论实际用例执行结果是失败还是成功,用例运行结果都记录xfail。如同直接强制指定用例结果为xfail,不管用例的运行,或者说用例根本没有得到执行的机会。
  3. raises预期用例会产生的指定异常,如果用例实际产生了该异常,则用例结果为xfail。如果用例实际没有产生该异常,在此基础上用例执行失败了,则pytest记录用例结果为fail,若在此基础上用例执行成功,则pytest记录用例结果为xpass
  4. strict参数是生效的参数,如果指定strict=True,则当对于标记为xfail的用例,但执行结果为xpass的用例,该用例的执行结果,会强制被指定为fail.
    xfail:预期失败,实际也失败
    xpass:预期失败,但实际成功了

xfail示例:

# ./test_case/test_func.py
import pytest
from func import *

class TestFunc:

	
	def test_add_by_class(self):
		assert add(2,3) == 5

@pytest.mark.xfail()
def test_add_by_func_aaa():
	a = 4
	b = 6
	assert add(a,b) == 9

# ./run_test.py
import pytest

if __name__ == '__main__':
	pytest.main(['-v'])

'''
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-5.3.4, py-1.8.1, pluggy-0.13.1 -- D:\Python3.7\python.exe
cachedir: .pytest_cache
rootdir: D:\Python3.7\project\pytest
plugins: allure-pytest-2.8.9, rerunfailures-8.0
collecting ... collected 2 items

test_case/test_func.py::TestFunc::test_add_by_class PASSED               [ 50%]
test_case/test_func.py::test_add_by_func_aaa XFAIL                       [100%]

======================== 1 passed, 1 xfailed in 0.10s =========================
[Finished in 1.5s]
'''	

xpass示例:

# ./test_case/test_func.py
import pytest
from func import *

class TestFunc:

	
	def test_add_by_class(self):
		assert add(2,3) == 5

@pytest.mark.xfail()
def test_add_by_func_aaa():
	a = 4
	b = 6
	assert add(a,b) == 10


# ./run_test.py
import pytest

if __name__ == '__main__':
	pytest.main(['-v'])
	

'''
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-5.3.4, py-1.8.1, pluggy-0.13.1 -- D:\Python3.7\python.exe
cachedir: .pytest_cache
rootdir: D:\Python3.7\project\pytest
plugins: allure-pytest-2.8.9, rerunfailures-8.0
collecting ... collected 2 items

test_case/test_func.py::TestFunc::test_add_by_class PASSED               [ 50%]
test_case/test_func.py::test_add_by_func_aaa XPASS                       [100%]

======================== 1 passed, 1 xpassed in 0.05s =========================
[Finished in 1.4s]
'''

strict=True示例:

# ./test_case/test_func.py
import pytest
from func import *

class TestFunc:

	
	def test_add_by_class(self):
		assert add(2,3) == 5

@pytest.mark.xfail(strict=True)
def test_add_by_func_aaa():
	a = 4
	b = 6
	assert add(a,b) == 10

# ./run_test.py
import pytest

if __name__ == '__main__':
	pytest.main(['-v'])


'''
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-5.3.4, py-1.8.1, pluggy-0.13.1 -- D:\Python3.7\python.exe
cachedir: .pytest_cache
rootdir: D:\Python3.7\project\pytest
plugins: allure-pytest-2.8.9, rerunfailures-8.0
collecting ... collected 2 items

test_case/test_func.py::TestFunc::test_add_by_class PASSED               [ 50%]
test_case/test_func.py::test_add_by_func_aaa FAILED                      [100%]

================================== FAILURES ===================================
____________________________ test_add_by_func_aaa _____________________________
[XPASS(strict)] 
========================= 1 failed, 1 passed in 0.04s =========================
[Finished in 1.4s]
'''	

Tips:还可以通过pytest.ini配置文件达到strict=True的效果。这样的效果范围将是全局的。

pytest.ini

[pytest]
xfail_strict = true

@pytest.mark.parametrize(argnames, argvalues):

调用一个测试函数多次,依次传递不同的参数。如果argnames只指定一个名称,则argvalues通常需要一个值列表;如果argnames指定多个名称,则需要一个值元组列表。例如:@ parametertrize (‘arg1’,[1,2])将导致对修饰测试函数的两个调用,一个调用arg1=1,另一个调用arg1=2。有关更多信息和示例,请参见https://docs.pytest.org/en/latest/parametertrize.html。

测试用例的参数化标记,该标记将单独在另外的文章中总结

@pytest.mark.usefixtures(fixturename1, fixturename2, …):

将测试标记为会使用所有指定的fixture。参考https://docs.pytest.org/en/latest/fixture.html usefixtures

pytest中指定测试用例使用fixture方法的标记。
指定测试用例使用fixture方法还可以通过像给函数传参的方式一般使用:
用法示例如下:

def test_case_001(fixturename1, fixturename2, ...):
	# 打印ixturename1代表的fixture方法的返回值
	print(ixturename1)

自定义标记:

指定pytest运行时,需要指定-m选项,来使用自定义标记。
该类mark主要用于给测试用例分门别类,使得运行测试时可以指定运行符合哪一类标记的测试用例。官方说法是将测试用例标记并分组,以便快速选中并运行。
-m选项可以使用表达式指定多个标记名。
比如:
选中同时符合mark1和mark2标记的用例:“-m mark1 and mark2”, 如果一个用例只有mark1标记,没有mark2标记,则是不会被选中。
仅选中符合mark1但不符mark2标记的用例:“-m mark1 and not mark2”,如果一个用例只有mark1标记,没有mark2标记,则会被选中。
mark1或者mark2中有一个符合就选中的用例:“-m mark1 or mark2”。
示例:

# ./test_case/test_func.py
import pytest
from func import *

class TestFunc:

	@pytest.mark.first
	def test_add_by_class(self):
		assert add(2,3) == 5

@pytest.mark.second
def test_add_by_func_aaa():
	a = 4
	b = 6
	assert add(a,b) == 10


# ./run_test.py
# ./run_test.py
import pytest

if __name__ == '__main__':
	# 该参数下:预期只会运行first标记的test_add_by_class
	pytest.main(['-v','-m', 'first and not second'])
	

'''
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-5.3.4, py-1.8.1, pluggy-0.13.1 -- D:\Python3.7\python.exe
cachedir: .pytest_cache
rootdir: D:\Python3.7\project\pytest
plugins: allure-pytest-2.8.9, rerunfailures-8.0
collecting ... collected 2 items / 1 deselected / 1 selected

test_case/test_func.py::TestFunc::test_add_by_class PASSED               [100%]

============================== warnings summary ===============================
D:\Python3.7\lib\site-packages\_pytest\mark\structures.py:327
  D:\Python3.7\lib\site-packages\_pytest\mark\structures.py:327: PytestUnknownMarkWarning: Unknown pytest.mark.first - is this a typo?  You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html
    PytestUnknownMarkWarning,

D:\Python3.7\lib\site-packages\_pytest\mark\structures.py:327
  D:\Python3.7\lib\site-packages\_pytest\mark\structures.py:327: PytestUnknownMarkWarning: Unknown pytest.mark.second - is this a typo?  You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html
    PytestUnknownMarkWarning,

-- Docs: https://docs.pytest.org/en/latest/warnings.html
================= 1 passed, 1 deselected, 2 warnings in 0.04s =================
[Finished in 1.4s]
'''	

这里有些警告,是因为我们的自定义标记没有注册,pytest识别为不合法标记。

如何注册自定义标记
通过在项目根目录下创建pytest.ini文件,然后添加section\option\value

[pytest]
markers=
markname1:description1
markname2:description2
addopts = --strict #该配置可以防止mark的拼写错误

 类似资料: