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

2023-11-07

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 assertion Nonfatal assertion Verifies
ASSERT_TRUE(condition); EXPECT_TRUE(condition); condition is true
ASSERT_FALSE(condition); EXPECT_FALSE(condition); condition is false

2.2 二进制比较

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

2.3 字符串比较

字符串主要用于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

2.4 浮点数比较

浮点数比较,默认的是是指两者的差值在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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux下使用gtest对接口进行单元测试 的相关文章

  • 批量删除文件名中包含 BASH 中特殊字符的子字符串

    我的目录中有一个文件列表 opencv calib3d so2410 so opencv contrib so2410 so opencv core so2410 so opencv features2d so2410 so opencv
  • 在内核代码中查找函数的最佳方法[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我开始浏览内核代码 遇到的一件事是如何跟踪函数调用 结构定义等 有没有一种好的方法可以快速跳转到函数定义并退出 我尝试过 Source N
  • SSH,运行进程然后忽略输出

    我有一个命令可以使用 SSH 并在 SSH 后运行脚本 该脚本运行一个二进制文件 脚本完成后 我可以输入任意键 本地终端将恢复到正常状态 但是 由于该进程仍在我通过 SSH 连接的计算机中运行 因此任何时候它都会登录到stdout我在本地终
  • python获取上传/下载速度

    我想在我的计算机上监控上传和下载速度 一个名为 conky 的程序已经在 conky conf 中执行了以下操作 Connection quality alignr wireless link qual perc wlan0 downspe
  • 通过 Visual Studio 2017 使用远程调试时 Linux 控制台输出在哪里?

    我的Visual Studio 2017 VS2017 成功连接Linux系统 代码如下 include
  • 在centos中安装sqlite3 dev和其他包

    我正在尝试使用 cpanel 在 centos 机器上安装 sqlite dev 和其他库 以便能够编译应用程序 我对 debian 比 centos 更熟悉 我知道我需要的库是 libsqlite3 dev libkrb5 dev lib
  • tcpdump 是否受 iptables 过滤影响?

    如果我的开发机器有iptables规则到FORWARD一些数据包 这些数据包是否被 tcpdump 捕获 我有这个问题 因为我知道存在其他链称为INPUT如果数据包路由到 它会过滤发往应用程序的数据包FORWARD链 它会到达吗tcpdum
  • 就分页分段内存而言的程序寿命

    我对 x86 Linux 机器中的分段和分页过程有一个令人困惑的概念 如果有人能澄清从开始到结束所涉及的所有步骤 我们将很高兴 x86 使用分页分段内存技术进行内存管理 任何人都可以解释一下从可执行的 elf 格式文件从硬盘加载到主内存到它
  • 执行“minikube start”命令时出现问题

    malik malik minikube start minikube v1 12 0 on Ubuntu 18 04 Using the docker driver based on existing profile Starting c
  • Linux 上的 Pervasive ODBC 错误 [01000][unixODBC][驱动程序管理器]无法打开 lib '/usr/local/psql/lib/odbcci.so':找不到文件

    我正在尝试让 Pervasive v10 客户端 ODBC 在 Centos 6 上运行 据我所知 没有 64 位 ODBC 客户端 因此我必须使用 32 位客户端 我终于成功安装了它 但尝试使用时出现以下错误 isql v mydsn 0
  • 并行运行 shell 脚本

    我有一个 shell 脚本 打乱大型文本文件 600 万行和 6 列 根据第一列对文件进行排序 输出 1000 个文件 所以伪代码看起来像这样 file1 sh bin bash for i in seq 1 1000 do Generat
  • CMake 链接 glfw3 lib 错误

    我正在使用 CLion 并且正在使用 glfw3 库编写一个程序 http www glfw org docs latest http www glfw org docs latest 我安装并正确执行了库中的所有操作 我有 a 和 h 文
  • 与 pthread 的进程间互斥

    我想使用一个互斥体 它将用于同步对两个不同进程共享的内存中驻留的某些变量的访问 我怎样才能做到这一点 执行该操作的代码示例将非常感激 以下示例演示了 Pthread 进程间互斥体的创建 使用和销毁 将示例推广到多个进程作为读者的练习 inc
  • 在生产服务器上使用 Subversion 使文件生效的最佳方法是什么?

    目前我已经设置了 subversion 这样当我在 Eclipse PDT 中进行更改时 我可以提交更改 它们将保存在 home administrator 中项目文件 该文件具有 subversion 推荐的 branches tags
  • 绕过 dev/urandom|random 进行测试

    我想编写一个功能测试用例 用已知的随机数值来测试程序 我已经在单元测试期间用模拟对其进行了测试 但我也希望用于功能测试 当然不是全部 最简单的方法是什么 dev urandom仅覆盖一个进程 有没有办法做类似的事情chroot对于单个文件并
  • 如何使用Android获取Linux内核的版本?

    如何在 Android 应用程序中获取 Linux 内核的版本 不是 100 确定 但我认为调用 uname r 需要 root 访问权限 无论如何 有一种不太肮脏的方法可以做到这一点 那就是 System getProperty os v
  • 使用 gdb 调试 Linux 内核模块

    我想知道 API 在内核模块 中返回什么 从几种形式可以知道 这并不是那么简单 我们需要加载符号表来调试内核模块 所以我所做的就是 1 尝试找到内核模块的 text bss和 data段地址 2 在 gdb 中使用 add symbol f
  • 如何在 Mac OSX Mavericks 中正确运行字符串工具?

    如何在 Mac OSX Mavericks 中正确运行字符串工具 我尝试按照我在网上找到的示例来运行它 strings a UserParser class 但我收到此错误 错误 Applications Xcode app Content
  • 为什么同一个curl命令在windows和linux下输出不同的东西?

    为什么同样的curl o file https www link com 命令输出不同的东西 例如 如果我运行命令curl o source txt https www youtube com playlist list PLIx6Fwnp
  • 无法显示 Laravel 欢迎页面

    我的服务器位于 DigitalOcean 云上 我正在使用 Ubuntu 和 Apache Web 服务器 我的家用计算机运行的是 Windows 7 我使用 putty 作为终端 遵循所有指示https laracasts com ser

随机推荐