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
然后
- 一方面,不会打印
msg
,因为从技术上讲,assertEqual
从未被要求用于有问题的迭代。
- 另一方面,从根本上说,
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
。
普遍接受的是标准库中的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
然后
- 一方面,不会打印
msg
,因为从技术上讲,assertEqual
从未被要求用于有问题的迭代。 - 另一方面,从根本上说,
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
。