简版C语言测试框架实现
一、什么是单元测试框架?
对于单元测试框架来讲,它主要完成以下几件事。
提供用例组织与执行:测试用例只有几条时,可以不考虑用例组织,但是用例达到成百上千时,大量的测试用例堆砌在一起,就产生了扩展性与维护性等问题
提供丰富的断言方法:不论是功能测试,还是单元测试,在用例执行完之后都需要将实际结果与预期结果相比较(断言),从而断定用例是否执行通过。单元测试框架一般提供丰富的断言方法。例如:判断相等/不等、包含/不包含、True/False的断言方法等
提供丰富的日志: 当测试用例执行失败时能抛出清晰的失败原因,当所有用例执行完成后能提供丰富的执行结果。例如,总执行时间、失败用例数、成功用例数等。
从这些特性来看单元测试框架的作用是:帮助我们更自动化完成测试,所以,它是自动化测试的基础。
接下来我们对照gtest测试框架实现一个自己的简化版测试框架。
二、代码
test.h
#ifndef __test_H__
#define __test_H__
#include <stdio.h>
#include "linklist.h"
//添加输出字体颜色
#define COLOR(str, color) "\033[" #color "m" str "\033[0m"
#define COLOR_HL(str, color) "\033[1;" #color "m" str "\033[0m"
#define RED(str) COLOR(str,31)
#define GREEN(str) COLOR(str,32)
#define YELLOW(str) COLOR(str,33)
#define BLUE(str) COLOR(str,34)
#define RED_HL(str) COLOR_HL(str,31)
#define GREEN_HL(str) COLOR_HL(str,32)
#define YELLOW_HL(str) COLOR_HL(str,33)
#define BLUE_HL(str) COLOR_HL(str,34)
//测试宏
#define TEST(a, b)\
void a##b();\
__attribute__((constructor)) /*使下一函数优先于main函数被执行*/ \
void add_##a##b() { \
add_function(a##b, #a "." #b);/*添加测试组函数到链表*/ \
}\
void a##b()
#define TYPE(a) _Generic((a), /*_Generic c11泛型关键字 将相应类型替换为格式控制字符串*/ \
int : "%d",\
float : "f",\
double : "lf",\
long long : "lld",\
char * : "%s",\
const char * : "%s"\
)
#define P(a, color) {\
char frm[1000];\
sprintf(frm, color("%s"), TYPE(a));\
printf(frm, a);\
}
//断言 根据 cmp 比较 a 和 b 的值
#define EXPECT(a, b, cmp) {\
__typeof(a) _a = (a);\
__typeof(b) _b = (b);\
test_info.total++;\
if (_a cmp _b) {test_info.success++;}\
else {\
printf("\r\n");\
printf(YELLOW_HL("\t%s:%d: failure\r\n"), __FILE__, __LINE__);/*输出错误点信息*/\
printf(YELLOW_HL("\t\texpect : " #a " " #cmp " " #b) "\r\n");\
printf(YELLOW_HL("\t\tactual : "));\
P(_a,YELLOW_HL);\
printf(YELLOW_HL(" vs "));\
P(_b,YELLOW_HL);\
printf("\r\n\r\n");\
}\
printf(GREEN_HL("[-----------] "));\
printf(#a " " #cmp " " #b " %s\r\n",_a cmp _b ? GREEN("True") : RED("False"));\
}
#define EXPECT_EQ(a, b) EXPECT(a, b, ==)
#define EXPECT_NE(a, b) EXPECT(a, b, !=)
#define EXPECT_LE(a, b) EXPECT(a, b, <=)
#define EXPECT_LT(a, b) EXPECT(a, b, <)
#define EXPECT_GE(a, b) EXPECT(a, b, >=)
#define EXPECT_GT(a, b) EXPECT(a, b, >)
//测试组函数指针类型
typedef void (*FunctestT)();
//测试组函数结构体
typedef struct Function
{
FunctestT func;
char *str;
LinkNode p;
}Function;
//测试信息结构体
typedef struct FunctionInfo
{
int total, success;
}FunctionInfo;
extern FunctionInfo test_info;
int add(int a, int b);
int RUN_ALL_TESTS();
void add_function(FunctestT, const char *);
#endif // __test_H__
linklist.h
#ifndef __linklist_H__
#define __linklist_H__
#define offset(T, name) (long long)(&(((T *)(NULL))->name))//结构体元素偏移量
#define HEAD(p, T, name) (T *)((char*)(p) - offset(T, name))//移动指针到结构体首地址
typedef struct LinkNode
{
struct LinkNode *next;
}LinkNode;
#endif // __linklist_H__
test.c
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "../include/test.h"
int func_cnt = 0;//测试组函数个数
Function func_head, *func_tail = &func_head;//头结点 和 尾节点指针
FunctionInfo test_info;//测试节点信息
int RUN_ALL_TESTS() {
for (LinkNode *p = func_head.p.next; p; p = p->next)
{
Function *func = HEAD(p, Function, p);//移动指针到结构体首地址
test_info.total = 0, test_info.success = 0;
printf(GREEN_HL("[====RUN====]")RED_HL(" %s\r\n"), func->str);
func->func();
free(func->str);
func->str = NULL;
free(func);
func = NULL;
printf(GREEN_HL("[ "));
double rate = 1.0 * test_info.success / test_info.total; //测试通过率
rate *= 100;
if (fabs(rate - 100) < 1e-6) {
printf(BLUE_HL("%6.2lf%%"), rate);
} else {
printf(RED_HL("%6.2lf%%"), rate);
}
printf(GREEN_HL(" ] "));
printf("total : %d success : %d \r\n", test_info.total,test_info.success);
}
return 0;
}
//添加测试组函数到结构体链表
void add_function(FunctestT func,const char *str) {
Function *temp = calloc(1, sizeof(Function));
temp->func = func;
temp->str = strdup(str);
func_tail->p.next = &(temp->p);
func_tail = temp;
func_cnt++;
return;
}
int add(int a, int b) {
return a + b;
}
main.c
#include "../include/test.h"
//一组测试例
TEST(test, add0) {
EXPECT_EQ(add(1, 1), 2);//断言
EXPECT_NE(add(1, 1), 3);
EXPECT_LE(add(1, 1), 2);
EXPECT_LT(add(1, 1), 5);
}
TEST(test, add1) {
EXPECT_EQ(add(1, 1), 2);
EXPECT_NE(add(1, 1), 2);
EXPECT_GE(add(1, 1), 2);
EXPECT_GT(add(1, 1), 3);
}
int main(int argc, char const *argv[]) {
//开始测试
return RUN_ALL_TESTS();
}
Makefile
CC = gcc
.PHONY:clean
test:main.o libtest.a
$(CC) ./build/main.o -I./include -L./lib -ltest -o ./bin/test
main.o:
$(CC) -I./include ./src/main.c -c -o ./build/main.o
libtest.a:test.o
ar -rc ./lib/libtest.a ./build/test.o
test.o:
$(CC) -I./include ./src/test.c -c -o ./build/test.o
clean:
rm -f ./build/*.o ./lib/libtest.a
运行结果:
$ make
gcc -I./include ./src/main.c -c -o ./build/main.o
gcc -I./include ./src/test.c -c -o ./build/test.o
ar -rc ./lib/libtest.a ./build/test.o
gcc ./build/main.o -I./include -L./lib -ltest -o ./bin/test