C语言[工程项目应用]gtest测试框架编写以及自定义测试框架

1 gtest测试框架:

下载地址: git clone https://github.com/google/googletest
安装部署:

cd googletest
cmake CmakeLists.txt #生成makefile
make #执行makefile

目录结构:

googletest
----lib
--------libgtest.a
----CMakeList.txt
----makefile
----googletest
--------include
------------gtest
----------------gtest.h

1.1 测试样例

项目目录构成:

project
----include
--------gtest
------------gtest.h
----lib
--------libgtest.a
----main.cpp

main.cpp:

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

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

// gtest.h 的 内部宏
TEST(func, add) {
	EXPECT_EQ(add(3, 4), 7);
	EXPECT_EQ(add(3, 1), 5);
	ASSERT_EQ(add(3, 1), 5);
}

int main(int args, char *argv[]) {
	// gtest的内部命名空间
	testing::InitGoogleTest(&argc, argv);
	// RUN_ALL_TESTS 也应为 gtest.h 的内部宏
	return RUN_ALL_TESTS();
}

该案例用于检测 add(3, 4)是否为7, add(3,1)是否为5, add(3,1) = 5是否出错

编译与运行:
g++ -std=c++11 -I./include main.cpp -L./lib -lgtest -lpthread

-std=c++11 : 指定编译标准为C++11
-I./include : 指定头文件地址
-L./lib : 指定静态库地址

-lgtest : 调用静态库 libgtest.a
-lpthread : 调用静态库 libpthread.a

<>搜索顺序为:系统目录–>环境变量目录–>用户自定义目录。
""搜索顺序为:用户自定义目录–>系统目录–>环境变量目录.

结果 :

[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from func
[ RUN      ] func.add
main.cpp:10: Failure
Expected equality of these values:
  add(3, 1)
    Which is: 4
  5
main.cpp:12: Failure
Expected equality of these values:
  add(3, 1)
    Which is: 4
  6
[  FAILED  ] func.add (0 ms)
[----------] 1 test from func (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[  PASSED  ] 0 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] func.add

 1 FAILED TEST

2 自定义测试框架

要求:

  1. 设计TEST(a , b)宏
  2. 设计RUN_ALL_TESTS()宏
  3. 设计EXPECT_EQ(…)等系列功能
  4. 实现代码变色
  5. 实现动态内存分布

主体架构:

main_my.c
----test.h
----test.c
makefile
bin
----a.out

2.1 主文件main_my.c :

#include <studio.h>
#inlcude "test.h"

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

// 测试用例组的整体宏替换
TEST(testfunc, add1) {
	// 测试单元的具体宏替换
	EXPECT_EQ(add(3, 4), 7);
	EXPECT_NE(add(3, 2), 5);
	EXPECT_EQ(add(3, 3), 6);
}
TEST(testfunc, add2) {
	EXPECT_EQ(add(3, 4), 7);
	EXPECT_LT(add(3, 1), 5);
	EXPECT_EQ(add(3, 3), 6);
}
TEST(test, funcadd1) {
	EXPECT_EQ(add(3, 4), 7);
	EXPECT_NE(add(1, 1), 5);
	EXPECT_EQ(add(3, 3), 6);
}

int main(int argc, char *argv[]) {
	// 运行所有测试用例
	return RUN_ALL_TESTS();
}

2.2 头文件include/test.h :

PART 1

#ifndef _TEST_H
#define _TEST_H
// 动态空间分布引入的自定义链表库
#include "linklist.h"

// TEST宏定义
#define TEST(a, b)\
void a##_haizei_##b();\
__attribute__((constructor)) void add##_haizei_##a##_haizei_##b() {\
	add_function(a##_haizei_##b, #a"."#b);\
}\
void a##_haizei_##b()
/* 1
 * a : 变量名1, b : 变量名2
 * 2
 * __attribute__(constructor) type func(...) {...}  
 * executing func before function main
 * (add the TEST and compose the name(add_function) before main)
 * 3
 * ## : concat a and b as ab
*/

#的用法:

  1. 将多个变量名连接成一个新的变量(函数)名
    例 : a##_haizei_##b() == a_haizei_b
  2. 取变量名的字符串
    例: #a"."#b == "a.b"

TEST宏实现 :
位于 : 主函数的前面
功能 : 1. 声明单元测试函数;2. 定义单元测试用例添加函数
技巧:

__attribute__((constructor)) func机制 : func先于main执行

int main(int argc, char * argv[]) {
    @autoreleasepool {
        printf("main function");
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
__attribute__((constructor)) static void beforeFunction()
{
    printf("beforeFunction\n");
}

// 输出结果顺序
beforeFunction
main function

__attribute__为GNU C的特色机制 : 可以设置 函数, 变量, 类型的属性

__attribute__相关博文链接

PART 2

// 输出颜色宏设置
#define COLOR(a, b) "\033[" #b "m" a "\033[0m"
#define COLOR_HL(a, b) "\033[1;" #b "m" a "\033[0m"

#define GREEN(a) COLOR(a, 32)
#define RED(a) COLOR(a, 31)
#define BLUE(a) COLOR(a, 34)
#define YELLOW(a) COLOR(a, 33)

#define GREEN_HL(a) COLOR_HL(a, 32)
#define RED_HL(a) COLOR_HL(a, 31)
#define BLUE_HL(a) COLOR_HL(a, 34)
#define YELLOW_HL(a) COLOR_HL(a, 33)
/* printf中设置颜色的方式, 其中A为被上色的字符
 * 正常亮度:
 * "\033[xxmA\033[0m"
 * 高亮:
 * "\033[1;xxmA\033[0m"
*/

PART 3

// 泛型宏: _Generic的使用 (为生成的值设置颜色)
// 只能用于.c文件中 (在.cpp & .cc文件中使用会报错)
// 依照变量a的类型返回对应的字符串
#define TYPE(a) _Generic((a), int:"%d", double:"%lf")

// 利用泛型宏 和 颜色函数名宏替换 对生成值设置颜色
// a: 生成值, color: 待替换颜色函数名
#define P(a, color) {\
	char frm[1000];\
	sprintf(frm, color("%s"), TYPE(a));\
	printf(frm, a);\
}

泛型宏_Generic的使用 :
_Generic是C11新增的一个关键字
_Generic((var), type1 : ..., type2 : ..., ……, default : ...)
type 表示类型,…表示对这个类型的var进行的操作,最终的default表示其他类型,也就是所定义的type中没有的类型,就会跳到default

__Generic()用法解析

PART 4

// 实际单元测试用例的部分共有功能的宏定义
/* EXPECT(a, b, cmp)
 * a : 单元用例的实际函数返回值; 
 * b : 单元用例的期望值; 
 * cmp : 用例的操作符
*/
#define EXPECT(a, b, cmp) {\
	printf(GREEN("[-----------] ") #a" " #cmp" "#b" ");\
	haizei_test_info.total += 1;\
	__typeof(a) _a = (a);\
	__typeof(b) _b = (b);\
	if (_a cmp _b) {\
		haizei_test_info.success += 1;\
		printf("%s\n",\
			(_a) cmp (_b) ? GREEN_HL("True") : RED_HL("False"));\
	}\
	else {\
		printf("%s\n",\
			(_a) cmp (_b) ? GREEN_HL("True") : RED_HL("False"));\
		printf("\n");\
		printf(YELLOW_HL("\t%s:%d: failure\n"), __FILE__, __LINE__);\
		printf(YELLOW_HL("\t\texpect : " #a " " #cmp " " #b "\n"));\
		printf(YELLOW_HL("\t\tactual : " ));\
		P(_a, YELLOW_HL);\
		printf("\n\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, <=)
#define EXPECT_GT(a, b) EXPECT(a, b, >)
#define EXPECT_GE(a, b) EXPECT(a, b, >=)

详细注释 :

EXPECT(a, b, cmp) {
	// a , b被变量替换, cmp被逻辑比较符替换
	
	printf(GREEN("[-----------] ") #a" " #cmp" "#b" ");
	// GREEN("...")将返回一个带颜色的字符串 并与(例)"5 >= 2"相连
	
	haizei_test_info.total += 1;
	// 更新测试用例计数
	
	__typeof(a) _a = (a);
	__typeof(b) _b = (b);

typeof() :
GUN C提供的一种特性,可以取得变量的类型,或者表达式的类型

在宏定义中动态获取相关结构体成员的类型 :

例:
1 .定义一个和变量x相同类型的临时变量_max1,定义一个和变量y相同类型的临时变量_max2
2 .判断两者类型是否一致,不一致给出一个警告,最后比较两者。

#define max(x, y) ({                \
    typeof(x) _max1 = (x);          \
    typeof(y) _max2 = (y);          \
    (void) (&_max1 == &_max2);      \//如果调用者传参时两者类型不一致,编译时会警告。
    _max1 > _max2 ? _max1 : _max2; })
	if (_a cmp _b) {
		
		// 表示用例通过, 更新用例通过计数, 并按要求设置打印信息
		haizei_test_info.success += 1;
		printf("%s\n",
			(_a) cmp (_b) ? GREEN_HL("True") : RED_HL("False"));
	}
	else {
		
		// 打印用例出错处的位置信息与代码信息, 并打印实际输出值的信息进行对比
		printf("%s\n",
			(_a) cmp (_b) ? GREEN_HL("True") : RED_HL("False"));
		printf("\n");
		printf(YELLOW_HL("\t%s:%d: failure\n"), __FILE__, __LINE__);
		printf(YELLOW_HL("\t\texpect : " #a " " #cmp " " #b "\n"));
		printf(YELLOW_HL("\t\tactual : " ));
		P(_a, YELLOW_HL); 
		// _a 与实际生成右值的类型相等, YELLOW_HL来替换颜色函数名
		
		printf("\n\n");
	}
}

PART 5

// 以下声明内容的定义将位于../src/test.c文件中

// 设置测试用例指针 : 用于计数 与 存储
typedef void (*Testfunc)();

// 存储测试用例函数指针, 函数字符串名, 以及链表信息
typedef struct Function {
	Testfunc func;
	const char* str;
	struct LinkNode p;
} Function;

// 用于记录测试用例总数与通过次数的结构体
struct FunctionInfo {
	int total, success;
};

// 声明外部变量,其具体定义会在后面的文件中出现
extern struct FunctionInfo haizei_test_info;

// 声明主函数中的方法
int RUN_ALL_TESTS();

// 声明将测试用录进行记录存储的方法
void add_function(Testfunc, const char*);

#endif

2.3 自定义链表头文件include/linklist.h

#ifndef _LinkList_H
#define _LinkList_H

// 获取 T 类型 name字段的偏移量
#define offset(T, name) (long long)( &( ((T *)(0))->name ) )
// p : 当前节点字段p的地址, T, name (可获取接下来T类型的name字段的偏移量)
// 因为地址的数字表示比较长, 所以需要log long来匹配
#define Head(p, T, name) (T *)( (char *)(p) - offset(T, name) )
struct LinkNode {
	struct LinkNode *next;
};
#endif

offset(T, name) :

  1. 将空地址0强转成T *类型 : (T *)(0)
  2. T对应的name成员变量的地址 : &(((T *)(0))->name)
  3. 地址取long long类型

Head(p, T, name)需结合后续操作进行解读

以上两步是为了将含LinkNode类型成员的结构体进行连接

2.4 功能函数文件src/test.c

#include "../include/test.h"
#include <string.h>
#inlcude <stdio.h>
#include <math.h>
#include <stdlib.h>

// 设置起始实例存储节点, 并设置末尾节点进行初始化
Function func_head, *func_tail = &func_head;

// 对应 include/test.h 中的 extern struct FunctionInfo haizei_test_info;
struct FunctionInfo haizei_test_info;

// 主函数的实际操作
int RUN_ALL_TESTS() {
	// 遍历之前在之前已经执行过的add_function所保存的各用例所处的地址信息(以便于调用)
	for (struct LinkNode *p = func_head.p.next; p; p = p->next) {
		// 获得当用例小组的指针, 打印开始信息 与 对应函数名
		Function *func = Head(p, Function, p);
		printf(GREEN("[====RUN====]") YELLOW_HL(" %s") "\n", func->str);
		// 初始化用例计数
		haizei_test_info.total = 0;
		haizei_test_info.success = 0;
		// 执行函数
		func->func();
		// 计算用例通过率
		double rate = 100.0 * haizei_test_info.success / haizei_test_info.total;
		printf(GREEN("[__"));
		if (fabs(rate - 100.0) < 1e-6) {
			printf(BLUE_HL("%6.2lf%%"), rate);
		}
		else {
			printf(YELLOW_HL("%6.2lf%%"), rate);
		}
		printf(GREEN("__]") "total : %d success : %d\n",
			haizei_test_info.total, haizei_test_info.success
		);
	}
	return 0;
}

// 存储用例信息的链表生成函数
void add_function(Testfunc func, const char* str) {
	struct Function *temp = (Function *)calloc(1, sizeof(Function));
	temp->func = func;
	temp->str = strdup(str);
	func_tail->p.next = &(temp->p);
	func_tail = temp;
}

strdup()strcpy()的区别:
strdup()可以把要复制的内容直接复制给没有初始化的指针,它会自动分配空间给目的指针,但需要手动free()进行内存回收。
strcpy()的目的指针一定是已经分配(足够)内存的指针

2.5 编译执行文件makefile

.PHONY:clean run

all: main_my.o src/test.o include/test.h
        gcc -I./ main_my.o src/test.o -o ./bin/my
main.o: main_my.c include/test.h
        gcc -I./ -c main_my.c
haizei/test.o: haizei/test.c haizei/test.h
        gcc -I./ -c src/test.c -o src/test.o
clean:
        rm -rf bin/my main_my.o src/test.o
run:
        ./bin/my

makefile*相关语法:

目标 : 依赖1, 依赖2, ...
	命令
依赖1 : 子依赖1, 子依赖2, ...
	命令
...

clear规则:
.PHONY用于声明伪目标 : 不会被检查是否存在于文件中, 且不会应用默认规则生成clean文件

.PHONY: clean
clean : 
		rm -f 待删除项

定义的变量可以直接通过$(变量名)进行使用

# 设置C语言编译器
CC = gcc

# -g 增加调试信息(以便gdb调试时可以list到代码信息)
# -Wall 打开大部分警告信息
CFLAGES = -g -Wall

# main 的依赖文件集合
MAINOBJS = main.o test.o

.PHONY: clean run
all : $(MAINOBJS)
	$(CC) $(GFLAGES) -o main $(MAINOBJS)
test.o : test.c test.h
	$(CC) $(GFLAGES) -c test.o test.c
main.o : main.c test.h
	$(CC) $(GFLAGES) -c main.o main.c
clean:
	rm -f $(MAINOBJS) main
run:
	./main

一个Makefile文档可以包含多个规则, 既可以每次在make后说明执行哪个功能, 又可以通过定义的all来执行一系列的规则

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 1024 设计师:白松林 返回首页