回答这个问题涉及许多其他主题。除了 CleanCode、TDD 和其他方面的宗教信仰之外:
有几种方法可以访问私有成员。在任何情况下,您都必须否决经过测试的代码!这在解析 C++ 的两个级别(预处理器和语言本身)上都是可能的:
全部定义为公开
通过使用预处理器,您可以打破封装。
#define private public
#define protected public
#define class struct
缺点是,交付代码的类与测试中的不一样!
第 9.2.13 章中的 C++ 标准说:
非静态数据成员的分配顺序不同
未指定访问控制。
这意味着,编译器有权为测试重新排序成员变量和虚函数。如果没有发生缓冲区溢出,您可能会感到困惑,这不会损害您的类,但这意味着您不会在交付时测试相同的代码。这意味着,如果您访问由代码初始化、使用 private 编译但未定义为 public 的对象的成员,则您的成员的偏移量可能会有所不同!
朋友
此方法需要更改被测类以使其与测试类或测试函数成为朋友。一些测试框架,如 gtest (FRIEND_TEST(..);) 具有特殊功能来支持这种访问私有事物的方式。
class X
{
private:
friend class Test_X;
};
它只为测试打开类而不打开世界,但您必须修改交付的代码。在我看来这是一件坏事,因为测试不应该改变被测试的代码。作为另一个缺点,它使交付代码的其他类有可能通过将自己命名为测试类来侵入您的类(这也会损害 C++ 标准的 ODR 规则)。
声明受保护的私有事物并从该类派生以进行测试
不是一种非常优雅的方式,非常侵入性,但也可以:
class X
{
protected:
int myPrivate;
};
class Test_X: public X
{
// Now you can access the myPrivate member.
};
使用宏的任何其他方式
有效,但与第一种方式一样,在标准一致性方面具有相同的缺点。例如:
class X
{
#ifndef UNITTEST
private:
#endif
};
我认为最后两种方式都不能替代前两种方式,因为它们与第一种方式相比没有优势,但对测试代码的侵入性更大。第一种方式风险很大,可以使用交友方式。
关于从不测试私人事物讨论的一些话。单元测试的好处之一是,你会很早就达到你必须改进代码设计的地步。这有时也是单元测试的缺点之一。它使面向对象有时比它必须的更复杂。尤其是如果您按照与现实世界对象相同的方式设计类的规则。
然后您有时必须将代码更改为丑陋的东西,因为单元测试方法迫使您这样做。处理用于控制物理过程的复杂框架就是一个例子。在那里,您想将代码映射到物理过程,因为过程的某些部分通常已经非常复杂。该进程的依赖关系列表有时会变得很长。这是一个可能的时刻,测试私人成员变得越来越好。您必须权衡每种方法的优缺点。
类有时会变得复杂!然后你必须决定分开它们还是照原样接受它们。有时第二个决定更有意义。归根结底,您想要实现的目标始终是一个问题(例如,完美的设计、快速的合并时间、低开发成本……)。
我的意见
我访问私人成员的决策过程如下所示:
- 您需要自己测试私有成员吗? (这通常会减少所需的测试总数)
- 如果是,您认为重构类有什么设计优势吗?
- 如果没有,请与您班上的测试成为朋友(使用此测试,因为缺少替代方案)。
我不喜欢交友方法,因为它会更改已测试的代码,但测试某些东西的风险可能与交付的不一样(与第一种方法一样),并不能证明更简洁的代码是合理的。
顺便说一句:只测试公共接口也是一个流畅的事情,因为根据我的经验,它的变化与私有实现一样频繁。所以你没有优势减少对公共成员的测试。