C++船长免费课程 Google测试框架实现

一;预备知识
1、
cout的本质;cout直接输出对象虚重载左移运算符,返回值和传参为左值引用和const引用,友元函数才能访问对象
代码规范其实是不允许直接using namespace的应该具体using要操作的对象
在这里插入图片描述
在这里插入图片描述
2、
1)四种编程方式实现加法 面向过程 面向对象 泛型编程 函数式编程
C++的语言特性很多,那么学习这些语言特性的时候要对应上这些语言特性用在了那个编程方式对于学习C++是很有帮助的
在这里插入图片描述

#include <iostream>
#include <cstdio>

using namespace std; 

//面向过程实现
int add1(int a, int b)
{
    
    
	return a+b;
}

//面向对象实现,将加法封装到对象的()运算符方法中
class ADD
{
    
    
public:
	int operator()(int a, int b)
	{
    
    
		return a+b;
	}
};
//模板泛型编程
//C++11 才有的 auto为占位符 decltype推导类型
//auto add3(T a, U b) -> decltype(a + b) 表示返回值类型decltype(a + b) 这个推导 auto占位
//C++14才有返回类型依赖于模板形参 https://zh.cppreference.com/w/cpp/language/decltype
template<typename T, typename U>
auto add3(T a, U b) -> decltype(a + b)
{
    
    
	return a+b;
}

//函数式编程 暂未了解
auto add4 = [](int a, int b) -> int 
{
    
    
	return a+b;
};


int main()
{
    
    
	ADD add2;
	
	cout << add1(1,2) << endl;
	cout << add2(1,2) << endl;
	cout << add3(1,2) << endl;
	cout << add4(1,2) << endl;
	
	return 0;
}

2)编译、链接的相关问题
一个程序到最后执行是需要通过编译生成目标文件.o 链接生成可执行程序.aut
编译(主要检查语法错误,函数或变量的声明就是编译时期使用的,声明与运行时期无关) 编译时期的错误都是语法错误;编译命令使用-c只编译不链接生产目标.o文件,可以用命令nm -C main.o查看 目标文件.o文件是一些定义
链接(将定义的东西进行内存链接生成可执行程序 链接时的报错形式一般都不同于编译错误,一般是符号未定义(有声明但是链接去找定义的时候没有) 符号重定义(有声明但是链接去找定义的时候有重复,产生歧义ambiguity))
定义作用到链接阶段 声明作用到编译阶段
在这里插入图片描述
在这里插入图片描述
3、Google框架下载测试
静态库.a的使用
cmake,makefile的相关知识
这里讲解国cmake相关
头文件用于编译时期,静态链接库用于链接时期
-I -大写i 指定include包含路径
-L - 大写l 指定库的路径
-l - 大写l 指定链接哪个库
例如;

//注意链接库的名字是gtest.a     -l后直接库名 
//makefile格式
all:
	g++ -I./include -Llib mian.cpp -lgtest -std=c++11

4、printf的显示信息颜色设置,属性代码的方式设置;以\033开头 以m结尾,属性代码放中间 用;分隔
\033[参数;参数m打印信息

颜色设置的一个工程规范,零值属性设置,要屏蔽之前属性的设置。
一个思想;在使用前归零 使用完后归零,保证前后不干扰
在这里插入图片描述

5、预处理;预处理命令家族;以#为开头,
1)如#include包含有文件
2)#define宏定义 做的事情就是简单替换 作用在预处理阶段 生成待编译源码 -E 查看
#define宏定义 做的事情就是简单替换 作用在预处理阶段 生成待编译源码 -E 查看
S(a,b) a*b
S(int, p)&i;
\ 连接符 宏的定义只占用一行代码
预处理命令的意义就是让编译器帮我们写代码,都是在编译之前的处理
内置宏 就是我们无法实现的,需要编译器内置我们使用
变参函数 可以传递任易个参数 变参列表
##两个内容连接在一起,变参列表可以为空

条件式编译主要是用于代码剪裁
#ifdef
#ifndef

#if
#elif
#else
#endif 结束条件宏后使用

在头文件中一般防止重复定义,一般这样使用

#ifndef __CLIBC_H__ 如果没有定义才
#define __CLIBC_H__

//内容

#endif

修改条件式编译的宏的两种方式
————修改源代码里面的宏
————用编译命令中使用 -D定义宏 这个方法更好 不用修改源代码

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
二 ;真正实现自己的测试框架
最后的效果为;用TEST方式定义一系列测试样例,里面用EXPECT_xx实现具体测试操作,并且最后调用RUN_ALL_TESTS();执行哪里测试样例

代码样子为;如果使用Google测试框架那么在测试代码中是这样来进行测试的,下面我们要做的是依旧用这样来测试,但是不用Google的测试框架也就是不用那个Google提供的静态链接库而是自己实现一套
#include <iostream>
#include <cstdio>

using namespace std;

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

TEST(test, add1)
{
    
    
	EXPECT_EQ(add(3,4),7);//等效于判断add(3,4)计算的结果是否等于7
	EXPECT_NE(add(3,4),8);
	EXPECT_LT(add(3,4),9);
	EXPECT_LE(add(3,4),7);
}
TEST(test, add3)
{
    
    
	EXPECT_EQ(add(3,4),7);//等效于判断add(3,4)计算的结果是否等于7
	EXPECT_NE(add(3,5),7);//定义一个错误,则执行的时候就报错
	EXPECT_LT(add(3,4),9);
	EXPECT_LE(add(3,4),7);
}

int main()
{
    
    
	printf("\033[0;1;33;41mhello world\033[0m\n");
	
	return RUN_ALL_TESTS();
}

首先先解析一下测试代码中用到了哪些东西以及实现的技术细节,用到了TEST、EXPECT_LT、RUN_ALL_TESTS。
1)先从比较简单实现的EXPECT_开始实现;
EXPECT_的作用就是没有错误的时候就正常运行,有错误的时候就显示出具体的错误信息,目前采用宏的方法来实现

//注意可以将EXPECT_EQ 这些相同的部分抽象出来再定义一个EXPECT宏的思维
/*
最初的实现方法为,其余几个都是符号不同,则抽象出#define EXPECT(a, cmp, b)
#define EXPECT_EQ(a, b){\
	if(!((a) == (b))){\
		printf("error\n");\
	}\
}
*/
#define EXPECT(a, cmp, b) {\
	if(!((a) cmp (b))){\
		printf("error\n");\
	}\
}
#define EXPECT_EQ(a, b) EXPECT(a, ==, b)
#define EXPECT_NE(a, b) EXPECT(a, !=, b)
#define EXPECT_LT(a, b) EXPECT(a, <, b)
#define EXPECT_LE(a, b) EXPECT(a, <=, b)

2)进行一个相关颜色的封装

//字符串的写法 "hello world" 等效于 "hello"" ""world"
//##两个#代表连接字符 #一个#表示字符串话就是两边加上双引号
#define COLOR(msg, code) "\033[0;1;" #code "m" msg "\033[0m"
#define RED(msg) COLOR(msg, 31)
#define GREEN(msg) COLOR(msg, 32)
#define YELLOW(msg) COLOR(msg, 33)

//最初的实现方法,其他颜色设置除开颜色属性不一致外其他一样,则可以进行二次封装
//#define RED(msg) ”\033[0;1;31m" msg "\033[0m"


可以在main函数中进行测试
printf(RED("hello world\n"));
printf(GREEN("hello world\n"));
printf(YELLOW("hello world\n"));

3)设置函数属性的基础知识__attribute__关键字和constructor属性
函数属性的设置 attribute((constructor))const主程序不一定是程序的入口,constructor这个属性并先于主函数实现
在这里插入图片描述

4)开始实现测试框架核心代码
RUN_ALL_TESTS函数为main函数的返回值,则应该也满足返回int 并且该函数作用应该是执行每一个测试用例。

实现TEST(test, add1){EXPECT_EQ(add(3,4),7);xxxx} 测试样例
这样子的测试样例,不符合合法的c语言代码则那么应该推断出TEST应该是一个宏,则目的就是让编译器帮我们写代码,进行简单替换的,结合传参后面大括号,应该最后展开应该是一个合法的函数,那具体是写什么代码呢?
因为是一个合法函数,又可以定义多个测试样例那么函数名应该与传参有关,则我们可以把函数名定义为两个参数的组合,最初的模型应该是 #define TEST(a,b) void a##_##b()这样的函数,在拼接到主工程的定义那里就可以拼凑为一个完整的函数,有这边替换的函数名和那边定义的函数体

RUN_ALL_TESTS函数怎么实现执行每一个测试用例,并且从上得知每个测试用例就是一个合法函数。因为要每一个函数都要执行,那么就应该有一个存储区域来存放要执行的函数的函数地址(函数指针)则在RUN_ALL_TESTS中就直接遍历这个存储区域去挨个执行这些函数指针即可。再接下来的问题是谁来把这些宏替换定义的函数的函数指针挨个存放到这个存储区域呢,这就需要一个注册函数用来将定义的测试样例函数将这些函数地址指针存储到存储区域提供给RUN_ALL_TESTS来挨个执行。
暂时先用简单的实现

//存储区域
struct {
    
    
	void (*func)();//存储测试样例的函数指针
	const char *funcname;//存储功能的名字,用于打印信息显示
}fun_arr[1000];//存储区域
int  func_cnt = 0;//计数便于变量

int RUN_ALL_TESTS()
{
    
    
	//执行每一个测试用例
	for(int i = 0; i < func_cnt; i++)
	{
    
    
		printf(GREEN("[  RUN   ] ") "%s\n", fun_arr[i].funcname);
		fun_arr[i].func();
	}
	
	return 0;
}

最后剩余的问题就是测试案例 注册函数 将那些测试函数放到fun_arr存储区域 这些要怎么实现;
解决方法为;在测试用例宏TEST扩展为测试样例函数t1的时候也扩展出一个对应的注册函数r1,并将注册函数的属性设置为constructor,优先于main执行,并且注册函数中来实现对测试案例的函数地址和名字加入到存储区域为后续RUN_ALL_TESTS挨个执行做准备。

#define TEST(a,b) \
void a##_##b();\
__attribute__((constructor))\
void reg_##a##_##b(){\
	add_func(a##_##b, #a"."#b);\
}\
void a##_##b()

//注册函数 做二次封装
void add_func(void (*func)(), const char *str_name)
{
    
    
	fun_arr[func_cnt].func = func;
	fun_arr[func_cnt].funcname = str_name;
	func_cnt++;
	return ;
}

注意最后主程序中TEST最后被替换为
TEST(test, add1)
{
    
    
	EXPECT_EQ(add(3,4),7);//等效于判断add(3,4)计算的结果是否等于7
	EXPECT_NE(add(3,4),8);
	EXPECT_LT(add(3,4),9);
	EXPECT_LE(add(3,4),7);
}
/*
最后替换成了
void test_add1();__attribute__((constructor))void reg_test_add1(){ add_func(test_add1, "test"".""add1");}void test_add1()
{
 { if(!((add(3,4)) == (7))){ printf("error\n"); }};
 { if(!((add(3,4)) != (8))){ printf("error\n"); }};
 { if(!((add(3,4)) < (9))){ printf("error\n"); }};
 { if(!((add(3,4)) <= (7))){ printf("error\n"); }};
}
*/

最后面代码实现效果
在这里插入图片描述
相关技术知识点
要如果是定义宏来实现,可以抽出共同部分,宏就是来帮我们写代码的,宏的二次封装
实现打印信息颜色设置封装以及零值属性设置的思想
字符串的""问题 “abc” 等效于 “a”“b”“c”
实现函数属性为程序最开始先执行,函数属性的设置 attribute((constructor))const主程序不一定是程序的入口,constructor这个属性并先于主函数实现
要学会推导技术点而不是记忆

程序源代码

zw.h文件
#ifndef _ZW_H
#define _ZW_H

//存储区域
struct stru_funcNode{
    
    
	void (*func)();
	const char *funcname;
}fun_arr[10];
int  func_cnt = 0;

//注意可以将EXPECT_EQ 这些相同的部分抽象出来再定义一个EXPECT宏进行二次封装思维
/*
最初的实现方法为,其余几个都是符号不同,则抽象出#define EXPECT(a, cmp, b)
#define EXPECT_EQ(a, b){\
	if(!((a) == (b))){\
		printf("error\n");\
	}\
}
*/
//__typeof 定义数据类型  #以字符串形式显示有双引号的
#define EXPECT(a, cmp, b) {\
	__typeof(a) __a = (a), __b = (b);\
	if(!((__a) cmp (__b))){\
		printf("%s:%d : Failure\n",__FILE__, __LINE__);\
		printf(YELLOW("Expect: (%s) %s (%s), actual: %d vs %d\n"),\
		#a, #cmp, #b, __a, __b);\
	}\
}
#define EXPECT_EQ(a, b) EXPECT(a, ==, b)
#define EXPECT_NE(a, b) EXPECT(a, !=, b)
#define EXPECT_LT(a, b) EXPECT(a, <, b)
#define EXPECT_LE(a, b) EXPECT(a, <=, b)

//字符串的写法 "hello world" 等效于 "hello"" ""world"
//##两个#代表连接字符 #一个#表示字符串话就是两边加上双引号
#define COLOR(msg, code) "\033[0;1;" #code "m" msg "\033[0m"
#define RED(msg) COLOR(msg, 31)
#define GREEN(msg) COLOR(msg, 32)
#define YELLOW(msg) COLOR(msg, 33)

//最初的实现方法,其他颜色设置除开颜色属性不一致外其他一样,则可以进行二次封装
//#define RED(msg) ”\033[0;1;31m" msg "\033[0m"

#define TEST(a,b) \
void a##_##b();\
__attribute__((constructor))\
void reg_##a##_##b(){\
	add_func(a##_##b, #a"."#b);\
}\
void a##_##b()



//注册函数
void add_func(void (*func)(), const char *str_name)
{
    
    
	fun_arr[func_cnt].func = func;
	fun_arr[func_cnt].funcname = str_name;
	func_cnt++;
	return ;
}



int RUN_ALL_TESTS()
{
    
    
	//执行每一个测试用例
	for(int i = 0; i < func_cnt; i++)
	{
    
    
		printf(GREEN("[  RUN   ] ") "%s\n", fun_arr[i].funcname);
		fun_arr[i].func();
	}
	
	return 0;
}


#endif
mian代码
#include <iostream>
#include <cstdio>
#include <zw/zw.h>//包含头文件路径的方法;以当前路径直接写

using namespace std;

int add(int a, int b)
{
    
    
	return a+b;
}
TEST(test, add1)
{
    
    
	EXPECT_EQ(add(3,4),7);//等效于判断add(3,4)计算的结果是否等于7
	EXPECT_NE(add(3,4),8);
	EXPECT_LT(add(3,4),9);
	EXPECT_LE(add(3,4),7);
}
/*
最后替换成了
void test_add1();__attribute__((constructor))void reg_test_add1(){ add_func(test_add1, "test"".""add1");}void test_add1()
{
 { if(!((add(3,4)) == (7))){ printf("error\n"); }};
 { if(!((add(3,4)) != (8))){ printf("error\n"); }};
 { if(!((add(3,4)) < (9))){ printf("error\n"); }};
 { if(!((add(3,4)) <= (7))){ printf("error\n"); }};
}
*/
TEST(test, add3)
{
    
    
	EXPECT_EQ(add(3,4),7);//等效于判断add(3,4)计算的结果是否等于7
	EXPECT_NE(add(3,5),8);//定义一个错误
	EXPECT_LT(add(3,4),9);
	EXPECT_LE(add(3,4),7);
}

/*
__attribute__((constructor))
void test()
{
	printf("%s : hello wolrd\n", __func__);
	return ;
}
*/
int main()
{
    
    
	//printf("%s : hello wolrd\n", __func__);
	
	
	
	//printf("\033[0;1;33;41mhello world\033[0m\n");
	printf(RED("hello world\n"));
	//printf(GREEN("hello world\n"));
	//printf(YELLOW("hello world\n"));
	//return 0;
	return RUN_ALL_TESTS();
}

猜你喜欢

转载自blog.csdn.net/zw1996/article/details/109519628