C++ 私有成员数据的模拟

C++ 私有成员数据的模拟,c++,unit-testing,C++,Unit Testing,我在寻找C++编译风格的最佳实践,以确保单元测试的容易性。问题来自于试图为私有数据成员实现一个模拟类。私有数据成员通过类中的几个不同方法进行访问。到目前为止,我所能找到的所有示例都展示了如何编写模拟类,但没有展示如何最好地编写同时使用真实对象和模拟对象的代码 在下面的示例中,我不确定如何将mCustom从类型MyOtherClass转换为mock MockMyOtherClass。我怀疑我的方法是错误的,这就是问题所在 class MyClass { MyOtherClass mCust


我在寻找C++编译风格的最佳实践,以确保单元测试的容易性。问题来自于试图为私有数据成员实现一个模拟类。私有数据成员通过类中的几个不同方法进行访问。到目前为止,我所能找到的所有示例都展示了如何编写模拟类,但没有展示如何最好地编写同时使用真实对象和模拟对象的代码

在下面的示例中,我不确定如何将mCustom从类型MyOtherClass转换为mock MockMyOtherClass。我怀疑我的方法是错误的,这就是问题所在

class MyClass {
    MyOtherClass mCustom;
};
[编辑]
我使用了一个编译器指令和一个新的构造函数

#ifdef UNIT_TESTING
 #include "mock.h"
#else
  #include "myotherclass.h"
#endif

class MyClass {
        MyOtherClass mCustom;
     public:
        MyClass(MyOtherClass pClass) : mCustom(pClass) {}
};

当我以前遇到这个问题时,我将
MyClass
分为两类:(1)不引用
MyOtherClass
的泛型类,以及(2)使用
MyOtherClass
实例化模板参数的实现类。定义的可见性并不完全相同,但我发现它对于任何实际目的都不重要


然后为了测试,我编写了类似于
MyTestOtherClass
的东西,并相应地实例化了
MyClass
。任何数量的模拟技术都可以应用于MyTestOtherClass,并且可以为不同的测试目的生成多个类。

您可以使用一些不同的策略(并根据需要进行混合)

  • 如果MyOtherClass提供了相当基本的功能,并且本身经过了良好的测试,那么我通常不会去嘲笑它。我只是确保在对整个项目运行单元测试时,MyOtherClass在MyClass之前进行测试

    class MyClass {
    private:
        MyOtherClass mCustom;
    public:
        //...
    };
    
  • 您可以模板化MyClass,在实际代码中提供MyOtherClass,并在测试代码中提供MyOtherClass的模拟版本

    template <class T>
    class MyClass_impl {
    private:
        T mCustom;
    public:
        //...
    };
    
    typedef MyClass_impl<MyOtherClass> MyClass; // convenience typedef
    
  • 您可以在标题的搜索路径上玩把戏。为此,您需要在单独的目录中创建MyOtherClass的模拟版本。然后,确保对于单元测试,首先在这个单独的目录中搜索头文件。这使得MyOtherClass的模拟版本首先被找到,并覆盖MyOtherClass的真实版本


  • 假设正在使用
    #ifndef
    头保护,一种不需要任何代码重新架构的黑客方法是利用它们,在包含实际头之前在测试代码中定义头保护宏

    例如:

    MyClass.hpp:

    #ifndef MY_CLASS_HPP
    #define MY_CLASS_HPP
    
    #include "MyOtherClass.hpp"
    
    class MyClass { MyOtherClass custom; };
    
    #endif
    
    #ifndef MY_OTHER_CLASS_HPP
    #define MY_OTHER_CLASS_HPP
    
    class MyOtherClass {};
    
    #endif
    
    MyOtherClass.hpp:

    #ifndef MY_CLASS_HPP
    #define MY_CLASS_HPP
    
    #include "MyOtherClass.hpp"
    
    class MyClass { MyOtherClass custom; };
    
    #endif
    
    #ifndef MY_OTHER_CLASS_HPP
    #define MY_OTHER_CLASS_HPP
    
    class MyOtherClass {};
    
    #endif
    
    TestCode.cpp:

    class MyOtherClass {
        // mocking
    };
    
    
    #define MY_OTHER_CLASS_HPP 
    // prevents the #include "MyOtherClass.hpp" in "MyClass.hpp" from doing anything
    // while the mock version is used
    
    #include "MyClass.hpp"
    
    // test code 
    

    如果您试图测试的代码位于.cpp文件中,您也可以在测试中包含.cpp文件(确保不要单独编译以避免重复定义),以便插入header guard宏定义。

    这些都是可行的解决方案。然而,如果你选择第4个,你将被其他开发者烧死——这可能就是为什么它在这个列表中最后一个:)就个人而言,我是方法1的粉丝,认为它是侵入性最小的。在我的例子中,“MyOtherClass”与一个设备驱动程序对话,所以我需要把它从循环中拿出来进行测试。所以选项1不是一个选项。其他的看起来很有趣。@Matt:Option 4实际上已经被我和其他开发者在一个商业项目中使用过了。不要冒险,只使用实用的解决方案。@Jim:选项2确实不允许有效地分离接口和类的实现。@Bartvaningeschenau好吧,这是真的:可以做到。它在某种程度上也很容易出错,这可能会花费人们大量的时间:在代码中没有任何提示的情况下交换类,并且您必须仔细考虑构建过程中的一行,以寻找不同的目录顺序-远离源代码,而您通常会将其视为开发人员。非常危险。但这是可以做到的。