导语 菜bpillar报名了他们学校的实训,听了老师对单元测试框架的讲解,觉得非常牛逼,打算写一篇博客整理整理。
测试程序 古话说得好,只要是程序,就有bug,因此,一个测试程序对于程序本身来说是很有必要的。虽然说通过测试不代表就没有bug,但至少说明程序是概率性正确的。咱就跟着实训来的例子来看看测试程序怎么写。
谷歌测试框架 谷歌有一个C++的测试框架gtest,我们在这个框架的基础上来看看。这是他的源码仓库:https://github.com/google/googletest 其中还有一个介绍怎么整一个自己的测试模块的。https://github.com/google/googletest/tree/master/googletest
步骤 gtest用cmake编译,所以如果你的电脑上没有cmake就去整一个 然后按照上面的教程,在clone下来的源码里执行
mkdir mybuild cd mybuild cmake ${GTEST_DIR}
这里的GTEST_DIR随便设。然后我们就会发现mybuild文件夹里出现了一些新的文件,其中包含Makefile
所以我们直接make+make install就可以了,我们可以发现很多的库文件和头文件被安装了。 然后就可以开始编写自己的测试模块了,这里我们就抄一下google自己的sample,测试下大于等于小于啥的 main.cpp:
#include <iostream> #include <cstdio> #include <cstdlib> #include <queue> #include <stack> #include <algorithm> #include <string> #include <map> #include <set> #include <vector> #include <mytest.h> using namespace std ;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 ), 7 ); EXPECT_LT(add(3 , 4 ), 8 ); EXPECT_GE(add(3 , 4 ), 7 ); EXPECT_LE(add(3 , 4 ), 7 ); } TEST(test, add2) { 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 () { return RUN_ALL_TESTS(); }
我们引入了一个mytest.h的头文件,里面定义了我们测试文件所需要用到的宏和函数等等东西。 mytest.h:
#ifndef _MYTEST_H #define _MYTEST_H #include <string.h> #define EXPECT(a, comp, b) { \ if (!((a) comp (b))) \ expect_printf(__FILE__, __LINE__, \ "((" #a ") " #comp " (" #b "))" ); \ } #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 COLOR(msg, code) "\033[" #code "m" msg "\033[0m" #define GREEN(msg) COLOR(msg, 32) #define YELLOW(msg) COLOR(msg, 33) #define BLUE(msg) COLOR(msg, 34) #define RED(msg) COLOR(msg, 35) #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 ; } void expect_printf(const char *file_name, int line_no, const char *msg) { printf (YELLOW("\t%s : %d : Failure\n" ), file_name, line_no); printf (YELLOW("\t\t%s\n" ), msg); return ; } int RUN_ALL_TESTS() { printf (GREEN("[==========] " ) "Running %d tests\n" , func_cnt); for (int i = 0 ; i < func_cnt; i++) { printf (GREEN("[ RUN ] " ) "%s\n" , func_arr[i].func_name); func_arr[i].func(); } return 0 ; } #endif
代码分析 首先我们看测试cpp里的这些内容
TEST(test, add) { EXPECT_EQ(add(3 , 4 ), 7 ); EXPECT_NE(add(3 , 4 ), 8 ); EXPECT_GT(add(3 , 4 ), 7 ); EXPECT_LT(add(3 , 4 ), 8 ); EXPECT_GE(add(3 , 4 ), 7 ); EXPECT_LE(add(3 , 4 ), 7 ); }
其中的TEST和EXPECT_EQ等等,明显可看出是宏,于是我们在mytest.h里看看他们的定义 首先是EXPECT系列的宏定义,这里我们还把宏给封装了一下。
#define EXPECT(a, comp, b) { \ if (!((a) comp (b))) \ expect_printf(__FILE__, __LINE__, \ "((" #a ") " #comp " (" #b "))" ); \ } #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)
宏的特点就是只替换,经过替换可以看出来他是判断条件是否成立,如果不成立就去调用expect_printf函数来输出错误,这里FILE LINE 都是编译器自带的变量。 于是我们来看看expect_printf函数
void expect_printf (const char *file_name, int line_no, const char *msg) { printf (YELLOW("\t%s : %d : Failure\n" ), file_name, line_no); printf (YELLOW("\t\t%s\n" ), msg); return ; }
就是把东西打印出去罢了,不过我们可以看到这里又有了宏YELLOW
#define COLOR(msg, code) "\033[" #code "m" msg "\033[0m" #define GREEN(msg) COLOR(msg, 32) #define YELLOW(msg) COLOR(msg, 33) #define BLUE(msg) COLOR(msg, 34) #define RED(msg) COLOR(msg, 35)
而COLOR的宏定是这样,就是shell变颜色高亮的输出语法。
TEST的宏定义:
#define TEST(a, b) \ void a##_##b(); \ __attribute__((constructor)) \ void register_##a##_##b() { \ add_test(a##_##b, #a "." #b); \ } \
这里定义了一个构造器函数register_a_b(),他调用add_test(a_b,”a.b”),其中a_b是函数指针,a.b是函数名。
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 ; }
而add_test函数就这,在这个函数上面几行我们定义了func_arr和func_cnt的全局变量,前者的类型是void指针+char数组,存储函数的函数指针和函数名,后者的类型是int,记录一共有几个函数。
最后就是我们在main.cpp中调用的函数
int RUN_ALL_TESTS () { printf (GREEN("[==========] " ) "Running %d tests\n" , func_cnt); for (int i = 0 ; i < func_cnt; i++) { printf (GREEN("[ RUN ] " ) "%s\n" , func_arr[i].func_name); func_arr[i].func(); } return 0 ; }
就是循环func_arr中存储的测试函数来测试。 这样,我们的测试就成了。
看看结果