万事都从异常开始,pytest也让我们从异常开始吧
前置条件
- 确认pytest完成安装
pip install pytest
1. 先看第一条用例
# content of test_1.py
import pytest
def f():
return 3
def test_function():
assert f() != 4
执行这条用例,进入用例所在目录,执行pytest -sv test_1.py
,稍后再解释pytest命令行所有的参数
(venv) C:\测试文件夹\project\python\pytest_demo> pytest -sv test_1.py
======================================================================================= test session starts =======================================================================================
platform win32 -- Python 3.6.8, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 -- c:\program files (x86)\python36-32\python.exe
cachedir: .pytest_cache
rootdir: C:\测试文件夹\project\python\pytest_demo
collected 1 item
test_1.py::test_function PASSED
======================================================================================== 1 passed in 0.01s ========================================================================================
2. 断言不总是成功,出现异常如何处理,尤其是当该异常是我们故意构造的异常。
- 修改
test_function
中的代码的!=
为==
再次执行:
(venv) C:\测试文件夹\project\python\pytest_demo>pytest -sv test_1.py
=============================================== test session starts ================================================
platform win32 -- Python 3.6.8, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 -- c:\program files (x86)\python36-32\python.e
xe
cachedir: .pytest_cache
rootdir: C:\测试文件夹\project\python\pytest_demo
collected 1 item
test_1.py::test_function FAILED
===================================================== FAILURES =====================================================
__________________________________________________ test_function ___________________________________________________
def test_function():
> assert f() == 4
E assert 3 == 4
E +3
E -4
test_1.py:9: AssertionError
============================================= short test summary info ==============================================
FAILED test_1.py::test_function - assert 3 == 4
================================================ 1 failed in 0.11s =================================================
3. 处理异常
- pytest自带异常处理方式,可以使用
with pytest.raises(ZeroDivisionError) as exception_info:
pass
- 其中
exception_info
有3个关键属性会经常用到:.type, .value, .typename
- 分别为异常的类型,异常的msg信息,异常的名称
4. 在步骤1的文件中添加以下代码:
def test_f2():
with pytest.raises(NameError) as exception_info:
f2()
print()
print('error type is {}'.format(exception_info.type))
print('error typename is {}'.format(exception_info.typename))
print('error message is {}'.format(exception_info.value))
- 执行如下命令
pytest -sv test_1.py::test_f2
,两个冒号表示层级关系,可以通过此方式指定具体要执行的用例 - 此时异常已经被捕获,用例正常通过
- 同时可以看到打印出来的
exception_info
的属性
(venv) C:\测试文件夹\project\python\pytest_demo>pytest -sv test_1.py::test_f2
========================================== test session starts ===========================================
platform win32 -- Python 3.6.8, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 -- c:\program files (x86)\python36-3
2\python.exe
cachedir: .pytest_cache
rootdir: C:\测试文件夹\project\python\pytest_demo
collected 1 item
test_1.py::test_f2
error type is <class 'NameError'>
error typename is NameError
error message is name 'f2' is not defined
PASSED
=========================================== 1 passed in 0.01s ============================================
5. 上述例子是可预期的错误,或者说是故意制造的错误后并产生异常
- 有时候用例可能因为功能没有实现,或者依赖项错误导致用例暂时失败,则需要用另外一种方式标记失败
@pytest.mark.xfail()
# param: reason 该用例当前失败的原因
# param: raises 声明用例失败是什么异常导致的,当该用例执行过程中抛出的异常和声明的不同时,该用例不会显示xfail,而是fail
@pytest.mark.xfail(reason='The function is not implemented', raises=NameError)
def test_f3():
f3()
执行这条用例
(venv) C:\测试文件夹\project\python\pytest_demo>pytest -sv test_1.py::test_f3
==================================================================== test session starts =====================================================================
platform win32 -- Python 3.6.8, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 -- c:\program files (x86)\python36-32\python.exe
cachedir: .pytest_cache
rootdir: C:\测试文件夹\project\python\pytest_demo
collected 1 item
test_1.py::test_f3 XFAIL (The function is not implemented)
===================================================================== 1 xfailed in 0.04s =====================================================================
6. 为失败的断言定义你自己的解释
- 可以通过实现
pytest_assertrepr_compare
钩子来添加自定义详细解释。 - 先定义如下的Foo和测试用例
# content of test_foo_compare.py
class Foo:
def __init__(self, val):
self.val = val
def __eq__(self, other):
return self.val == other.val
def test_compare():
f1 = Foo(1)
f2 = Foo(2)
assert f1 == f2
- 先执行一次,现在使用的是默认的断言,展示的错误信息也是默认的
(venv) C:\测试文件夹\project\python\pytest_demo>pytest -sv test_foo_compare.py::test_compare
=========================================================================================== test session starts ============================================================================================
platform win32 -- Python 3.6.8, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 -- c:\program files (x86)\python36-32\python.exe
cachedir: .pytest_cache
rootdir: C:\测试文件夹\project\python\pytest_demo
collected 1 item
test_foo_compare.py::test_compare FAILED
================================================================================================= FAILURES =================================================================================================
_______________________________________________________________________________________________ test_compare _______________________________________________________________________________________________
def test_compare():
f1 = Foo(1)
f2 = Foo(2)
> assert f1 == f2
E assert <test_foo_com...at 0x040EB310> == <test_foo_com...at 0x040EB2D0>
E +<test_foo_compare.Foo object at 0x040EB310>
E -<test_foo_compare.Foo object at 0x040EB2D0>
test_foo_compare.py:13: AssertionError
========================================================================================= short test summary info ==========================================================================================
FAILED test_foo_compare.py::test_compare - assert <test_foo_com...at 0x040EB310> == <test_foo_com...at 0x040EB2D0>
============================================================================================ 1 failed in 0.10s =============================================================================================
- 在同级目录的
conftest.py
中定义钩子,来生成自定义比较的异常信息 - op 表示断言的操作符
- left 表示操作符左侧被比较的对象
- right 表示操作符右侧被比较的对象
from test_foo_compare import Foo
def pytest_assertrepr_compare(op, left, right):
if isinstance(left, Foo) and isinstance(right, Foo) and op == "==":
return [
"Comparing Foo instances:",
" vals: {} != {}".format(left.val, right.val),
]
- 再次执行用例,断言已经变成自定义断言了,需要注意的是,此处断言仅仅是对两个Foo的等于生效,对其他的不生效
(venv) C:\测试文件夹\project\python\pytest_demo>pytest -sv test_foo_compare.py::test_compare
=========================================================================================== test session starts ============================================================================================
platform win32 -- Python 3.6.8, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 -- c:\program files (x86)\python36-32\python.exe
cachedir: .pytest_cache
rootdir: C:\测试文件夹\project\python\pytest_demo
collected 1 item
test_foo_compare.py::test_compare FAILED
================================================================================================= FAILURES =================================================================================================
_______________________________________________________________________________________________ test_compare _______________________________________________________________________________________________
def test_compare():
f1 = Foo(1)
f2 = Foo(2)
> assert f1 == f2
E assert Comparing Foo instances:
E vals: 1 != 2
test_foo_compare.py:13: AssertionError
========================================================================================= short test summary info ==========================================================================================
FAILED test_foo_compare.py::test_compare - assert Comparing Foo instances:
============================================================================================ 1 failed in 0.10s =============================================================================================