我们应该为单元测试修改功能签名吗?

 收藏

Suppose we have a function add() as below:

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

这个简单的功能可以添加输入值,将总和打印到控制台,也可以将其发送到某个外部存储(例如文件)。这就是我们理想地在应用程序中希望它的方式(意味着,我们不希望它返回任何东西)。

For purposes of unit testing, is it valid (from a design perspective) if we modify the function signature so that it returns the sum? We could then have a test like:

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

Better yet, is this (returning a value) the only way we could unit test it? Is there some valid way in which we could unit test the add() function without changing the function signature?

回复
  • in_id 回复

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

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

    我知道,除了打印并将其写入存储之外,您还应该更改函数,使其返回计算的值。这将允许您测试函数的计算部分。但是,该功能将仅部分测试。并且,该功能的目的将进一步变得模糊。

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

  • 糖小果 回复

    忽略此示例设计不佳的事实。

    对于这种情况,当您要检查某些内部行为而不是API时,您应该尝试使用一些测试库,例如gtest和gmock。 它使您不仅可以描述功能结果,还可以描述更复杂的期望。

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

    More details here: https://github.com/google/googletest/blob/master/googlemock/docs/ForDummies.md

    回答您的问题,出于测试目的而修改测试代码的任何部分始终是一种不良做法。在这种情况下,您将不再测试生产代码。正如之前建议的那样,最好将功能拆分为较小的部分并进行隔离测试。

  • 红颜暗 回复

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

    我为什么要这样说呢?

    好吧,您有一个名为add的方法,该方法将两个数字相加并调用其他名称。基本上,您的方法不执行任何操作,而是执行两项操作,这违反了“单一职责原则”。

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

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

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