【问题标题】:Should I unit test *what* or *how* for composite functions?我应该对复合函数的 *what* 或 *how* 进行单元测试吗?
【发布时间】:2020-02-26 09:43:49
【问题描述】:

我有函数 fg,它们已经有一组单元测试,确保它们的行为对于某些已知的输入输出对(加上异常处理等)是正确的。

现在我正在创建函数h(),如下:

def h(x):
    return f(x) + g(2*x)

对这个函数进行单元测试的好方法是什么?如果h() 更加复杂,这种情况会改变吗?


到目前为止我的想法:

我看到了测试h() 的两种可能性。

1。测试h() 是否在做正确的“管道”

模拟f(),以便在使用输入x_0 调用时返回y_0;模拟g(),以便在使用输入2*x_0 调用时返回z_0。然后检查调用h(x_0) 是否返回y_0+z_0

优势

  • 实施测试非常简单。
  • 可以快速找到错误连接fg 的输出,或使用错误参数调用它们的错误(例如,在h() 中调用g(x) 而不是g(2*x))。李>

缺点

  • 这是测试如何,而不是什么 h() 应该做什么。如果以后我想重构h(),那么我可能需要重写这些类型的测试。
  • 如果测试指定的管道没有为h() 产生预期的高级行为,那么这些测试将不会捕获此错误。例如,可能正确的管道应该是f(-x) + g(2*x),而我在函数定义和测试定义中都弄错了。

2。测试 什么 h() 应该做什么

假设h() 的目的是计算给定参数以下的素数之和。在这种情况下,h() 的自然测试套件将涉及使用已知的输入输出对对其进行测试。确保h(1)=2h(2)=5h(5)=28 等不关心如何 h() 计算这些数字的东西。

优势

  • 这种类型的测试检查h() 确实遵循其预期的高级行为。任何改变这一点的管道错误都将被捕获。
  • 重构h() 可能不需要更改测试套件,甚至会更容易,因为测试帮助我们保证函数的行为不会改变。

缺点

  • 在这个简单的示例中,很容易生成这样的对,因为h() 执行的映射不是很复杂(只需将n 的第一个素数相加即可)。然而,对于一个非常复杂的h(),我生成此类对的唯一选择可能是我想出一个输入x 并手动计算正确的输出。如果h 非常复杂,这似乎不合理。
  • 由于提出已知的输入-输出对需要我计算f()g() 在给定特定输入的情况下会产生什么,因此可能会有一些重复的工作,因为我在创建时已经花费了一些时间这些功能的单元测试。

相关问题Unit testing composite functions.

这个问题乍一看和我的很相似。然而,投票最多的两个答案提出了完全不同的解决问题的方法(我上面提到的两种方法)。我的问题是试图澄清每种方法的优缺点(也许还可以学习其他方法),并可能确定哪种方法总体上最好。如果没有一种方法在所有情况下都是最好的,我想了解在哪些情况下应该使用它们中的每一种。

【问题讨论】:

    标签: unit-testing testing


    【解决方案1】:

    问题是,您不想重复您的测试逻辑。

    您可能不想在 h 的测试中包含 fg 逻辑。
    在这种情况下,模拟非常适合,因为它只允许您测试h。 如果f 发生变化并出现回归,那么h 的测试不会失败,因为它的逻辑仍然有效。

    这样做的问题是您添加了更多层(单元)测试。 然而,好处是您完全隔离了您的测试,并且只在应该在的地方测试逻辑。

    至于一切,总要找到一个平衡点。但如果你的逻辑很复杂并且包含多个协作者(如fg),那么模拟可以降低测试的复杂性。

    如果您熟悉 TDD,则它与 Mockist vs Classicist 直接相关。 如果你有时间,我建议你看看这些videos 关于Outside-In TDD。你会学到很多东西。

    【讨论】:

    • 您好 Arnaud,感谢您抽出宝贵时间。我不认为我真的理解你的建议。你是说如果我的代码非常复杂,那么我应该测试 how 它在做什么而不是 what 它在做什么?也就是说,我应该测试它是否使用正确的输入调用正确的模拟,而不是找到已知的输入输出对?
    • 是的。这个想法是,如果您的逻辑很复杂,您希望将关注点和责任分开。因此,您可以拥有多个具有协作者的较小组件,而不是只测试一个组件。每个组件都可以通过模拟其协作者并断言正确的交互来进行单元测试。然后添加一个不使用模拟的验收测试,并将测试整个系统。
    【解决方案2】:

    这取决于你如何解释h

    1. 它是否代表fg 的组合,不管fg 将来会如何表现?然后你测试管道。

    2. 它是否会产生一个值,目前您使用fg 计算但可以使用替代实现?然后根据输入的预期值测试输出。

    【讨论】:

    • 这听起来很合理,谢谢!关于第 2 点,您将如何得出已知的输入输出对?您是否会手动计算预期输出,验证fg 应该为某个输入做什么,然后对结果求和?这不会导致重复工作吗?
    • 最终,是的。测试与效率无关;这是关于验证h 产生正确的结果。通常,您不会在测试中计算期望值;你硬编码它。关键是,无论如何您实现h,您的测试都应该简单地验证x 的输入产生y 的输出,但是您选择确定y
    • 使用h 使用的相同方法exact 生成预期的输出通常是个坏主意,因为您所做的只是验证h 是否正确执行,而不是 h 正确实现assert h(x) == f(x) * g(2*x) 之类的问题应该很明显。即使f(x) * g(2*x) 不是实现h 的正确方法,该测试也会通过。
    • 有趣的点。关于硬编码,是的,这就是我的意思(我会手动计算结果,并对其进行硬编码)。关于如何生成预期的输出,这对我来说有点棘手。在我目前的情况下,我能想到计算h 的预期值的唯一方法是计算f(x) * g(2*x)。也许这表明我应该将h 视为fg 的组合。谢谢!
    猜你喜欢
    • 2011-01-01
    • 1970-01-01
    • 2013-07-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-25
    相关资源
    最近更新 更多