将单元测试作为正在测试的类的朋友有什么问题?

cch*_*ion 54 c++ oop unit-testing encapsulation friend

在C++中,我经常将单元测试类作为我正在测试的类的朋友.我这样做是因为我有时觉得需要为私有方法编写单元测试,或者我想要访问某个私有成员,这样我就可以更轻松地设置对象的状态,以便我可以测试它.对我来说,这有助于保持封装和抽象,因为我没有修改类的公共接口或受保护的接口.

如果我购买第三方库,我不希望它的公共接口被一堆我不需要知道的公共方法污染,因为供应商想要进行单元测试!

我也不想担心一群受保护的成员,如果我从一个类继承,我不需要知道.

这就是我说它保留抽象和封装的原因.

在我的新工作中,他们不赞成使用朋友课,甚至进行单元测试.他们说因为班级不应该"知道"关于测试的任何内容,而且你不希望课程和测试的紧密耦合.

有人可以向我解释这些理由,以便我可以更好地理解吗?我只是不明白为什么使用朋友进行单元测试很糟糕.

bca*_*cat 58

理想情况下,您根本不需要对私有方法进行单元测试.你班上的所有消费者都应该关心的是公共界面,这就是你应该测试的.如果私有方法有bug,它应该被一个单元测试捕获,该单元测试调用类上的一些公共方法,最终最终调用bug的私有方法.如果一个错误设法漏掉了,这表明您的测试用例并不能完全反映您希望您的课程实现的合同.这个问题的解决方案几乎肯定是要对公共方法进行更严格的审查,而不是让你的测试用例深入到类的实现细节中.

同样,这是理想的情况.在现实世界中,事情可能并不总是如此清晰,并且让单元测试类成为它所测试的类的朋友可能是可接受的,甚至是可取的.不过,这可能不是你想要做的事情.如果它看起来经常出现,那可能表明你的课程太大和/或执行太多任务.如果是这样,通过将复杂的私有方法集重构为单独的类来进一步细分它们应该有助于消除单元测试需要了解实现细节.

  • 我不同意这一点.如果你正在实现一个复杂的算法,并希望在很小的步骤中打破它(在这个算法的实现之外这将是完全无用的),那么想要将这些步骤保持为私有实现细节而代码中没有其他对象是没有错的.应该能够访问,并且仍然希望检查每个子步骤在给定正确输入的情况下返回正确的结果. (15认同)
  • +1提示暗示私有方法通常可以在另一个类中公开. (10认同)
  • @Jean-MichaëlCelerier+1。您可能有一个很长的a **函数,该函数是公共的,也可以将其分解为逻辑部分。但是逻辑部分不必公开。但是,它们需要进行测试。例如 图像变形的变形。如果不使用库,那么如果不分成几部分,那将是很长的代码 (3认同)

Phi*_*ipp 12

您应该考虑有不同的样式和方法来测试:黑盒测试只测试公共接口(将类视为黑盒子).如果您有一个抽象基类,您甚至可以对所有实现使用相同的测试.

如果您使用白盒测试,您甚至可以查看实现的详细信息.不仅包括一个类有哪些私有方法,还包括哪种条件语句(即如果你想增加条件覆盖率,因为你知道条件很难编码).在白盒测试中,您肯定在类/实现和测试之间存在"高耦合",这是必要的,因为您要测试实现而不是接口.

正如bcat指出的那样,使用组合和更多但更小的类而不是许多私有方法通常是有帮助的.这简化了白盒测试,因为您可以更轻松地指定测试用例以获得良好的测试覆盖率.


Spa*_*ose 11

我觉得Bcat给出了一个非常好的答案,但我想阐述他所暗示的例外情况

在现实世界中,事情可能并不总是如此清晰,并且让单元测试类成为它所测试的类的朋友可能是可接受的,甚至是可取的.

我在一家拥有大量遗留代码库的公司工作,这有两个问题,这两个问题都有助于让朋友单元测试成为可取的.

  • 我们遇到了需要重构的大量功能和类,但是为了重构它有助于进行测试.
  • 我们的大部分代码都依赖于数据库访问,由于各种原因不应将其引入单元测试.

在某些情况下,Mocking对于缓解后一个问题非常有用,但是这通常只会导致复杂的设计(类别层次结构,否则不需要),而人们可以通过以下方式非常简单地重构代码:

class Foo{
public:
     some_db_accessing_method(){
         // some line(s) of code with db dependance.

         // a bunch of code which is the real meat of the function

         // maybe a little more db access.
     }
}
Run Code Online (Sandbox Code Playgroud)

现在我们遇到了函数的需要重构的情况,所以我们想要一个单元测试.它不应该公开曝光.现在,有一种很棒的技术,可以在这种情况下使用,但事实是,在这种情况下,模拟是过度的.这需要我通过不必要的层次结构来增加设计的复杂性.

更务实的方法是做这样的事情:

 class Foo{
 public: 
     some_db_accessing_method(){
         // db code as before
         unit_testable_meat(data_we_got_from_db);
         // maybe more db code.    
 }
 private:
     unit_testable_meat(...);
 }
Run Code Online (Sandbox Code Playgroud)

后者为我提供了单元测试所需的所有好处,包括为我提供了宝贵的安全网,以捕捉重构代码时产生的错误.为了对它进行单元测试,我必须与UnitTest类成为朋友,但我强烈认为这比其他无用的代码heirarchy要好得多,只允许我使用模拟.

我认为这应该成为一个成语,我认为这是一个合适的,务实的解决方案,以提高单元测试的投资回报率.


bjs*_*123 5

像bcat建议的那样,尽可能使用公共接口本身来查找错误.但是如果你想做一些事情,比如打印私有变量并与预期结果进行比较等(有助于开发人员轻松调试问题),那么你可以将UnitTest类作为要测试的类的朋友.但您可能需要在下面的宏下添加它.

class Myclass
{
#if defined(UNIT_TEST)
    friend class UnitTest;
#endif
};
Run Code Online (Sandbox Code Playgroud)

仅在需要进行单元测试时才启用标志UNIT_TEST.对于其他版本,您需要禁用此标志.


小智 5

在许多情况下,我认为使用朋友单元测试类没有任何问题。是的,将大类分解为小类有时是更好的方法。我认为人们对于这样的事情使用朋友关键字有点过于草率 - 这可能不是理想的面向对象设计,但如果我真的需要,我可以牺牲一点理想主义以获得更好的测试覆盖率。