在学习 leveldb 源码的过程中,肯定少不了对特定函数或结构的测试。LevelDB几乎给每个关键的数据结构都写好了测试代码,我们可以直接使用,或者根据他的框架自己编写新的测试体,很方便。
但是,我不会用。。。
我看的源码是截止写这篇博客时的最新版,commit:aa5479bbf47e9df86e0afbb89e6246085f22cdd4。当我深究 filter_block 时,需要用到它的单元测试,测试代码位于 filter_block_test.cc 中,测试函数体如下:
TEST_F(FilterBlockTest, SingleChunk) {
FilterBlockBuilder builder(&policy_);
builder.StartBlock(100);
builder.AddKey("foo");
builder.AddKey("bar");
builder.AddKey("box");
builder.StartBlock(200);
builder.AddKey("box");
builder.StartBlock(300);
builder.AddKey("hello");
Slice block = builder.Finish();
FilterBlockReader reader(&policy_, block);
ASSERT_TRUE(reader.KeyMayMatch(100, "foo"));
ASSERT_TRUE(reader.KeyMayMatch(100, "bar"));
ASSERT_TRUE(reader.KeyMayMatch(100, "box"));
ASSERT_TRUE(reader.KeyMayMatch(100, "hello"));
ASSERT_TRUE(reader.KeyMayMatch(100, "foo"));
ASSERT_TRUE(!reader.KeyMayMatch(100, "missing"));
ASSERT_TRUE(!reader.KeyMayMatch(100, "other"));
}
函数体很容易理解,但问题来了,我不知道怎么运行它。TEST_F 是 google 下的一个单元测试框架gtest,我从来没用过甚至也没听说过,对它的使用很懵逼,连这个函数的声明方式都看不懂。并且,这个 filter_block_test.cc 文件没有 main 函数,说明我连编译都进行不了。另外,即使可以编译也不能只编译这一个文件,因为它链接了整个 leveldb 中的很多库。也就是说:
- gtest 是一点都没用过,TEST_F 是啥?
- 没 main 函数我怎么进去?
- 咋编译?
带着这三个问题我去搜了很多资料,直到碰见了这一篇:测试代码编译。让我有方向的这篇文章的最后一部分:

这两段话解答了我的第3个问题,如何编译。也就是说,要么先编译整个 leveldb 生成 libleveldb.a 然后再编译单元测试,要么直接将测试随着整个 leveldb 一同编译了。如果经常更改单元测试,肯定是前者要好很多,但这里我只是用一下,所以干脆直接选后者了。
问题来了,我查找 CMakeLists.txt 中的相关代码,发现了如下内容:

这段注释发布于13个月前,核心为:
Remove main() from most tests.
This gives some flexibility to embedders.
Currently, embedders have to build a binary for each test file.
这样就解答第2个问题了,main 函数在哪。从注释中可以看到,在该版本中,main() 被剥离了,因为想让测试变的更容易扩展,但这样就需要使用者自己去构建。被剥离了,说明之前存在过,于是我 pull 了之前的版本(v1.23),重新查看 filter_block_test.cc ,部分内容如下:
TEST_F(FilterBlockTest, SingleChunk) {
FilterBlockBuilder builder(&policy_);
builder.StartBlock(100);
builder.AddKey("foo");
builder.AddKey("bar");
builder.AddKey("box");
builder.StartBlock(200);
builder.AddKey("box");
builder.StartBlock(300);
builder.AddKey("hello");
Slice block = builder.Finish();
FilterBlockReader reader(&policy_, block);
ASSERT_TRUE(reader.KeyMayMatch(100, "foo"));
ASSERT_TRUE(reader.KeyMayMatch(100, "bar"));
ASSERT_TRUE(reader.KeyMayMatch(100, "box"));
ASSERT_TRUE(reader.KeyMayMatch(100, "hello"));
ASSERT_TRUE(reader.KeyMayMatch(100, "foo"));
ASSERT_TRUE(!reader.KeyMayMatch(100, "missing"));
ASSERT_TRUE(!reader.KeyMayMatch(100, "other"));
}
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
重点来了,这个版本的 xxx_test.cc 是有 main 入口的,说明可以直接编译生成可执行文件来使用。具体的流程为 RUN_ALL_TESTS() 会执行上面的所有 TEST_F,代码都在项目中写有,这里就不展示了。
CMakeLists.txt,不同于最新的版本,该版本把每一个 xxx_test.cc 的编译都考虑进去了,如下图所示:
此时,只需要按照正常流程编译 leveldb,这些测试文件即可一同被编译:
mkdir -p build && cd build
cmake -DCMAKE_BUILD_TYPE=Release .. && cmake --build .
生成的可执行文件位于 bulid/ 目录下:
只需运行即可:
至此,leveldb 如何进行单元测试的问题就解决了。
开发者将 main 函数从 xxx_test.cc 中移出,固然有它的道理,但由于我对 gtest 并不了解,所以还是习惯于使用旧版的测试。