【问题标题】:Should we modify a function signature for unit testing?我们应该修改单元测试的函数签名吗?
【发布时间】:2019-10-15 19:15:48
【问题描述】:

假设我们有如下函数add()

void add(int a, int b) {
    int sum=a+b;
    cout<<sum;
    sendSumToStorage(sum);
}

这个简单的函数添加到输入值,将总和打印到控制台,并将其发送到一些外部存储(例如,文件)。这就是我们在应用程序中理想的方式(意思是,我们不希望它返回任何东西)。

出于单元测试的目的,如果我们修改函数签名使其返回sum,它是否有效(从设计的角度来看)?然后我们可以进行如下测试:

bool checkAdd() {
    int res=add(3, 4);
    if(res==7) return true;
    else return false;
}

更好的是,这是(返回一个值)我们可以对其进行单元测试的唯一方法吗?是否有一些有效的方法可以在不更改函数签名的情况下对add() 函数进行单元测试?

【问题讨论】:

  • 使用模拟类以某种方式观察发送到存储的值会更正常
  • @AlanBirtles,你能详细说明一下吗?
  • 如果你添加一个minimal reproducible example 那么我可以

标签: c++ unit-testing testing


【解决方案1】:

像您示例中的函数这样的函数被认为是非常糟糕的做法。

我为什么这么说?

好吧,您有一个名为 add 的方法,它将两个数字相加 AND 调用其他方法。基本上你的方法不是做一件事,而是做两件事,这违反了单一职责原则。

这使得测试变得更加困难,因为您不能单独测试 add 方法。

因此,您可以将其分成两个具有良好名称的方法,以反映它们的作用并分别测试它们。

如果您不想在方法之间出现状态问题,则必须在有意义的地方开始返回结果。

【讨论】:

  • 我非常同意你所说的;但请注意,以上只是 一个示例。认为该功能只有两个任务(如运输者的任务)可能更有帮助 - 发送到控制台和存储。这里进行的计算只是一些与如何发送数据相关的配置参数。我们要测试的正是这些配置参数。
【解决方案2】:

忽略这个例子设计不好的事实。

对于这样的情况,当您想要检查一些内部行为而不是 API 时,您应该尝试使用一些测试库,例如 gtest 和 gmock。 它允许您描述更复杂的期望,而不仅仅是函数结果。

例如,您可以使用 EXPECT_CALL 宏设置期望在代码执行期间调用某些方法。

更多细节在这里: https://github.com/google/googletest/blob/master/googlemock/docs/ForDummies.md

回答您的问题,为了测试目的而修改测试代码的任何部分始终是一种不好的做法。在这种情况下,您不再测试生产代码。正如之前建议的那样,最好将功能分成更小的部分并单独测试它们。

【讨论】:

    【解决方案3】:

    更改代码设计以提高可测试性非常普遍,通常被认为是一种良好做法。显然,并非所有此类更改都必然是真正的改进 - 有时存在更好的解决方案。

    在您的情况下,代码很难测试,因为它结合了计算(加法)与依赖组件(输出和存储数据)的交互。在您的情况下(正如 Andrei 指出的那样),该函数也违反了 SRP,但混合计算和交互通常会使测试更加困难,即使在没有违反 SRP 的情况下也是如此。

    我了解,您可以更改函数,使其返回计算值,并将其打印并写入存储。这将允许您测试函数的计算部分。然而,该功能将仅被部分测试。而且,函数的用途会被进一步混淆。

    如果您将函数拆分为一个执行计算的函数和一个执行交互的函数,您可以使用单元测试彻底测试第一个函数,而对另一个使用集成测试。同样,这种方法的有用性与代码是否违反 SRP 无关。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-02
      • 2016-07-23
      • 2021-10-09
      • 1970-01-01
      • 2020-05-30
      • 2021-08-08
      相关资源
      最近更新 更多