Rim*_*imo 17 c++ testing pimpl-idiom
c ++中的pImpl惯用法旨在从该类的用户隐藏类的实现细节(=私有成员).但是它也隐藏了该类的一些依赖关系,从测试的角度来看,这通常被认为是不好的.
例如,如果类A隐藏其类AImpl中的实现细节,只能从A.cpp访问,并且AImpl依赖于许多其他类,则单元测试类A变得非常困难,因为测试框架无法访问AImpl也没办法将依赖注入AImpl.
有没有人遇到过这个问题?你找到了解决方案吗?
- 编辑 -
在一个相关的主题上,似乎人们建议应该只测试由接口而不是内部公开的公共方法.虽然我可以在概念上理解该语句,但我经常发现我需要单独测试私有方法.例如,当公共方法调用包含一些非平凡逻辑的私有帮助器方法时.
Mar*_*k B 17
为什么单元测试需要访问A实现的内部?
单元测试应该是测试A,因此应该只关心A的输入和输出.如果在A的界面中(直接或间接)看不到某些东西,那么它实际上可能根本不需要成为Aimpl的一部分(因为它的结果对于外部世界是不可见的).
如果Aimpl产生需要测试的副作用,则表明您应该查看您的设计.
Win*_*ert 16
pimpl背后的想法不是隐藏类的实现细节,(私有成员已经这样做了),而是将实现细节移出标题.问题是在C++的包含模型中,更改私有方法/变量将强制重新编译包含此文件的任何文件.这是一种痛苦,这就是为什么pimpl试图消除的原因.它无助于防止对外部库的依赖.其他技术也是如此.
您的单元测试不应该依赖于类的实现.他们应该验证你的班级实际上是应该做的.唯一真正重要的是物体如何与外界相互作用.您的测试无法检测到的任何行为必须是对象的内部因素,因此无关紧要.
话虽如此,如果在类的内部实现中发现太多复杂性,您可能希望将该逻辑分解为单独的对象或函数.基本上,如果您的内部行为过于复杂而无法间接测试,请将其作为另一个对象的外部行为并对其进行测试.
例如,假设我有一个类,它将一个字符串作为其构造函数的参数.该字符串实际上是一个小的语言,它指定了对象的一些行为.(字符串可能来自配置文件或其他东西).从理论上讲,我应该能够通过构造不同的对象和检查行为来测试该字符串的解析.但如果迷你语言足够复杂,那将很难.所以,我定义另一个函数,它接受字符串并返回上下文的表示(如关联数组或其他东西).然后我可以与主对象分开测试该解析功能.
tza*_*man 11
如果您正在进行依赖注入,那么任何依赖类A都应该通过其公共接口传入 - 如果您的pImpl由于依赖性而干扰您的测试,那么您似乎并没有注入这些依赖项.
单元测试应该只关注A类暴露的公共接口; A 内部的依赖关系不是你的问题.只要正确注入所有内容,您就应该能够传入模拟而无需担心A的内部实现.从某种意义上说,你可以说可测试性和适当的pImpl是相辅相成的,因为不可测试的实现隐藏了不应隐藏的细节.
pImpl 习惯用法使测试变得容易得多。看到一组以“不要测试实现”为主题的答案来激发在 OP 之后这么长时间的回答,这真是太奇怪了。
通常,基于非 pimpl 的 C++ 有一个包含公共和私有字段的类。公共字段很容易测试,私有字段有点乏味。公共和私有之间的划分很重要,因为它减少了 api 的宽度并且通常使以后的更改更容易。
使用这个习语时,有一个更好的选择。您可以拥有与单个类完全相同的“公共”接口,但现在只有一个私有字段包含某种指针,例如
class my_things
{
public:
my_things();
~my_things();
void do_something_important(int);
int also_this();
private:
struct my_things_real;
std::unique_ptr<my_things_real> state;
};
Run Code Online (Sandbox Code Playgroud)
my_things_real 类应该在与外部可见类的析构函数相同的源文件中可见,但不在头文件中。它不是公共接口的一部分,因此所有字段都可以是公共的。
void my_things::do_something_important(int x) { state->doit(x); } // etc
class my_things_real // I'd probably write 'struct'
{
public:
int value;
void doit(int x) { value = x; }
int getit() { return value; }
};
Run Code Online (Sandbox Code Playgroud)
然后针对真实的类编写单元测试。尽可能多地或尽可能少地测试它。我特意将其称为“真实”而不是“impl”,以帮助确保它不会被误认为仅仅是实现细节。
由于所有字段都是公开的,因此测试此类非常容易。外部接口非常小,因为它是由另一个类定义的。极薄的翻译层很难出错,但仍然欢迎您通过外部 api 进行测试。从更显着地分离接口和实现来说,这是一个明显的胜利。
在一个模糊相关的注释中,让我觉得很荒谬的是,如此多的其他连贯的人主张跳过任何无法通过外部 API 轻松访问的内容的单元测试。最底层的函数几乎不受程序员错误的影响。验证 api 可用的测试对于验证实现细节是否正确既重要又正交。