unittest 的 assert* 的 eval 版本是个好主意吗?

Are eval Versions of unittest's assert* A Good Idea?

普遍接受的是标准库中的eval is bad practice. The accepted answer to this question states that there is almost always a better alternative. However, the timeit模块使用它,而我偶然发现了一个找不到更好的替代方案的案例。

unittest module 具有形式为

的断言函数
self.assert*(..., msg=None)

允许断言某些东西,如果失败则可选择打印 msg。这允许 运行 代码如

for i in range(1, 20):
    self.assertEqual(foo(i), i, str(i) + ' failed')

现在考虑 foo 可以引发异常的情况,例如

def foo(i):
    if i % 5 == 0:
        raise ValueError()
    return i

然后

  1. 一方面,不会打印 msg,因为从技术上讲,assertEqual 从未被要求用于有问题的迭代。
  2. 另一方面,从根本上说,foo(i) == i 不成立(不可否认,因为 foo(i) 从未完成执行),因此在这种情况下打印 msg.

我想要一个打印出 msg 的版本,即使失败原因是异常 - 这将允许准确了解哪个调用失败。使用 eval,我可以通过编写一个带有字符串的版本来做到这一点,例如以下(这是一个稍微简化的版本,只是为了说明这一点):

def assertEqual(lhs, rhs, msg=None):
    try:
        lhs_val = eval(lhs)
        rhs_val = eval(rhs)
        if lhs_val != rhs_val:
            raise ValueError()
    except:
        if msg is not None:
            print msg
        raise

然后使用

for i in range(1, 20):
    self.assertEqual('foo(i)', 'i', str(i) + ' failed')

当然,从技术上讲,通过将对 assert* 的每个调用放在 try/except/finally 中,当然可以完全不同地做到这一点,但我只能想到极其冗长的替代方案(这也需要重复 msg.)

这里使用 eval 是合法的,还是有更好的选择?

如果 意外 引发异常,则表明您的代码中存在错误。正是您希望通过单元测试发现的情况。这不仅仅是 不等于,它是您发现的需要修复的错误。

如果您期望 引发异常,请使用:

断言
with self.assertRaises(ValueError):
    foo(i)

如果您希望引发 no 异常,请使用:

try:
    foo(i)
except ValueError:
    self.fail("foo() raised ValueEror unexpectedly!")

如果有的话,我建议您像这样编写自己的包装器:

self.assertEqualsAndCatch(foo, i, msg=...)

即:将回调及其参数而不是字符串传递给 eval