单元测试框架的开发

单元测试框架的开发

对测试的理解

  • 通过测试 = 概率性正确 != 没有BUG
  • 要注意编码规范,比如某些标准库的禁止使用、C++中的避免直接使用using namespace std .

认识Google Test框架

下载

Github地址:Google Test

本例使用Ubuntu 18.04环境,执行命令

git clone https://github.com/google/googletest

编译GoogleTest

首先,新建一个文件夹名为/build 并进入该目录。

mkdir build
cd build

执行命令cmake ../,cmake利用CMakeLists.txt中的规则生成Makefile文件,当前build目录下将会产生构建测试框架的内容文件,包括Makefile文件。接着直接执行make命令即可对googletest项目进行编译。若cmake阶段报错,首先需要确保安装了cmake。

安装cmake,然后输入root密码(Linux输入密码默认不回显,输完后直接回车即可)

sudo apt  install cmake

检验是否安装成功

cmake --version

然后执行刚刚的步骤(cmake …/ 以及 make)即可。若cmake仍然有问题,可以在googletest/CMakeLists.txt中添加一行语句:add_definitions(-std=c++11),使其执行C++11的标准即可。

安装成功:

测试用例

要使用gtest来进行测试,需要先将googletest目录下的/include/lib目录拷贝到自己的项目中,因为它们分别包含了gtest的声明和定义文件。

cp googletest/build/lib/* lib/
cp -R googletest/googletest/include/* include/

使用如下测试用例:

#include <gtest/gtest.h>
#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

TEST(test,add) {
    EXPECT_EQ(add(3,4),7);
    EXPECT_NE(add(3,4),8);
    EXPECT_GT(add(3,4),6);
    EXPECT_LT(add(3,4),8);
    EXPECT_GE(add(3,4),7);
    EXPECT_LE(add(3,4),7);
}

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

编译测试项目

g++ -I./include -L./lib test.cpp -lgtest
  • -I 用来增加头文件的检索路径
  • -L 用来增加库文件的检索文件
  • -lgtest 链接库,实际查找libgtest.a,位于lib目录下

如果在编译时出错,可以尝试这条命令:

g++ -std=c++11 test.cpp -I./include -L./lib -lgtest -lpthread
  • -std=c++11 使用c++11标准编译
  • -lpthread 加上线程库

GoogleTest效果展示

疑问

  • TEST是如何进行测试的?
  • RUN_ALL_TESTS()是如何知道我们定义了哪些TEST的呢?

模仿gtest开发一个测试框架

基本结构

  • 使用宏定义手段实现单元测试,首先根据流程,初步搭建一个测试框架
/*mytest.h*/
#ifndef _MYTEST_H
#define _MYTEST_H

#define EXPECT(a, comp, b) { \
    if (!((a) comp (b))) printf("error\n"); \
}
//减少代码冗余

#define EXPECT_EQ(a, b) EXPECT(a, ==, b)
#define EXPECT_NE(a, b) EXPECT(a, !=, b)
#define EXPECT_GT(a, b) EXPECT(a, >, b)
#define EXPECT_GE(a, b) EXPECT(a, >=, b)
#define EXPECT_LT(a, b) EXPECT(a, <, b)
#define EXPECT_LE(a, b) EXPECT(a, <=, b)

#define TEST(a, b) void a##_##b()

int RUN_ALL_TESTS() {
    // to do something
    return 0;
}

#endif

一个小技巧

  • 设置函数属性,让其成为构造函数,先于主函数运行.

    __attribute__((constructor))
    //放在目标函数定义之前
    

RUN_ALL_TESTS()功能实现

  • 利用上述__attribute__关键字技巧,使得TEST宏不仅能够扩充为对应的函数名,还能扩充为一整个功能函数,attribute使其能够在主函数之前运行后将结果存到某一存储区,那么RUN_ALL_TEST就可以遍历该存储区来对所有的测试用例进行测试了。
#define TEST(a, b) \
void a##_##b(); \
__attribute__((constructor)) \
void register_##a##_##b() { \
    add_test(a##_##b, #a "." #b); \
} \
void a##_##b()
//扩展为功能函数,不要忘了函数声明。该函数将测试结果存到存储区中。

struct TestData {
    void (*func)();
    char *func_name;
} func_arr[100];
int func_cnt = 0;
//定义存储区的格式,工程中不建议与宏定义和声明放在同一文件中

void add_test(void (*func)(), const char *func_name) {
    func_arr[func_cnt].func = func;
    func_arr[func_cnt].func_name = strdup(func_name);
    func_cnt += 1;
    return ;
}
//将test添加到存储区

int RUN_ALL_TESTS() {
    for (int i = 0; i < func_cnt; i++) {
        printf("[    RUN  ] %s\n", func_arr[i].func_name);
        func_arr[i].func();
    }
    return 0;
}
//批量遍历test输出结果(简易版本)

初步效果

使用如下的测试文件:

初步的mytest.h实现:

编译后的运行效果:

由上图可见,已经展示出了初步的效果。

测试框架进阶美化

改变部分字体颜色

gnu规则下改变字体颜色方法

终端的字符颜色是用转义序列控制的,是文本模式下的系统显示功能,和具体的语言无关。

转义序列是以ESC开头,即用\033 来完成(ESC的ASCII码用十进制表示是27,用八进制表示就是033)。

书写格式

开头部分:\033[显示方式;前景色;背景色m + 结尾部分:\033[0m

注意:开头部分的三个参数:显示方式,前景色,背景色是可选参数,可以只写其中的某一个;另外由于表示三个参数不同含义的数值都是唯一的没有重复的,所以三个参数的书写先后顺序没有固定要求,系统都能识别;但是,建议按照默认的格式规范书写。

对于结尾部分,其实也可以省略,但是为了书写规范,建议\033[***开头,\033[0m结尾

数值表示的参数含义:

显示方式: 0(默认值)、1(高亮)、22(非粗体)、4(下划线)、24(非下划线)、 5(闪烁)、25(非闪烁)、7(反显)、27(非反显)前景色: 30(黑色)、31(红色)、32(绿色)、 33(黄色)、34(蓝色)、35(洋 红)、36(青色)、37(白色)背景色: 40(黑色)、41(红色)、42(绿色)、 43(黄色)、44(蓝色)、45(洋 红)、46(青色)、47(白色)

常见开头格式

\033[0m        默认字体正常显示,不高亮

\033[31;0m     红色字体正常显示

\033[1;32;40m  显示方式: 高亮    字体前景色:绿色  背景色:黑色

\033[0;31;46m  显示方式: 正常    字体前景色:红色  背景色:青色

在该例中,我们要将[=========]改为绿色,只需要这样修改:

printf("\033[1;32m[=========]\033[0m running %d test cases\n", func_cnt);
//局部改变颜色都以\033[0m结束,只有[ , 没有 ].

但是,反复如此书写代码较为繁琐,所以可以使用宏定义来改进:

对需要改变颜色的内容,只需将其作为msg参数传入即可:

效果如下:

提示错误信息

将测试不通过的文件、行号输出在屏幕上,并且输出预期的值。

新引入一个expect_printf函数,将FILE和LINE的宏作为参数传入,下面是expect_printf的定义:

输出总结语句

  • 若测试运行完毕后没有发生错误,则输出OK,否则输出Failed,并且输出执行时间。
  • 只需设置一个全局标志err_exist,初始化为0,然后每次调用expect_printf函数(即发生错误时),将err_exist设置为1;
  • 在RUN_ALL_TESTS中,每一次遍历测试函数时都将err_exist初始化为0,执行完测试函数后,根据err_exist的值作为条件输出不同的总结语句。
  • 记录测试函数运行的时间,用到了ctime,需要include一下,然后定义两个clock_t类型的变量startTime和endTime,运行时调用clock()函数,将返回值作为开始时间赋给startTime,结束时间同理。
  • RUN_ALL_TESTS完整代码实现如下:

最终效果展示

一个简单又美观的单元测试框架就完成了!是不是比GoogleTest还要美观一些呢?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值