C++ UnitTest编写

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

0 0
原创粉丝点击