cch*_*ion 54 c++ oop unit-testing encapsulation friend
在C++中,我经常将单元测试类作为我正在测试的类的朋友.我这样做是因为我有时觉得需要为私有方法编写单元测试,或者我想要访问某个私有成员,这样我就可以更轻松地设置对象的状态,以便我可以测试它.对我来说,这有助于保持封装和抽象,因为我没有修改类的公共接口或受保护的接口.
如果我购买第三方库,我不希望它的公共接口被一堆我不需要知道的公共方法污染,因为供应商想要进行单元测试!
我也不想担心一群受保护的成员,如果我从一个类继承,我不需要知道.
这就是我说它保留抽象和封装的原因.
在我的新工作中,他们不赞成使用朋友课,甚至进行单元测试.他们说因为班级不应该"知道"关于测试的任何内容,而且你不希望课程和测试的紧密耦合.
有人可以向我解释这些理由,以便我可以更好地理解吗?我只是不明白为什么使用朋友进行单元测试很糟糕.
bca*_*cat 58
理想情况下,您根本不需要对私有方法进行单元测试.你班上的所有消费者都应该关心的是公共界面,这就是你应该测试的.如果私有方法有bug,它应该被一个单元测试捕获,该单元测试调用类上的一些公共方法,最终最终调用bug的私有方法.如果一个错误设法漏掉了,这表明您的测试用例并不能完全反映您希望您的课程实现的合同.这个问题的解决方案几乎肯定是要对公共方法进行更严格的审查,而不是让你的测试用例深入到类的实现细节中.
同样,这是理想的情况.在现实世界中,事情可能并不总是如此清晰,并且让单元测试类成为它所测试的类的朋友可能是可接受的,甚至是可取的.不过,这可能不是你想要做的事情.如果它看起来经常出现,那可能表明你的课程太大和/或执行太多任务.如果是这样,通过将复杂的私有方法集重构为单独的类来进一步细分它们应该有助于消除单元测试需要了解实现细节.
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要好得多,只允许我使用模拟.
我认为这应该成为一个成语,我认为这是一个合适的,务实的解决方案,以提高单元测试的投资回报率.
像bcat建议的那样,尽可能使用公共接口本身来查找错误.但是如果你想做一些事情,比如打印私有变量并与预期结果进行比较等(有助于开发人员轻松调试问题),那么你可以将UnitTest类作为要测试的类的朋友.但您可能需要在下面的宏下添加它.
class Myclass
{
#if defined(UNIT_TEST)
friend class UnitTest;
#endif
};
Run Code Online (Sandbox Code Playgroud)
仅在需要进行单元测试时才启用标志UNIT_TEST.对于其他版本,您需要禁用此标志.
小智 5
在许多情况下,我认为使用朋友单元测试类没有任何问题。是的,将大类分解为小类有时是更好的方法。我认为人们对于这样的事情使用朋友关键字有点过于草率 - 这可能不是理想的面向对象设计,但如果我真的需要,我可以牺牲一点理想主义以获得更好的测试覆盖率。