leveldb单元测试之宏定义源码剖析
前言
leveldb 是一个库,没有 main() 函数入口, 故非常难理清其中的代码逻辑。但好在库中有非常多的单元测试代码,帮助读者理解其中的各个模块的功能。然而,测试代码个人觉得一开始看时非常费解,特别是其中非常复杂的宏定义让人陷于云里雾里一般。研究 leveldb 的时间也有一段时间了,但一直都不想也不愿去弄懂。今天算是拿出破釜沉舟的勇气弄懂了这部分的原理,不能让谷歌大神辛苦写的测试代码没有发挥出它应有的价值。其实单元测试对于开发的作用非常重要的,这是毋庸置疑的。但我一直都没有去了解这部分的知识,平时自己写的代码都是只进行一些简单的调用测试就完事。
其实,单元测试的代码不好理解是因为代码里用了比较复杂的宏定义,如果对于宏定义的理解不够深刻的话理解起来非常困难。但如果耐心一点,将宏定义的代码全部进行字符替换,最后梳理起来其实非常简单。
源码分析
在 db/db_test.cc中,我们跟踪TEST一个单元测试的实现。首先,在 main() 函数中:
int main(int argc, char** argv) {
return leveldb::test::RunAllTests();
}
RunAllTests() 定义
int RunAllTests() {
int num = ;
if (tests != NULL) {
for (int i = ; i < tests->size(); i++) {
const Test& t = (*tests)[i];
fprintf(stderr, "==== Test %s.%s\n", t.base, t.name);
(*t.func)();
++num;
}
}
fprintf(stderr, "==== PASSED %d tests\n", num);
return ;
}
Test 定义
struct Test {
const char* base;
const char* name;
void (*func)();
};
看其中最简单的一个 TEST 的代码
TEST(DBTest, Empty) {
ASSERT_TRUE(db_ != NULL);
ASSERT_EQ("NOT_FOUND", Get("foo"));
}
看 TEST 定义,是一个较为复杂的宏定义
#define TCONCAT(a,b) TCONCAT1(a,b)
#define TCONCAT1(a,b) a##b #define TEST(base,name) \
class TCONCAT(_Test_,name) : public base { \
public: \
void _Run(); \
static void _RunIt() { \
TCONCAT(_Test_,name) t; \
t._Run(); \
} \
}; \
bool TCONCAT(_Test_ignored_,name) = \
::leveldb::test::RegisterTest(#base, #name, &TCONCAT(_Test_,name)::_RunIt); \
void TCONCAT(_Test_,name)::_Run() // Register the specified test. Typically not used directly, but
// invoked via the macro expansion of TEST.
extern bool RegisterTest(const char* base, const char* name, void (*func)());
在宏定义中 # 表示将后面的参数替换成字符串,如:#abc 为 "abc"。 ## 表示粘连符,即将 a 和 b 连成一个字符串,比如:a = abc, b = 123, a##b 为 abc123。#define 为预处理命令,定义了一个标识符及一个串,在源程序中每次遇到该标识符时,均以定义的串代换它。预编译期间进行宏替换:
class _Test_Empty : public DBTest {
public:
void _Run();
static void _RunIt() {
_Test_Empty t;
t._Run();
}
};
bool _Test_ignored_name =
::leveldb::test::RegisterTest("DBTest", "Empty", &_Test_Empty::_RunIt); // 为全局变量,在 main() 函数运行前执行
void _Test_Empty::_Run() {
ASSERT_TRUE(db_ != NULL);
ASSERT_EQ("NOT_FOUND", Get("foo"));
}
RegisterTest() 定义,这个是注册测试用例的作用,tests 是一个全局 std::vector<Test>指针,存储测试用例。
bool RegisterTest(const char* base, const char* name, void (*func)()) {
if (tests == NULL) {
tests = new std::vector<Test>;
}
Test t;
t.base = base;
t.name = name;
t.func = func;
tests->push_back(t);
return true;
}
以上的代码实现其实非常巧妙,主要目的是用 TEST(xxx, yyy) {} 来定义一段单元测试代码 。思路是这样的,TEST(xxx, yyy)其实定义的是一个宏 ,{} 后面是要测试的代码,其实就是就把他当成一个函数,每声明一个宏就可以定义了一个函数并注册到全局的 tests 中去。这个过程中会定义一个 xxxyyy 的一个专属的测试用例类,而用宏定义实现这个过程主要是为了减少代码的冗余。因为测试用例非常多,如果对每个测试用例都专门编码定义一个类,那代码的冗余是让人无法接受的。其实这种做法也可以借鉴到其他地方来达到减少代码冗余的效果。
leveldb单元测试之宏定义源码剖析的更多相关文章
- Nodejs事件引擎libuv源码剖析之:高效线程池(threadpool)的实现
声明:本文为原创博文,转载请注明出处. Nodejs编程是全异步的,这就意味着我们不必每次都阻塞等待该次操作的结果,而事件完成(就绪)时会主动回调通知我们.在网络编程中,一般都是基于Reactor线程 ...
- Android源码剖析之Framework层升级版(窗口、系统启动)
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 看本篇文章之前,建议先查看: Android源码剖析之Framework层基础版 前面讲了frame ...
- 菜鸟nginx源码剖析 框架篇(一) 从main函数看nginx启动流程(转)
俗话说的好,牵牛要牵牛鼻子 驾车顶牛,处理复杂的东西,只要抓住重点,才能理清脉络,不至于深陷其中,不能自拔.对复杂的nginx而言,main函数就是“牛之鼻”,只要能理清main函数,就一定能理解其中 ...
- 《STL源码剖析》相关面试题总结
原文链接:http://www.cnblogs.com/raichen/p/5817158.html 一.STL简介 STL提供六大组件,彼此可以组合套用: 容器容器就是各种数据结构,我就不多说,看看 ...
- STL源码剖析——hashtable
二叉搜索树具有对数时间的搜索复杂度,但是这样的复杂度是再输入数据有足够的随机性的假设上哈希表在插入删除搜索操作上也具有常数时间的表现,而且这种表现是以统计为基础,不需要依赖输入元素的随机性 hasht ...
- 面试题总结(三)、《STL源码剖析》相关面试题总结
声明:本文主要探讨与STL实现相关的面试题,主要参考侯捷的<STL源码剖析>,每一个知识点讨论力求简洁,便于记忆,但讨论深度有限,如要深入研究可点击参考链接,希望对正在找工作的同学有点帮助 ...
- linux0.11内核源码剖析:第一篇 内存管理、memory.c【转】
转自:http://www.cnblogs.com/v-July-v/archive/2011/01/06/1983695.html linux0.11内核源码剖析第一篇:memory.c July ...
- Python开发【源码剖析】 List对象
前言 本文探讨的Python版本为2.7.16,可从官网上下载,把压缩包Python-2.7.16.tgz解压到本地即可 需要基本C语言的知识(要看的懂) PyListObject对象 PyListO ...
- ucore 源码剖析
lab1 源码剖析 从实模式到保护模式 初始化ds,es和ss等段寄存器为0 使能A20门,其中seta20.1写数据到0x64端口,表示要写数据给8042芯片的Output Port;seta20. ...
随机推荐
- CF920C Swap Adjacent Elements 贪心
我也不知道该说啥,水就是了~ code: #include <bits/stdc++.h> #define N 300004 #define setIO(s) freopen(s" ...
- create-react-app 构建的项目使用代理 proxy
1. 正常运行 npm run eject (前三个步骤可省略,最好的是按照第四步操作) 2. create-react-app 的版本在低于 2.0 的时候可以在 package.json 增加 p ...
- springboot中web应用的统一异常处理
在web应用中,请求处理过程中发生异常是非常常见的情况.springboot为我们提供了一个默认的映射:/error,当处理中抛出异常之后,会转到该请求中处理,并且该请求有一个全局的错误页面用来展示异 ...
- 一句话题解&&总结
CF79D Password: 差分.两点取反,本质是匹配!最短路+状压DP 取反是套路,匹配是发现可以把操作进行目的化和阶段化,从而第二次转化问题. 且匹配不会影响别的位置答案 sequence 计 ...
- Raspberry Pi 4B 使用OpenCV访问摄像头picamera模块
目录 1.OpenCV安装 (1)安装依赖 (2)下载OpenCV源码 (3)安装pip (4)安装Python虚拟机 (5)编译OpenCV (6)验证安装 2.使用OpenCV和Python控制摄 ...
- elasticsearch _create api创建一个不存在的文档
https://www.elastic.co/guide/cn/elasticsearch/guide/current/create-doc.html当我们索引一个文档, 怎么确认我们正在创建一个完全 ...
- python 进程池和任务量变化测试
今天闲,测试了下concurrent.futures 模块中的ThreadPoolExecutor,ProcessPoolExecutor. 对开不同的数量的进程池和任务量时,所耗时间. from c ...
- JMeter首金网自营项目-转义及数据库数据乱码的解决
param的string参数: 需要对”进行转义,加/ { "prdCreditInfo": { "revision": 0, "maxCredit& ...
- 01背包---P2392 kkksc03考前临时抱佛脚
P2392 kkksc03考前临时抱佛脚 题解 01背包,类似于这道题,相似度99.999999%: 01-背包 P2663 越越的组队 一共有4科,每科的时间独立,然后每一科做一遍 P2663越 ...
- Swift 常量
常量一旦设定,在程序运行时就无法改变其值. 常量可以是任何的数据类型如:整型常量,浮点型常量,字符常量或字符串常量.同样也有枚举类型的常量: 常量类似于变量,区别在于常量的值一旦设定就不能改变,而变量 ...