当前位置:文档之家› 用gtest测试类的私有成员

用gtest测试类的私有成员

用gtest测试类的私有成员
使用gtest、或者cppunit之类的框架编写单元测试代码,一个最常见的问题是对类私有成员的测试与验证。

理想情况下,我们希望在测试中,类中所有的数据与方法都是可以访问的;而在产品代码中,只暴露实现定义好的接口。

gtest官方文档中,也提到了对私有成员的处理,方法不外乎两种:一是使用friend关键字,骗取信任得以通行;二是重构采用Pimpl模式,公共类中只暴露接口,而实现类中暴露所有细节(public),测试时包含实现类即可。

但这两个方法都试了一下,觉不太方便。

∙使用friend关键字
gtest提供了一个FRIEND_TEST的宏,用来将一个test声明为产品类的
友元。

其缺点是显而易见的。

一是需要往产品类中添加纯测试代码;二是每加一个test,需要在产品类中添加一项FRIEND_TEST。

∙重构采用Pimpl模式
我认可Pimpl模式,但同时我也不会对所有的类都这么做。

所以如果让我只是为了支持测试而做这样的重构,我可能并不情愿。

况且,手工重构很麻烦,而我手头也没有这么个自动化的工具。

(当然,我相信这是可以自动化的)
其实我们想要达到的目的无非是:在产品代码中,该pubic的是public,该private的还是private;而在测试代码中,全部都是public。

于是:
1 #ifdef GTEST
2 #define private public
3 #define protected public
4 #endif
将其放在每个类的声明前,或者放在一个单独的头文件如ForcePublic.h中并包含之。

这样,在编译测试代码时,加上GTEST的预编译宏,就可以非常方便的使用被测试类中的任何成员了,并不会对产品代码产生任何的影响。

如果你是以纯源代码的方式使用你的产品类的,这个方法没有任何问题;但如果你是通过静态库,或者动态库的方式使用你的产品类的,在测试代码中若直接使用这些库,编译是没有问题,因为全伪装成了public,但在链接的时候,因为private的成员是没有从库中导出来的,必然会出现链接错误。

此时有两个方案:一是以GTEST的方式重新编译库;二是直接将产品源代码编译进你的测试工程中去。

我使用的是动态库,选择了将代码编译进测试工程的方法,为了解决dllexport, dllimport相关的一些编译问题,还做了如下定义:
1 #ifdef TXNMGR_EXPORTS
2 #define TXNMGR_API __declspec(dllexport)
3 #else
4 #ifdef GTEST
5 #define TXNMGR_API
6 #else
7 #define TXNMGR_API __declspec(dllimport)
8 #endif
9 #endif
这样,在测试工程中,TXNMGR_API宏的定义为空,自然被忽略了,而不会影响
到产品代码。

感觉着这种方法的好处在于只要在一开始做一些小小的修改,便可以一劳永逸的解决访问所有产品类所有私有成员的问题;由于我们改变的只是成员的访问级别,对类的行为应该没有什么影响。

更新:
在gtest的google group中就这个问题提出了讨论,大家指出这种方式的问题在于:
∙使得访问私有成员过分容易,从而导致写出来的test访问私有成员的可能性增大。

测试访问了私有成员,也就是依赖于实现,这让单元测试成为
你重构的负担,而不是保证。

∙万战勇同学也指出,这种方法是不标准的C++用法: 一是C++标准不允许重定义关键词,所以这种方法即使此时在你当前的编译器上是可行的,你
也不能保证将来,或者在其他编译器上可行;二是public, protected
private等访问修饰符可能会影响对象成员的布局,这样当你的测试是直
接链接到产品代码时会有些问题。

所以,除非你对以上两点十分清楚并且可以接受,不然,还是使用官方的FRIEND_TEST要更好一些。

∙因为每一个需要访问私有成员的test都需要在产品代码上加上一项 FRIEND_TEST,这会让你思考:我真的需要访问私有成员吗?有没有不
用访问私有成员的方法?从而帮助形成更好的设计与测试∙因为每一个依赖于实现的test都显式的登记在案,这样当你的实现细节有所改变时,你知道会影响哪些test

∙如何使用gtest测试类的成员函数
∙这两天学习了一下gtest,仅仅是接触到了皮毛,有很多问题还是不怎么明白,不过还是要将自己的一点学习心得与大家分享一下。

∙刚开始学习gtest都是直接使用一些函数,但是在c++中我们最经常用到的是类,那么在单元测试的时候如何测这个类呢?
∙gtest的一个特点是它可以参数化,即产生任意类型的对象,包括基本类型和你的自定义类型。

基本类型很简单,对于自定义类型如何测试呢……∙今天在看关于gmock的一篇文章时突然想到了gtest参数化,之前没有想明白如何测试类。

于是自己就随手写了一个测试程序结果真的通过了。

∙在玩转gtest系列文章中有一篇就是介绍参数化,其中的一个例子类似于:∙class testMyMath:public testing::TestWithParam<MyMath> {
∙};

∙这个类是不需要实现的,当然我的意思是不要添加任何的成员变量和函数,因为你只是测试其他的类。

我的待测类是:
∙class MyMath
{
public:
MyMath(void);
MyMath(int x,int y);
int Add();
int Sub();
int Mul();
int Mod();
~MyMath(void);
private:
int m_x;
int m_y;
};
∙这个类用来实现简单的加减乘除而已。

∙关于参数化不多介绍,因为玩转gtest中介绍的已经蛮好了,何况我还是个菜鸟。

∙接下来:
∙TEST_P(testMyMath,test_Add)
{
MyMath my=GetParam();
EXPECT_EQ(3,my.Add());
}
∙TEST_P(testMyMath,test_Sub)
{
MyMath my=GetParam();
EXPECT_EQ(-1,my.Sub());
}
∙TEST_P(testMyMath,test_Mul)
{
MyMath my=GetParam();
EXPECT_EQ(2,my.Mul());
}
∙TEST_P(testMyMath,test_Mod)
{
MyMath my=GetParam();
EXPECT_EQ(0,my.Mod());
}
∙关于TEST_P这个宏也不介绍,只是要说一点第一个参数一定要是你写的那个用来产生测试类类型的那个类,这里必须是testMyMath,第二个参数的话就是自己起的测试用例的名字了,我感觉英文文档给颠倒了。

∙然后最后一步通过初始化添加测试数据:
∙INSTANTIATE_TEST_CASE_P(Test_MyMath,testMyMath,testing::Values( MyMath(2,1),MyMath(3,4),MyMath(2,2)));

∙ok,然后在运行就可以了。

∙。

相关主题