在Qt中是否可以对单元测试(访问)私有方法进行单元测试?

Is it possible in Qt to unit test (access) private methods?

我正在为我的应用程序编写单元测试,现在我偶然发现了一个我应该测试私有方法的类。 这可能是特定班级设计不佳的结果,但我必须这样做。 有没有办法在Qt中调用私有方法,可能使用QMetaObject或类似的东西?

对于单元测试,我使用的是QTestLib框架。


正确的(读令人讨厌的)答案是你不应该测试私有方法,那些是实现细节;-)

OTOH - 您是否考虑过有条件地声明它们是受保护的/私有的,这取决于您是在测试还是没有然后扩展?我过去常常用它来解决类似的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifdef TESTING
// or maybe even public!
#define ACCESS protected
#else
#define ACCESS private
#endif

/* your class here */
class Foo {
ACCESS
    int fooeyness;
}

// or better yet, place this in a different file!
#ifdef TESTING
/*
    class which extends the tested class which has public accessors
*/
#endif


解决方案1,快速简便:使测试类成为公众的朋友。

1
2
3
4
5
class Foo {
   // ...
private:
   friend class FooTest;
};

这样,您的FooTest类可以访问公共类的所有成员。但是,这样您每次要从不同的测试中访问私有数据时都需要修改原始类,并且在公共API中泄漏有关测试的信息,并且您可能会打开类命名冲突(如果有/另一个/ FooTest类?),等等。

解决方案2,也称为正确完成:不要将私有方法放在公共类中,而是使用公共方法创建私有类。

1
2
3
4
5
6
class Foo {
    //
private:
    friend class FooPrivate;
    FooPrivate *d;
};

FooPrivate在其自己的标头中声明,该标头可能未安装,或保留在include-privates /子目录或其他任何内容中 - 即它不会被正常使用。公共班级保持干净。

1
2
3
4
5
6
class FooPrivate {
public:
    // only public stuff in here;
    // and especially this:
    static FooPrivate *get(Foo *f) { return f->d; }
};

然后测试包括私有头,并调用FooPrivate::get(fooObj)来获取私有类实例,然后愉快地使用它。


我不同意"私有成员是实现细节"的心态,在我看来,大致翻译为"仅测试代码的一部分"。

我可以参考"单位是单位"的论点,但为什么不尝试用尽可能多的代码覆盖你的代码,甚至内部单位呢?阿卡。给你的单位一个彻底的直肠检查。

考虑到这种形象,我经常使用的一种方法是在其他答案中没有提到的是执行以下操作:

  • 始终在代码中声明要测试的成员为protected而不是private
  • 在测试代??码中对您的类进行子类化,只需创建所需的选择器,或者直接将测试代码直接编写为该子类实现中的成员。
  • 注意:您必须小心依赖复杂实例化模式的类,以确保正确构造它们(读取:从子类ctor调用原始类的构造函数)。


    我曾经读过Unit Test应该测试类的公共接口,而不是受保护/私有的东西。

    你的班级应该从外面表现得很好。如果实施策略发生变化,您的Unit Test类仍然是相同的。


    我找到了更方便的方法来做到这一点。首先,所有私有方法都应该是私有槽。
    然后,您创建该类的实例:

    1
    Foo a;

    然后我们可以使用QMetaObject :: invokeMethod来调用方法具有的任何插槽(公共或私有)。因此,如果我们想调用方法Test,我们可以这样做:

    1
    QMetaObject::invokeMethod(&a,"Test", Qt::DirectConnection);

    此外,我们可以有返回值并发送参数......实际上,一切都在这里得到解答:http://doc.qt.nokia.com/stable/qmetaobject.html#invokeMethod