DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

pytest.raises 的 match 有括号,明明有相同文本却没匹配

pytest.raises 的 match 按正则搜索,空括号不是字面括号。用 re.escape 检查固定文本,并区分子串匹配和完整异常消息断言。

异常已经按预期抛出,pytest.raises(..., match=...) 却说消息不匹配;断言里和报错里看起来都是 func() takes ...。先检查 match 中的括号、方括号、星号等字符:这里接收正则表达式,直接复制错误文本不一定表示按字面匹配。

match 使用正则搜索,不要求默认匹配整条消息。短模式 func() 可能通过,完整的 func() takes ... 却失败,并不矛盾。

空括号表示分组,不表示两个括号字符

正则里的 () 是空分组。func() 可以在含有 func() 的消息里找到 func;加入后面的空格和文字后,模式要求 func 后直接出现那段文字,而实际消息中间还有字面括号。

Python 原始字符串也不会关闭正则语法。r"func()" 仍有分组含义;要写字面括号,需使用 r"func\(\)"。原始字符串处理的是 Python 字符串转义,正则引擎还有自己的解释步骤。

只想检查固定文本,用 re.escape

下面用项目自己定义的错误消息演示,避免依赖某个 Python 版本的内置异常措辞。示例可作为测试文件的一部分:

import re
import pytest


def reject_call():
    raise TypeError("func() rejected [argument]")


def test_rejection_message():
    expected = "func() rejected [argument]"
    with pytest.raises(TypeError, match=re.escape(expected)):
        reject_call()

re.escape 把固定文本中的正则特殊字符转为字面匹配。异常类型仍限定为 TypeError,不用把它扩大到 Exception,也不用把消息缩成一个过于宽泛的词来让测试通过。

若需要检查可变部分,明确写正则,并仅转义固定部分。文件路径、括号内参数和其他动态内容不要在未经处理时直接拼进模式。异常文本中出现 2**32 等内容时,直接当模式使用还可能产生正则本身的错误,而不是简单的不匹配。

想匹配整条消息,需要另加约束

match=re.escape(expected) 仍是搜索:消息前后增加其他文字时,它可能继续通过。若测试契约要求完整字符串相同,可以在取得异常后单独比较:

def test_exact_rejection_message():
    with pytest.raises(TypeError) as excinfo:
        reject_call()
    assert str(excinfo.value) == "func() rejected [argument]"

需要正则表示完整结构时,也可以明确设置边界,但应同时考虑换行和标志的含义。Python 新版本中,异常 notes 也可能参与 pytest 的 match 检查;完整 str(excinfo.value) 比较检查的是异常本身的字符串,两者的断言范围不能随意互换。

修改前先确定测试要求的是异常类型、固定子串、可变模式还是完整消息。保留原错误信息和调用路径,只调整相应匹配方式。验证时让正确错误通过,再确认错误类型不符或关键消息不符时仍失败;不要为了消除一次失败而削弱测试原来要保护的条件。

内置异常的详细措辞可能随解释器版本变化。项目跨版本运行时,应检查这项全文断言是否真属于稳定契约,必要时断言稳定的关键内容;这与正则字符需要转义是两个独立判断。