前言
在
pytest
测试框架中,xfail
是一个重要的标记,用于明确标识那些预期会失败的测试用例。这不是表示测试编写有误或测试框架有问题,而是表示在特定条件下,我们预期这些测试会失败。
当我们使用
xfail
标记一个测试时,pytest
将这样处理:
- 如果测试确实失败了,它会被认为是预期失败(xfail),并且会在测试报告中标记为成功。
- 如果测试意外成功了,它会被认为是预期通过(xpass),这通常是一个警告信号,表明预期失败的原因可能已经不存在,或者测试本身的实现可能有问题。
应用场景
使用
xfail
标记的预期失败场景可能包括:
- 尚未完成的功能或修复。
- 在某些平台或环境上已知的问题。
- 依赖于外部资源或服务的测试,当这些资源或服务暂时不可用时。
- 尚未实现或尚未完成的测试逻辑。
通过明确标记这些预期失败的测试,
pytest
帮助开发者更好地了解测试套件的状态,避免误报,并允许他们更专注于解决真正的问题。
xfail 源码分析
这段代码是
pytest
框架内部的一部分,用于处理xfail
(预期失败)标记。
_XfailMarkDecorator
类继承自MarkDecorator
,是专门用来处理xfail
标记的装饰器。
- 第一个
__call__
方法接受一个Markable
类型的参数arg
,并返回一个Markable
类型的对象。这里Markable
可能是一个协议(Protocol),定义了可以被标记的对象应有的接口。这个方法可能是用于直接标记一个测试函数或方法。- 第二个
__call__
方法接受多个参数,包括condition
、*conditions
、reason
、run
、raises
和strict
,并返回一个MarkDecorator
类型的对象。这个方法提供了更灵活的标记选项,允许用户指定多个条件、失败的原因、是否运行测试、预期抛出的异常类型以及是否严格等。参数解释:
condition
和\*conditions
:用于指定标记的条件,可以是字符串或布尔值。多个条件可以通过*conditions
传递。reason
:一个字符串,解释为什么测试预期会失败。run
:一个布尔值,指定是否应该运行这个测试。如果设置为False
,则测试将被跳过。raises
:一个异常类型或异常类型的元组,指定测试应该引发的异常。如果测试引发了其他异常或没有引发异常,则测试将失败。strict
:一个布尔值,如果设置为True
,则只有当测试确实失败时,才会将其视为xfail
。如果测试成功,则会被视为真正的失败。总结:
_XfailMarkDecorator
类的源码定义了如何使用xfail
标记来标记预期会失败的测试。- 通过重载
__call__
方法并提供灵活的参数选项,我们可以方便地给测试函数或方法添加xfail
标记,并指定相关的条件和信息。- 这些标记信息将被
pytest
框架用于运行时的测试和报告生成。
class _XfailMarkDecorator(MarkDecorator):
@overload # type: ignore[override,misc,no-overload-impl]
def __call__(self, arg: Markable) -> Markable:
...
@overload
def __call__(
self,
condition: Union[str, bool] = False,
*conditions: Union[str, bool],
reason: str = ...,
run: bool = ...,
raises: Union[
None, Type[BaseException], Tuple[Type[BaseException], ...]
] = ...,
strict: bool = ...,
) -> MarkDecorator:
...
xfail 装饰方法
示例代码
import pytest
@pytest.mark.xfail
def test_case_01():
print("进入test_case_01函数")
assert 1 == 1
执行结果
xfail 装饰类
示例代码
import pytest
@pytest.mark.xfail
class TestClassDemo2:
def test_case_01(self):
print("进入test_case_01函数")
assert 1 == 1
def test_case_02(self):
print("进入test_case_02函数")
assert 1 == 2
执行结果
xfail 装饰模块
示例代码
import pytest
pytestmark = pytest.mark.xfail(reason="当前环境或其他原因没法测试")
def test_case():
print("进入test_case函数")
assert 1 == 1
class TestClassDemo2:
def test_case_01(self):
print("进入test_case_01函数")
assert 1 == 1
def test_case_02(self):
print("进入test_case_02函数")
assert 1 == 2
执行结果
xfail的run参数讲解
官方描述:
- 如果
run
为True
,则即使测试被标记为xfail
,它仍然会被执行。这允许你查看测试的运行情况,即使你知道它会失败。- 如果
run
为False
,则测试不会被执行。这在你想要跳过某些测试,但又不想将它们完全从测试套件中移除时很有用。简而言之:
run=True
(默认值):测试会被执行,即使它被标记为xfail
。run=False
:测试不会被执行。
示例代码
import sys
import pytest
@pytest.mark.xfail(condition=sys.platform == "darwin", reason="Mac系统无法执行")
def test_case():
print("进入test_case函数")
assert 1 == 1
@pytest.mark.xfail(condition=True, run=False, reason="Mac系统无法执行")
class TestClassDemo2:
print("进入类的内部")
def test_case_01(self):
print("进入test_case_01函数")
assert 1 == 1
def test_case_02(self):
print("进入test_case_02函数")
assert 1 == 2
执行结果
xfail的raises参数讲解
示例代码
raises:抛出某类型异常,和用例中raise的异常类型一样,结果就是FAILED,否则结果是XFAIL
import pytest
@pytest.mark.xfail
def test_case_01():
print("进入test_case_01函数")
assert 1 == 1
@pytest.mark.xfail
def test_case_02():
print("进入test_case_02函数")
assert 1 == 1
raise Exception("手动抛出异常")
@pytest.mark.xfail(raises=RuntimeError, reason="预期抛出RuntimeError异常")
def test_case_03():
print("进入test_case_03函数")
assert 1 == 1
raise RuntimeError("运行时异常")
@pytest.mark.xfail(raises=RuntimeError, reason="预期抛出RuntimeError异常,但实际没有异常")
def test_case_04():
print("进入test_case_04函数")
assert 1 == 1
@pytest.mark.xfail(raises=RuntimeError, reason="预期抛出RuntimeError异常,但实际不是")
def test_case_05():
print("进入test_case_05函数")
assert 1 == 1
raise TypeError("随便抛出个异常")
执行结果
xfail的strict参数讲解
官方描述:
- 如果
strict
为False
(默认值),即使xfail
标记的测试用例通过了断言(即测试用例预期失败但实际上成功了),该测试仍然会被标记为XPASS
(意外的通过)。如果测试用例未能通过断言(即测试用例预期失败并且实际也失败了),则会被标记为XFAIL
(预期的失败)。- 如果
strict
为True
,则xfail
标记的测试用例在通过断言时会被视为FAILED
(执行失败),因为这违反了预期失败的行为。如果测试未能通过断言,它仍然会被标记为XFAIL
(预期的失败)。简而言之:
strict=False
(默认):- 测试通过断言:
XPASS
(意外的通过)- 测试未通过断言:
XFAIL
(预期的失败)strict=True
:- 测试通过断言:
FAILED
(失败)- 测试未通过断言:
XFAIL
(预期的失败)
示例代码
import pytest
@pytest.mark.xfail
def test_case_01():
print("进入test_case_01函数")
assert 1 == 1
@pytest.mark.xfail
def test_case_02():
print("进入test_case_02函数")
assert 1 == 2
@pytest.mark.xfail(strict=True, reason="如果断言成功,则标记测试用例为执行失败")
def test_case_03():
print("进入test_case_03函数")
assert 1 == 1
@pytest.mark.xfail(strict=True, reason="如果断言失败,则标记测试用例为预期失败")
def test_case_04():
print("进入test_case_04函数")
assert 1 == 2
@pytest.mark.xfail(strict=False, reason="如果断言成功,则标记测试用例为预期通过")
def test_case_05():
print("进入test_case_05函数")
assert 1 == 1
@pytest.mark.xfail(strict=False, reason="如果断言失败,则标记测试用例为预期失败")
def test_case_06():
print("进入test_case_06函数")
assert 1 == 2
执行结果