您的位置:首页 > 编程语言 > C语言/C++

C++ UnitTest编写

2017-05-08 16:14 344 查看
最近看了很多大神的文章,加之对于最近工作以及职业生涯的一些思考,终于开始写自己的第一篇技术博客了,虽然做软件开发也是多年,但是似乎没有对自己之所学及所做进行过持续的总结和思考,所以开此博客权作对于自己的督促与总结。
C++已经不是一门新兴的语言了,但是本人由于工作的原因却是刚刚才开始使用其进行项目的开发,所以写下此篇总结自己前端UnitTest开发中的认识与感悟。
UT应该说是程序员的本职工作之一,无论是使用哪种语言,进行单元测试都是开发所必不可少的一项内容。那么UT主要目标是什么呢?就是完成单一函数的功能测试,也就是说要测试在不同的情况下函数的不同行为(分支执行是否正确, 是否给予正确的返回值),函数无非也是根据不同的情况对于数据进行不同的处理。那么对于本人来说, UnitTest也就是来验证函数是否被调用,以及返回值是否正确。
这里使用了GTEST以及GMOCK两个google的开源项目来进行单元测试。至于这两个开源项目的使用介绍网络上已经有大量的文章来说明,所以这里仅总结如何使用它们来解决以上的两个问题。


返回值及参数值验证

这里来看一个简单而又stupid的函数, 相信几乎没有人会在实际代码中使用这样的函数:

int foo(int i)
{
return i;
}


那么对于这个函数如何测试呢?这里就要用到GTEST的框架,当然我们也可以开发自己的框架来进行测试,不过GTEST则是方便、可靠的UT测试架构。

测试用例如下:

#include <gtest/gtest.h>
TEST(unitTest, fooTest)
{
int f_input = 1;
EXPECT_EQ(f_input, foo(f_input));
}


要使用GTEST, 首先要对其进行安装, 安装的方法可以参考:http://blog.csdn.net/pbe_sedm/article/details/42240885

这里不再赘述。

那么正确TestCase中对于GTEST的使用则是通过include头文件来实现的。

之后便是使用TEST来定义自己的测试case, 这里使用TEST宏来定义。通过查看TEST的定义可以看出:

# define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name)
#define GTEST_TEST(test_case_name, test_name)\
GTEST_TEST_(test_case_name, test_name, \
::testing::Test, ::testing::internal::GetTestTypeId())
#define GTEST_TEST_(test_case_name, test_name, parent_class, parent_id)\
class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {\
public:\
GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {}\
private:\
virtual void TestBody();\
static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;\
GTEST_DISALLOW_COPY_AND_ASSIGN_(\
GTEST_TEST_CLASS_NAME_(test_case_name, test_name));\
};\
#define GTEST_TEST_CLASS_NAME_(test_case_name, test_name) \
test_case_name##_##test_name##_Test


实际上, 通过TEST宏定义了一个名为test_case_name##_##test_name##_Test的类, 其包含了一个TestBody()的成员函数,其为一个虚函数, 而真实的实现即为testcase。

在testcase中, 主要是使用了EXPECT_EQ这个宏来判断预期得到的值与函数的返回值是否一致。EXPECT_EQ用来完成对于整型数的判断, 当然针对不同类型的数据有不同的宏可以使用。那么同样道理, 对于参数或成员变量的check,也可以参照以上方法来编写测试用例。

2. 函数调用的验证

那么如果目标测试函数中调用了其他的函数,而没有返回值, 也没有对于其他参数的修改,那么又怎么进行测试呢?

这里就需要引入GMOCK来写测试case,由于被调用的函数其实不在我们测试范围之内,所以完全没有必要其实现细节,一般只需验证其是否被调用, 至多需要控制其返回不同的值即可。GMOCK则完全可以满足上面的需要。

对于GMOCK的使用,安装和include的头文件自不用说,而我的理解GMOCK之所以可以完成对于函数调用的验证主要是运用了C++中父类对象指向子类对象,则其调用的成员函数为子类成员的特点,从而达到验证的目的。

首先针对C++代码的UnitTest主要目的也就是测试类的成员函数的工作,如下被调用为class A的成员函数:

class A
{
virtual int func_invoked();
};


而在待测函数中对于其调用如下:

class B
{
int func_to_test()
{
a->func_invoked();
return 0;
}
private:
A *a;
};


那么如何进行测试呢?首先需要构造一个A的派生类Gmock_A, 并使用gmock来实现其接口func_invoked:

class Gmock_A: public A
{
MOCK_METHOD0(func_invoked, int());
};


这里的宏MOCK_METHOD0用来定义派生类的一个实现,“0”表示定义的接口没有形参,其第一个参数为定义接口的名称,第二个用来定义函数的返回类型,而括号中用来指示形参的类型, 由于没有形参所以为空。这样就定义了一个可以使用mock的没有形参且返回值为int类型的接口函数。若需要定义有1~n个形参的函数则可之用MOCK_METHODn来定义。

下面就可以开始写TestCase了:

TEST(unitTest, testWithMock)
{
B b;
Gmock_A *gm_a = new Gmock_A();
b.a = gm_a;
EXPECT_CALL(gm_a, func_invoked()).WillOnce(Return(0));
b.func_to_test();
}


这里的宏EXPECT_CALL的第一个参数为被调用函数所在类的实例,也就是父类对象指向的子类对象;第二个则是被调用的函数, 后面的WillOnce来指示函数的行为, 这里使用Return方法来返回一个int类型的0。那么顾名思义,EXPECT_CALL就是告诉mock期望调用gm_a的func_invoked的函数,这里省略了Times(n)方法调用,n表示n次调用,省略则为默认一次调用。这样就完成了一个简单函数调用的验证。

以上两方面的总结仅为初学者的一点体会,希望对于刚刚开始使用GTEST和GMOCK的开发者有所帮助。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c语言 单元测试
相关文章推荐