使用 'pytest.raises()' 捕获自定义异常并断言错误消息
Capture a custom exception with 'pytest.raises()' and assert the error message
我正在使用 pytest
到 运行 一些单元测试。我的一些测试涉及使用无效参数初始化对象,并测试引发的异常是否包含预期的错误消息。
为此,我使用 raises
,但是 pytest
未通过测试而不是捕获异常。测试输出显示出现了预期的异常。
>>> def test_400_invalid_org_id(self):
...
... # Setting host, org_id and body here.
...
... with pytest.raises(InvalidOrgIdException) as e_info:
... OrgRequest(host, org_id, body)
...
... assert str(e_info.value) == 'Invalid organisation ID.'
E InvalidOrgIdException: Invalid organisation ID.
显然 InvalidOrgIdException
是自定义类型(正确导入),是 Exception
的子类。出于某种原因 pytest.raises(Exception)
按预期工作。查看 documentation,它表明我应该能够断言已捕获的异常类型,但这也失败了。
>>> def test_400_invalid_org_id(self):
...
... # Setting host, org_id and body here.
...
... with pytest.raises(Exception) as e_info:
... OrgRequest(host, org_id, body)
...
... assert str(e_info.value) == 'Invalid organisation ID.'
... assert e_info.type is InvalidOrgIdException
E AssertionError: assert <class 'InvalidOrgIdException'> is InvalidOrgIdException
E + where <class 'InvalidOrgIdException'> = <ExceptionInfo InvalidOrgIdException('xxx', '^[a-z0-9]{16}$') tblen=3>.type
比较e_info.type
和InvalidOrgIdException
时,__init__
和__str__
方法有区别。请注意,导入的对象都来自同一个模块 - 我也不是在嘲笑。
>>> pprint(vars(e_info.type))
mappingproxy({'__doc__': 'Organisation ID in path is invalid.',
'__init__': <function InvalidOrgIdException.__init__ at 0x7f58b867e8b0>,
'__module__': 'builtins',
'__str__': <function InvalidOrgIdException.__str__ at 0x7f58b867e9d0>,
'__weakref__': <attribute '__weakref__' of 'InvalidOrgIdException' objects>})
...
>>> pprint(vars(InvalidOrgIdException))
mappingproxy({'__doc__': 'Organisation ID in path is invalid.',
'__init__': <function InvalidOrgIdException.__init__ at 0x7f58b867e670>,
'__module__': 'builtins',
'__str__': <function InvalidOrgIdException.__str__ at 0x7f58b867e700>,
'__weakref__': <attribute '__weakref__' of 'InvalidOrgIdException' objects>})
那么为什么 pytest
会这样,是否可以改变这种行为?
Class 引发 InvalidOrgIdException
class OrgRequest():
def __init__(self) -> None:
raise InvalidOrgIdException() from None
来自 pytest
运行
的完整输出
正在从 myproject
目录中 运行 进行测试(请参阅问题底部的文件结构)。
python -m pytest tests/unit/test_requests.py --verbose
测试 运行 带有 --verbose
参数。我不是 pytest
专家,但我认为没有更详细的输出可用。
========================================================================================= test session starts ==========================================================================================
platform linux -- Python 3.8.10, pytest-7.0.1, pluggy-1.0.0 -- /usr/bin/python
cachedir: .pytest_cache
rootdir: /home/me/myproject/tests, configfile: pytest.ini
plugins: env-0.6.2, mock-3.7.0
collected 1 item
tests/unit/test_requests.py::TestRequests::test__400_org_id_invalid FAILED
=============================================================================================== FAILURES ===============================================================================================
_________________________________________________________________________________ TestRequests.test_400_org_id_invalid _________________________________________________________________________________
self = <tests.unit.test_requests.TestRequests object at 0x7f7627e07e20>
def test_400_org_id_invalid(self):
with pytest.raises(InvalidOrgIdException) as e_info:
> OrgRequest()
tests/unit/test_requests.py:9:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <myfunction.core.request.OrgRequest object at 0x7f7627e20130>
def __init__(self) -> None:
> raise InvalidOrgIdException() from None
E InvalidOrgIdException: Invalid organisation ID.
myfunction/core/request.py:19: InvalidOrgIdException
======================================================================================= short test summary info ========================================================================================
FAILED tests/unit/test_requests.py::TestRequests::test_400_org_id_invalid - InvalidOrgIdException: Invalid organisation ID.
========================================================================================== 1 failed in 0.06s ===========================================================================================
文件结构
myproject
├── myfunction
| ├── app.py
| └── core
| ├── exception.py
| └── request.py
└── tests
└── unit
└── test_requests.py
Hacky“修复”
如果我将 InvalidOrgIdException
移动到与 OrgRequest
相同的模块中,然后从那里导入异常,则测试通过。显然,尽管我不想这样做,因为所有异常一起生活是有意义的。尽管存在“修复”,但我仍然想了解正在发生的事情以及它起作用的确切原因。
from app import InvalidOrgIdException
正如我在问题中提到的,pytest.raises()
捕获的异常的比较似乎与从我的代码导入的异常不同。我必须承认,我不是 Python 期望的,我不明白为什么这有效,但解决方案是添加 init.py 到 core
模块,并在那里导入该包中的所有模块。
from core.exception import *
from core.logging import *
# etc.
然后在 tests/unit/test_requests.py 中,我能够从 core
包中导入所有对象。
from myfunction.core import *
现在 pytest
看到它捕获的异常和导入的异常一样,我的测试通过了。
我正在使用 pytest
到 运行 一些单元测试。我的一些测试涉及使用无效参数初始化对象,并测试引发的异常是否包含预期的错误消息。
为此,我使用 raises
,但是 pytest
未通过测试而不是捕获异常。测试输出显示出现了预期的异常。
>>> def test_400_invalid_org_id(self):
...
... # Setting host, org_id and body here.
...
... with pytest.raises(InvalidOrgIdException) as e_info:
... OrgRequest(host, org_id, body)
...
... assert str(e_info.value) == 'Invalid organisation ID.'
E InvalidOrgIdException: Invalid organisation ID.
显然 InvalidOrgIdException
是自定义类型(正确导入),是 Exception
的子类。出于某种原因 pytest.raises(Exception)
按预期工作。查看 documentation,它表明我应该能够断言已捕获的异常类型,但这也失败了。
>>> def test_400_invalid_org_id(self):
...
... # Setting host, org_id and body here.
...
... with pytest.raises(Exception) as e_info:
... OrgRequest(host, org_id, body)
...
... assert str(e_info.value) == 'Invalid organisation ID.'
... assert e_info.type is InvalidOrgIdException
E AssertionError: assert <class 'InvalidOrgIdException'> is InvalidOrgIdException
E + where <class 'InvalidOrgIdException'> = <ExceptionInfo InvalidOrgIdException('xxx', '^[a-z0-9]{16}$') tblen=3>.type
比较e_info.type
和InvalidOrgIdException
时,__init__
和__str__
方法有区别。请注意,导入的对象都来自同一个模块 - 我也不是在嘲笑。
>>> pprint(vars(e_info.type))
mappingproxy({'__doc__': 'Organisation ID in path is invalid.',
'__init__': <function InvalidOrgIdException.__init__ at 0x7f58b867e8b0>,
'__module__': 'builtins',
'__str__': <function InvalidOrgIdException.__str__ at 0x7f58b867e9d0>,
'__weakref__': <attribute '__weakref__' of 'InvalidOrgIdException' objects>})
...
>>> pprint(vars(InvalidOrgIdException))
mappingproxy({'__doc__': 'Organisation ID in path is invalid.',
'__init__': <function InvalidOrgIdException.__init__ at 0x7f58b867e670>,
'__module__': 'builtins',
'__str__': <function InvalidOrgIdException.__str__ at 0x7f58b867e700>,
'__weakref__': <attribute '__weakref__' of 'InvalidOrgIdException' objects>})
那么为什么 pytest
会这样,是否可以改变这种行为?
Class 引发 InvalidOrgIdException
class OrgRequest():
def __init__(self) -> None:
raise InvalidOrgIdException() from None
来自 pytest
运行
的完整输出
正在从 myproject
目录中 运行 进行测试(请参阅问题底部的文件结构)。
python -m pytest tests/unit/test_requests.py --verbose
测试 运行 带有 --verbose
参数。我不是 pytest
专家,但我认为没有更详细的输出可用。
========================================================================================= test session starts ==========================================================================================
platform linux -- Python 3.8.10, pytest-7.0.1, pluggy-1.0.0 -- /usr/bin/python
cachedir: .pytest_cache
rootdir: /home/me/myproject/tests, configfile: pytest.ini
plugins: env-0.6.2, mock-3.7.0
collected 1 item
tests/unit/test_requests.py::TestRequests::test__400_org_id_invalid FAILED
=============================================================================================== FAILURES ===============================================================================================
_________________________________________________________________________________ TestRequests.test_400_org_id_invalid _________________________________________________________________________________
self = <tests.unit.test_requests.TestRequests object at 0x7f7627e07e20>
def test_400_org_id_invalid(self):
with pytest.raises(InvalidOrgIdException) as e_info:
> OrgRequest()
tests/unit/test_requests.py:9:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <myfunction.core.request.OrgRequest object at 0x7f7627e20130>
def __init__(self) -> None:
> raise InvalidOrgIdException() from None
E InvalidOrgIdException: Invalid organisation ID.
myfunction/core/request.py:19: InvalidOrgIdException
======================================================================================= short test summary info ========================================================================================
FAILED tests/unit/test_requests.py::TestRequests::test_400_org_id_invalid - InvalidOrgIdException: Invalid organisation ID.
========================================================================================== 1 failed in 0.06s ===========================================================================================
文件结构
myproject
├── myfunction
| ├── app.py
| └── core
| ├── exception.py
| └── request.py
└── tests
└── unit
└── test_requests.py
Hacky“修复”
如果我将 InvalidOrgIdException
移动到与 OrgRequest
相同的模块中,然后从那里导入异常,则测试通过。显然,尽管我不想这样做,因为所有异常一起生活是有意义的。尽管存在“修复”,但我仍然想了解正在发生的事情以及它起作用的确切原因。
from app import InvalidOrgIdException
正如我在问题中提到的,pytest.raises()
捕获的异常的比较似乎与从我的代码导入的异常不同。我必须承认,我不是 Python 期望的,我不明白为什么这有效,但解决方案是添加 init.py 到 core
模块,并在那里导入该包中的所有模块。
from core.exception import *
from core.logging import *
# etc.
然后在 tests/unit/test_requests.py 中,我能够从 core
包中导入所有对象。
from myfunction.core import *
现在 pytest
看到它捕获的异常和导入的异常一样,我的测试通过了。