工程中涉及基础接口的设计,为了保证接口的质量,所以需要进行单元测试:
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
单元测试需要构建测试代码(开源软件中一般都提供了tests目录),测试代码需要制造测试数据,对case进行覆盖,并统计case的成功率。所以说测试框架的功能比较繁琐,一般使用第三方框架来实现代码的unit-test。
本节对google的开源框架进行实践,并对libevent源码进行一个ut的编写测试。
(gtest相关知识章节比较多,本节仅对最常使用的断言部分进行列举)
GTest使用最多的就是断言了,与系统assert不同,到了断言部分并不会导致整个程序退出,并且能够统计相关信息。
GTest中断言都是成对出现的。即分为两个系列:
EXPECT_*系列是比较常用的。在一个测试Case中,如果局部测试使用了EXPECT_*系列函数,它将保证本次局部测试结果不会影响之后的流程。但是ASSERT_*系列在出错的情况下,当前测试Case中剩下的流程就不走了。
Fatal assertion | Nonfatal assertion | Verifies |
---|---|---|
ASSERT_TRUE(condition); | EXPECT_TRUE(condition); | condition is true |
ASSERT_FALSE(condition); | EXPECT_FALSE(condition); | condition is false |
Fatal assertion | Nonfatal assertion | Verifies | Note |
---|---|---|---|
ASSERT_EQ(val1,val2); | EXPECT_EQ(val1,val2); | val1 == val2 | equal |
ASSERT_NE(val1,val2); | EXPECT_NE(val1,val2); | val1 != val2 | not equal |
ASSERT_LT(val1,val2); | EXPECT_LT(val1,val2); | val1 < val2 | less than |
ASSERT_LE(val1,val2); | EXPECT_LE(val1,val2); | val1 <= val2 | less equal |
ASSERT_GT(val1,val2); | EXPECT_GT(val1,val2); | val1 > val2 | greater than |
ASSERT_GE(val1,val2); | EXPECT_GE(val1,val2); | val1 >= val2 | greater equal |
字符串主要用于string、char * 类型:
Fatal assertion | Nonfatal assertion | Verifies | Note |
---|---|---|---|
ASSERT_STREQ(str1,str2); | EXPECT_STREQ(str1,_str_2); | the two C strings have the same content string | equal |
ASSERT_STRNE(str1,str2); | EXPECT_STRNE(str1,str2); | the two C strings have different content string | not equal |
ASSERT_STRCASEEQ(str1,str2); | EXPECT_STRCASEEQ(str1,str2); | the two C strings have the same content, ignoring case | string (ignoring) case equal |
ASSERT_STRCASENE(str1,str2); | EXPECT_STRCASENE(str1,str2); | the two C strings have different content, ignoring case | string (ignoring) case not euqal |
浮点数比较,默认的是是指两者的差值在4ULP之内(Units in the Last Place)。
Fatal assertion | Nonfatal assertion | Verifies |
---|---|---|
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 assertion | Nonfatal assertion | Verifies |
---|---|---|
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 |
本小节针对 libevent中 evbuffer接口进行一个单元测试,根据源码 test/regress_buffer.c
进行一个修改
main函数比较简单,对gtest整体实例进行一个初始化:
int main(int argc, char *argv[])
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
在测试过程中,我们需要在每个Case中使用一个随机字符串,用于测试输入输出使用,
该字符串生命周期为一个Case,借助于gtest,我们可以在每个Case自带的实例,在构造函数进行完成我们的功能:
#include
#include
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_one
、evb_two
进行空间申请、随机字符生成,析构函数对空间进行释放,
这样我们每个Case就不用再额外进行申请、释放代码的编写了,简化了编写Case的负担。
然后来看我们一个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);
}
编译器注意需要使用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.
使用 gtest 测试框架可以简化我们的测试工作,可以集中精力处理Case的编写设计,而不用去自己构建测试框架。
本节仅讨论gtest的入门用法,其他还有许多高级用法等待学习~!
参考文章:
[1]: gtest,https://blog.csdn.net/breaksoftware/article/details/51059406
[2]: 单元测试,https://www.liaoxuefeng.com/wiki/897692888725344/953546675792640