Linux下使用gtest对接口进行单元测试

1. 背景

工程中涉及基础接口的设计,为了保证接口的质量,所以需要进行单元测试:

单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。

单元测试需要构建测试代码(开源软件中一般都提供了tests目录),测试代码需要制造测试数据,对case进行覆盖,并统计case的成功率。所以说测试框架的功能比较繁琐,一般使用第三方框架来实现代码的unit-test。

本节对google的开源框架进行实践,并对libevent源码进行一个ut的编写测试。
(gtest相关知识章节比较多,本节仅对最常使用的断言部分进行列举)

2. gtest 断言

GTest使用最多的就是断言了,与系统assert不同,到了断言部分并不会导致整个程序退出,并且能够统计相关信息。
GTest中断言都是成对出现的。即分为两个系列:

  1. ASSERT_*系列;
  2. EXPECT_*系列;

EXPECT_*系列是比较常用的。在一个测试Case中,如果局部测试使用了EXPECT_*系列函数,它将保证本次局部测试结果不会影响之后的流程。但是ASSERT_*系列在出错的情况下,当前测试Case中剩下的流程就不走了。

2.1 布尔值判断

Fatal assertionNonfatal assertionVerifies
ASSERT_TRUE(condition);EXPECT_TRUE(condition);condition is true
ASSERT_FALSE(condition);EXPECT_FALSE(condition);condition is false

2.2 二进制比较

Fatal assertionNonfatal assertionVerifiesNote
ASSERT_EQ(val1,val2);EXPECT_EQ(val1,val2);val1 == val2equal
ASSERT_NE(val1,val2);EXPECT_NE(val1,val2);val1 != val2not equal
ASSERT_LT(val1,val2);EXPECT_LT(val1,val2);val1 < val2less than
ASSERT_LE(val1,val2);EXPECT_LE(val1,val2);val1 <= val2less equal
ASSERT_GT(val1,val2);EXPECT_GT(val1,val2);val1 > val2greater than
ASSERT_GE(val1,val2);EXPECT_GE(val1,val2);val1 >= val2greater equal

2.3 字符串比较

字符串主要用于string、char * 类型:

Fatal assertionNonfatal assertionVerifiesNote
ASSERT_STREQ(str1,str2);EXPECT_STREQ(str1,_str_2);the two C strings have the same content stringequal
ASSERT_STRNE(str1,str2);EXPECT_STRNE(str1,str2);the two C strings have different content stringnot equal
ASSERT_STRCASEEQ(str1,str2);EXPECT_STRCASEEQ(str1,str2);the two C strings have the same content, ignoring casestring (ignoring) case equal
ASSERT_STRCASENE(str1,str2);EXPECT_STRCASENE(str1,str2);the two C strings have different content, ignoring casestring (ignoring) case not euqal

2.4 浮点数比较

浮点数比较,默认的是是指两者的差值在4ULP之内(Units in the Last Place)。

Fatal assertionNonfatal assertionVerifies
ASSERT_FLOAT_EQ(val1, val2);EXPECT_FLOAT_EQ(val1, val2);the two float values are almost equal
ASSERT_DOUBLE_EQ(val1, val2);EXPECT_DOUBLE_EQ(val1, val2);the two double values are almost equal

我们还可以自己制定精度:

Fatal assertionNonfatal assertionVerifies
ASSERT_NEAR(val1, val2, abs_error);EXPECT_NEAR(val1, val2, abs_error);the difference between val1 and val2 doesn’t exceed the given absolute error

3. 实践

本小节针对 libevent中 evbuffer接口进行一个单元测试,根据源码 test/regress_buffer.c 进行一个修改

3.1 框架使用

main函数比较简单,对gtest整体实例进行一个初始化:

int main(int argc, char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

在测试过程中,我们需要在每个Case中使用一个随机字符串,用于测试输入输出使用,
该字符串生命周期为一个Case,借助于gtest,我们可以在每个Case自带的实例,在构造函数进行完成我们的功能:

#include <gtest/gtest.h>
#include <openssl/rand.h>
class sdk_buffer :
    public testing::Test,
    public ::testing::WithParamInterface<int>
{
public:
    virtual void TearDown()
    {
    }

    sdk_buffer()
    {
        res = -1;
        RAND_bytes((u8 *)buffer, sizeof(buffer));
        evb_one = evbuffer_new();
        evb_two = evbuffer_new();
    };

    ~sdk_buffer()
    {
        evbuffer_free(evb_one);
        evbuffer_free(evb_two);
    }

protected:
    int res;
    char buffer[512];
    struct evbuffer *evb_one;
    struct evbuffer *evb_two;

};

可见构造函数分别对evb_oneevb_two进行空间申请、随机字符生成,析构函数对空间进行释放,
这样我们每个Case就不用再额外进行申请、释放代码的编写了,简化了编写Case的负担。

3.2 用例编写

然后来看我们一个case的编写,该Case主要对evbuffer_add_printf函数进行功能测试:

TEST_F(sdk_buffer, evbuffer_add_printf)
{
    evbuffer_add_printf(evb_one, "%s/%d", "hello", 1);
    evbuffer_validate(evb_one);

    ASSERT_EQ(evbuffer_get_length(evb_one), 7u);
    ASSERT_STREQ((char *)EVBUFFER_DATA(evb_one), "hello/1");

    evbuffer_add_buffer(evb_one, evb_two);
    evbuffer_validate(evb_one);

    evbuffer_drain(evb_one, strlen("hello/"));
    evbuffer_validate(evb_one);

    ASSERT_EQ(evbuffer_get_length(evb_one), 1u);
    ASSERT_STREQ((char *)EVBUFFER_DATA(evb_one), "1");

    evbuffer_add_printf(evb_two, "%s", "/hello");
    evbuffer_validate(evb_one);
    evbuffer_add_buffer(evb_one, evb_two);
    evbuffer_validate(evb_one);

    ASSERT_EQ(evbuffer_get_length(evb_two), 0u);
    ASSERT_EQ(evbuffer_get_length(evb_one), 7u);
    ASSERT_STREQ((char *)EVBUFFER_DATA(evb_one), "1/hello");
}

然后做一个evbuffer字符串追加删除的测试:

TEST_F(sdk_buffer, evbuffer_add_buffer)
{
    char *tmp = NULL;

    evbuffer_add(evb_one, buffer, sizeof(buffer));
    evbuffer_validate(evb_one);
    ASSERT_EQ(sizeof(buffer), evbuffer_get_length(evb_one));
    ASSERT_STREQ((char *)EVBUFFER_DATA(evb_one), buffer);

    char a[] = "Hello";
    char b[] = "Something";
    char c[] = "Else";

    evbuffer_prepend(evb_one, a, strlen(a) + 1);
    evbuffer_validate(evb_one);
    evbuffer_prepend(evb_one, b, strlen(b));
    evbuffer_validate(evb_one);
    evbuffer_prepend(evb_one, c, strlen(c));
    evbuffer_validate(evb_one);

    tmp = (char *)evbuffer_pullup(evb_one, 1 + strlen(a) + strlen(b) + strlen(c));
    ASSERT_STREQ((char *)EVBUFFER_DATA(evb_one), "ElseSomethingHello");
}

最后再来一个 evbuffer的批量数据移动的功能:

TEST_F(sdk_buffer, evbuffer_add_remove)
{
    int ix = 0;
    size_t sz_tmp = 0;

    evbuffer_validate(evb_one);
    evbuffer_validate(evb_two);

    for (ix = 0; ix < 3; ++ix) {
        evbuffer_add(evb_two, buffer, sizeof(buffer));
        evbuffer_validate(evb_two);
        evbuffer_add_buffer(evb_one, evb_two);
        evbuffer_validate(evb_one);
        evbuffer_validate(evb_two);
    }

    ASSERT_EQ(evbuffer_get_length(evb_two), 0u);
    ASSERT_EQ(evbuffer_get_length(evb_one), ix * sizeof(buffer));

    sz_tmp = (size_t)(sizeof(buffer) * 2.5);
    evbuffer_remove_buffer(evb_one, evb_two, sz_tmp);
    evbuffer_validate(evb_one);

    ASSERT_EQ(evbuffer_get_length(evb_two), sz_tmp);
    ASSERT_EQ(evbuffer_get_length(evb_one), sizeof(buffer) / 2);
}

3.3 编译运行

编译器注意需要使用g++编译器。

g++ -Wall -g2 -o ut_buffer2 \
                ut_buffer.cc \
                -I../include -DMLEVEL=5 -levent -lcrypto -lgtest -pthread

我们来看一下运行结果:

$ ./ut_buffer

[==========] Running 3 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 3 tests from sdk_buffer
[ RUN      ] sdk_buffer.evbuffer_add_printf
[       OK ] sdk_buffer.evbuffer_add_printf (0 ms)
[ RUN      ] sdk_buffer.evbuffer_add_buffer
[       OK ] sdk_buffer.evbuffer_add_buffer (0 ms)
[ RUN      ] sdk_buffer.evbuffer_add_remove
[       OK ] sdk_buffer.evbuffer_add_remove (1 ms)
[----------] 3 tests from sdk_buffer (1 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test case ran. (1 ms total)
[  PASSED  ] 3 tests.

如果我们希望只运行某个Case的时候,可以通过--gtest_filter=xxx进行控制

$ ./ut_buffer --gtest_filter=sdk_buffer.evbuffer_add_remove 

Note: Google Test filter = sdk_buffer.evbuffer_add_remove
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from sdk_buffer
[ RUN      ] sdk_buffer.evbuffer_add_remove
[       OK ] sdk_buffer.evbuffer_add_remove (1 ms)
[----------] 1 test from sdk_buffer (1 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (1 ms total)
[  PASSED  ] 1 test.

4.结论

使用 gtest 测试框架可以简化我们的测试工作,可以集中精力处理Case的编写设计,而不用去自己构建测试框架。
本节仅讨论gtest的入门用法,其他还有许多高级用法等待学习~!


参考文章:
[1]: gtest,https://blog.csdn.net/breaksoftware/article/details/51059406
[2]: 单元测试,https://www.liaoxuefeng.com/wiki/897692888725344/953546675792640

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值