最近因为科研需求,一直在研究Google的开源RE2库(正则表达式识别库),库源码体积庞大,用C++写的,对于我这个以前专供Java的人来说真的是一件很痛苦的事,每天只能啃一点点。今天研究了下里面用到的测试方法,感觉挺好的,拿来跟大家分享下!(哈~C++大牛勿喷)
对于我这个C++菜鸟中的菜鸟而言,平时写几个函数想要测试一般都是在main中一个一个的测试,因为没用C++写过项目,没有N多方法所以在main中一个个测试也不费劲。但是对于一个项目而言,或多或少都有N多方法,如果在main中一个个测试的话,不仅效率低而且还容易出错遗漏什么的。那么该怎么进行测试呢?貌似现在有很多C++自动化测试的工具,反正我是一个没用过,也没法评价。我就说下Google在RE2库里是怎么测试的吧。
先用一个超级简单的例子来做讲解:测试两个方法getAsciiNum()和getNonAsciiNum(),分别求flow中ASCII码字符的数目和非ASCII码字符的数目。
第一步:写个头文件,定义测试所用类和测试方法。
// test.h
#define TEST(x, y) \
void x##y(void); \
TestRegisterer r##x##y(x##y, # x "." # y); \
void x##y(void)
void RegisterTest(void (*)(void), const char*);
class TestRegisterer {
public:
TestRegisterer(void (*fn)(void), const char *s) {
RegisterTest(fn, s);
}
};
解析:首先看定义的类TestRegisterer,有个构造方法,两个参数:
1. 一个函数指针:void (*fn)(void),指向我们具体要编写的测试方法名;
2. 一个字符串:constchar *s,属于该测试方法的描述信息。
这个构造函数调用了另一个函数RegisterTest(),具体实现见下面。
然后看最上面定义的宏TEST(x, y),主要将其替换为TestRegisterer r##x##y(x##y, # x"."
# y);其中x##y作为方法名,# x"." # y作为描述信息。这里可能有些和我一样入门级别的人没怎么看懂这个宏,因为不知道前后加void
x##y(void);这个是干嘛用的?一开始我也没想明白,因为不加的话就会报错,后来通过gcc的-E选项激活宏编译,看了下编译期间展开成啥模样了。这里以一个简单的例子作为说明:假设x为test,y为flow,如果不加前后那个,那么展开后为TestRegisterer
rtestflow(testflow, "test.flow"); 这明显是个函数声明,有两个参数,第二个是字符串,那么第一个是什么?编译器会认为是个函数名(实际上也是的),但这个函数前面明显未定义,就会报找不到此函数声明的错误,所以就需要在之前加上void x##y(void);声明函数,当然光声明不实现在链接时同样报错,所以就需要在之后加上void
x##y(void)进行具体实现了,注意这里没有逗号,也没有具体实现的{},因为这只是宏,Google的所有测试函数是这样写的:
TEST(x, y) {
.... // 具体实现
}
那么上面例子TEST(test, flow){ ... // 具体实现 },整体展开后就是这样:
void testflow(void);
TestRegisterer rtestflow(testflow, "test.flow");
void testflow(void) {
.... // 具体实现
}
第二步:N多个具体的测试实现。
#include <string>
#include <vector>
#include "test.h"
#define arraysize(array) (sizeof(array)/sizeof((array)[0]))
#define CHECK_EQ(x, y) if((x) != (y)) { printf("test failed!\n"); system("pause"); exit(0); }
struct TestFlow {
const char* flow;
const int num;
};
static struct TestFlow tests1[] = {
{"\x02\x97\xa4\xe6\xfe\x0c", 2},
{"\x05\x97\x35\xe6\xfe\xac\x04", 3},
{"\xb2\x97\xa5\xe6\x9c\x1c\x58\xaa\x97\x03", 3},
{"\x32\x97\xa5\x05\x9c\xac\xe8\xaa\x57", 3},
{"\x42\x01\xa5\x86\x0c\x56\xe8\xaa\x97\x03", 5},
};
static struct TestFlow tests2[] = {
{"\x02\x97\xa4\xe6\xfe\x0c", 4},
{"\x05\x97\x35\xe6\xfe\xac\x04", 4},
{"\xb2\x97\xa5\xe6\x9c\x1c\x58\xaa\x97\x03", 7},
{"\x32\x97\xa5\x05\x9c\xac\xe8\xaa\x57", 6},
{"\x42\x01\xa5\x86\x0c\x56\xe8\xaa\x97\x03", 5},
};
int getAsciiNum(const char*);
int getNonAsciiNum(const char*);
TEST(TestAsciiNum, Simple) {
int failed = 0;
for (int i = 0; i < arraysize(tests1); i++) {
const TestFlow& t = tests1[i];
int num = getAsciiNum(t.flow);
if (num != t.num) {
failed++;
}
}
CHECK_EQ(failed, 0);
}
TEST(TestNonAsciiNum, Simple) {
int failed = 0;
for (int i = 0; i < arraysize(tests2); i++) {
const TestFlow& t = tests2[i];
int num = getNonAsciiNum(t.flow);
if (num != t.num) {
failed++;
}
}
CHECK_EQ(failed, 0);
}
int getAsciiNum(const char* flow) {
// we assume that there's no \x00 in flow otherwise we cannot use strlen()
int num = 0, i;
for(i = 0; i < strlen(flow); i++) {
// ASCII: 0 ~ 127
if(flow[i] >= 0 && flow[i] < 128)
num++;
}
return num;
}
int getNonAsciiNum(const char* flow) {
// we assume that there's no \x00 in flow otherwise we cannot use strlen()
int num = 0, i;
for(i = 0; i < strlen(flow); i++) {
// ASCII: 0 ~ 127
if(flow[i] < 0 || flow[i] >= 128)
num++;
}
return num;
}
看上去一目了然,TEST(TestAsciiNum, Simple)和TEST(TestNonAsciiNum, Simple)就是两个具体的测试实现了,这个例子很简单,仅仅是为了说明问题。
第三步:具体的测试方案。
// test.cpp
#include <stdio.h>
#include <stdlib.h>
#include "test.h"
struct Test {
void (*fn)(void);
const char *name;
};
static Test tests[10000];
static int ntests;
void RegisterTest(void (*fn)(void), const char *name) {
tests[ntests].fn = fn;
tests[ntests++].name = name;
}
int main(int argc, char **argv) {
for (int i = 0; i < ntests; i++) {
printf("%s\n", tests[i].name);
tests[i].fn();
}
printf("PASS\n");
system("pause");
return 0;
}
解析:
1. 结构体Test存储具体的测试实现,定义最多能有10000个不同的方法测试,也就是能同时测试10000个方法。
2. ntests代表实际所测试的方法数,我这里就是2了。
3. RegisterTest()具体的实现也比较简单,就是将实际所要测试的方法名和描述信息存储到Test结构体数组tests中。
4. 最后就是在main中进行统一测试了,首先输出测试方法描述信息,以便知道当前测试了哪些方法及如果有测试失败时能及时进行排查。然后就是具体的执行测试函数了。
本例的测试结果如下:
思考:下面看下具体是如何执行的:
大家可能觉得main写的太简洁,一开始什么都没调用,直接来个for循环,ntests的值初始不是0吗?在main一开始也没显式的调用RegisterTest()将测试方法加进去啊,怎么一进入main,ntests就变成2了?
大家要记住:所有的测试具体实现都是在TEST这个宏里面,而宏是在编译期间就开始展开了。以 TEST(TestAsciiNum, Simple){ ... }为例,具体的执行过程如下:
编译期间:
TEST(TestAsciiNum, Simple)展开为:
void TestAsciiNumSimple(void);
TestRegisterer rTestAsciiNumSimple(TestAsciiNumSimple, "TestAsciiNum.Simple");
void TestAsciiNumSimple(void) {
int failed = 0;
for (int i = 0; i < arraysize(tests1); i++) {
const TestFlow& t = tests1[i];
int num = getAsciiNum(t.flow);
if (num != t.num) {
failed++;
}
}
CHECK_EQ(failed, 0);
}
然后就触发调用了TestRegisterer的构造方法从而开始执行RegisterTest(TestAsciiNumSimple,"TestAsciiNum.Simple")方法,将TestAsciiNumSimple方法名和描述信息"TestAsciiNum.Simple"加入到结构体数组tests中,这时ntests增为1,同理另一个宏TEST(TestNonAsciiNum,
Simple)展开后也将TestAsciiNonNumSimple方法名和描述信息"TestNonAsciiNum.Simple"加入到结构体数组tests中,这时ntests增为2,这是编译期间做的事。
运行期间:
从main开始,执行for循环,先后执行了具体的测试实现方法TestAsciiNumSimple()和TestAsciiNonNumSimple()从而完成测试。
用一个图来说明更加清晰(图画的不太好,望见谅~~~)
微信学习公众平台-媛媛推荐
微信号:programer-idea
名称:程序媛想事儿
功能介绍:媛媛的主题包括技术蛋糕(包括IT最新资讯、C/C++/Java等编程语言知识及有关算法探讨等IT资料)、生活指南、轻松一刻三个栏目,每天会推送这三个方面的信息给大家,让猿媛们在学习IT知识的同时能关注生活关注健康,同时还能轻松开怀一笑。同时,大家可以回复关键词定制自己想要的信息,如只看C/C++相关资料、只看Java相关资料、只看生活指南或只想开怀一笑都是可以的,后期会根据需要开设疑难解惑等其它平台,欢迎大家加入学习!!!
分享到:
相关推荐
re2是 google 开源的正则表达式库,由Rob Pike和Russ Cox两位来自 google 的大牛用 C++ 实现。它快速、安全,线程友好,是PCRE、PERL和Python等回溯正则表达式引擎(backtracking regular expression engine)的一个...
MD5 开源库 c++ 代码, 带测试代码 void printMD5(const string& message) { cout (\"" ) = " (message).toStr() ; } int main() { printMD5(""); printMD5("a"); printMD5("abc"); printMD5("message ...
比如在线程库方面,还有ZThread、boost::thread,如果放大到C/C++领域,还有APR,还有CII。在文件和目录操作方面,boost也有相应的组件,而在网络编程方面有socket++,还有boost::asio,未来的C ++0X中几乎肯定有一...
基于libmodbus开源库 C++ modbus-rtu通信测试程序源码,vs2013平台。此文件为主站程序可实现与从站(从站可以使用Modbus Slave 仿真软件)通信,实现寄存器的读写功能。
poco/c++开源库学习资料打包下载,通过网络收集整理,比较多 POCO_C++库学习和分析_--__跨平台库的生成.docx POCO_C++库学习和分析_--__随机数和数字摘要.docx POCO_C++库学习和分析_--_Cache.docx POCO_C++库学习和...
无需安装office,开源的Docx开发库,对word进行读写编辑等功能。
开源的C++压缩库,简单实用,通俗易懂很容易上手。很多项目都在引用,压缩后可以使用各主流压缩软件进行解压。
C/C++ Base64编解码开源库,第三方开源库,亲测可用;
c++经典好使精简易懂易用开源日志库
玩转Google开源C++单元测试框架Google Test
前段时间学习和了解了下Google的开源C++单元测试框架Google Test,简称gtest,非常的不错。 我们原来使用的是自己实现的一套单元测试框架,在使用过程中,发现越来越多使用不便之处,而这样不便之处,gtest恰恰很好...
GLog是Google开发的一套日志输出框架。是一个基于程序级记录日志信息的c++库,编程使用方式与c++的stream操作类似,由于其具有功能强大、方便使用等特性,它被众多开源项目使用
一个用C++写的机器学习的库,作者是Ron Kohavi,是SGI的一个开源项目
C++开源库 -.txt
介绍一些 gtest 的基本使用,包括下载,安装,编译,建立第一个测试 Demo 工程,以及编写一个最简单的测试案例。
玩转Google开源C++单元测试框架GoogleTest
libharu 写PDF 开源 C C++ 库,我已经使用VS2010编译过了,可以直接使用。也可以调试学习。
Google 开源项目风格指南中文版pdf 里面规定了google的C++开源项目的代码规范,是提高代码易读性和健壮性的重要参考资料。
Visual C++ 开源高效开发库WTL 8.0
SP++ (Signal Processing in C++) 是一个关于信号处理与数值计算的开源C++程序库,该库提供了信号处理与数值计算中常用算法的C++实现。