繁体   English   中英

pImpl成语和可测试性

[英]The pImpl idiom and Testability

c ++中的pImpl惯用法旨在从该类的用户隐藏类的实现细节(=私有成员)。 但是它也隐藏了该类的一些依赖关系,从测试的角度来看,这通常被认为是不好的。

例如,如果类A隐藏其类AImpl中的实现细节,只能从A.cpp访问,并且AImpl依赖于许多其他类,则单元测试类A变得非常困难,因为测试框架无法访问AImpl也没办法将依赖注入AImpl。

有没有人遇到过这个问题? 你找到了解决方案吗?

- 编辑 -

在一个相关的主题上,似乎人们建议应该只测试由接口而不是内部公开的公共方法。 虽然我可以在概念上理解该语句,但我经常发现我需要单独测试私有方法。 例如,当公共方法调用包含一些非平凡逻辑的私有帮助器方法时。

为什么单元测试需要访问A实现的内部?

单元测试应该是测试A,因此应该只关心A的输入和输出。 如果在A的界面中(直接或间接)看不到某些东西,那么它实际上可能根本不需要成为Aimpl的一部分(因为它的结果对于外部世界是不可见的)。

如果Aimpl产生需要测试的副作用,那表明你应该看一下你的设计。

pimpl背后的想法不是隐藏类的实现细节,(私有成员已经这样做了),而是将实现细节移出标题。 问题是在C ++的包含模型中,更改私有方法/变量将强制重新编译包含此文件的任何文件。 这是一种痛苦,这就是为什么pimpl试图消除的原因。 它无助于防止对外部库的依赖。 其他技术也是如此。

您的单元测试不应该依赖于类的实现。 他们应该验证你的班级实际上是应该做的。 唯一真正重要的是物体如何与外界相互作用。 您的测试无法检测到的任何行为必须是对象的内部因素,因此无关紧要。

话虽如此,如果在类的内部实现中发现太多复杂性,您可能希望将该逻辑分解为单独的对象或函数。 基本上,如果您的内部行为过于复杂而无法间接测试,请将其作为另一个对象的外部行为并对其进行测试。

例如,假设我有一个类,它将一个字符串作为其构造函数的参数。 该字符串实际上是一个小的语言,它指定了对象的一些行为。 (字符串可能来自配置文件或其他东西)。 从理论上讲,我应该能够通过构造不同的对象和检查行为来测试该字符串的解析。 但如果迷你语言足够复杂,那将很难。 所以,我定义另一个函数,它接受字符串并返回上下文的表示(如关联数组或其他东西)。 然后我可以与主对象分开测试该解析功能。

如果您正在进行依赖注入,那么任何依赖类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;
};

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; }
};

然后针对真实类别编写单元测试。 尽可能多地测试它。 我故意称它为“真实的”而不是“impl”,以帮助确保它不会被误认为只是一个实现细节。

由于所有字段都是公共的,因此测试此类非常简单。 外部接口非常小,因为它是由另一个类定义的。 晶圆薄的转换层很难出错,但您仍然可以通过外部api进行测试。 这是从更明显地分离界面和实现的明显胜利。

在一个含糊不清的相关说明中,令我觉得荒谬的是,如此多的其他连贯的人主张跳过单元测试,以查找通过外部API无法轻易访问的任何内容。 最低级别的功能几乎不受程序员错误的影响。 验证api是否可用的测试对于验证实现细节是否正确非常重要且正交。

单元测试应该使实现类达到它的步伐。 一旦PIMPL类出现在图片中,您已进入“集成”领域 - 因此U / T不适用于此类。 PIMPL是关于隐藏实现的 - 你不应该知道实现的类设置。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM