【问题标题】:Are eval Versions of unittest's assert* A Good Idea?unittest 的 assert* 的评估版本是一个好主意吗?
【发布时间】:2016-07-21 07:30:40
【问题描述】:

普遍认为使用eval 是不好的做法。对this question 的公认答案表明几乎总是有更好的选择。但是,标准库中的 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 的使用在这里合法吗,还是有更好的选择?

【问题讨论】:

  • 您这样做是为了解决什么问题?
  • @snakecharmerb 正如问题中所述,在“这允许运行类似代码”之后运行 sn-p,并在失败的迭代中打印出 msg
  • 当然,但这比获取现有行为更好吗?我看不出这比仅仅阅读你免费获得的堆栈跟踪和异常消息有什么好处。
  • @snakecharmerb IINM,堆栈跟踪不会显示在哪个迭代中引发了异常,不是吗?
  • 确实如此。你需要stackoverflow.com/a/34094/5320906之类的东西吗?

标签: python exception eval python-unittest


【解决方案1】:

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

如果您预计会引发异常,请通过以下方式断言:

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

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

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

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

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

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

【讨论】:

  • @Ami 那就是我的意思:如果你期望 no 异常被引发并且只是得到一个可比较的结果,那么你的单元测试应该做一个简单的assertEquals;如果以及何时引发了异常:恭喜,您发现了一个必须修复的错误!您绝对不应该将其压制为简单的“哎呀,不等于”。
  • @Ami 我想告诉你的是,我认为你的前提是完全有缺陷的。你为什么要处理一个异常,一个不应该发生的异常情况,和一个错误的计算一样?也许您的 API 设计不当,并且作为“正常”情况引发异常......?
  • @Ami 1) 如果你在循环中调用它,循环中的所有值都应该产生相同的结果:要么成功,要么预期失败。预期结果不应在循环中途改变。请记住:您总是为预期结果编写测试;你告诉 unittest 你期望,它会提醒你期望失败的情况。 2)如果循环写,LOC开销不会很大;您可以使用相同的 try..catch 语句覆盖对您的函数的 20 次调用;所以我看不出有什么大问题。
  • @Ami 如果有的话,我建议您编写自己的包装器,例如self.assertEqualsAndCatch(foo, i, msg=...)。即:将回调及其参数而不是字符串传递给eval
  • 只是想我会说这是一个很好的话题。 :) 希望有更多这样的好对话。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-05
  • 1970-01-01
  • 2012-10-16
  • 2020-03-29
  • 1970-01-01
  • 2022-08-23
  • 1970-01-01
相关资源
最近更新 更多