leveldb 阅读笔记 简易测试框架

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了leveldb 阅读笔记 简易测试框架相关的知识,希望对你有一定的参考价值。

随leveldb一起开源的代码中,还包括一些测试程序, 发现这些测试程序都使用了一些公共的部分代码,很容易编写多个测试用例,自动运行,还能生成测试报告。原来这就是一个简单的测试框架啊,非常实用,实现也很美观,因此记下来。

自动化测试中的必不可少的过程,是需要针对不同的输入条件自动执行测试对象程序,比较输出结果和预期答案,并且提供测试报告, 而使用leveldb中的测试框架实现这些是件很方便的事情。

 

测试过程中的断言:

    每使用一个断言都会产生一个 Tester 的临时对象,断言的时候还允许附加额外的输出信息。

    该对象销毁时会判断断言是否成功,如果断言失败,会自动输出此次断言的文件位置以及失败原因,然后退出程序,结束测试。

实现代码如下:

// An instance of Tester is allocated to hold temporary state during
// the execution of an assertion.
class Tester {
 private:
  bool ok_;             // 断言是否成功
  const char* fname_;      // 文件名
  int line_;         // 行数, 与文件名 一起定位断言失败的位置
  std::stringstream ss_;   // 断言失败时输出的具体错误信息

 public:
  Tester(const char* f, int l)
      : ok_(true), fname_(f), line_(l) {
  }
 // 析构时判断断言结果
~Tester() { if (!ok_) { fprintf(stderr, "\\033[31m%s:%d:%s\\033[0m\\n", fname_, line_, ss_.str().c_str()); exit(1); } } Tester& Is(bool b, const char* msg) { if (!b) { ss_ << " Assertion failure " << msg; ok_ = false; } return *this; } Tester& IsOk(const Status& s) { if (!s.ok()) { ss_ << " " << s.ToString(); ok_ = false; } return *this; } #define BINARY_OP(name,op) \\ template <class X, class Y> Tester& name(const X& x, const Y& y) { if (! (x op y)) { ss_ << " failed: " << x << (" " #op " ") << y; ok_ = false; } return *this; } BINARY_OP(IsEq, ==) BINARY_OP(IsNe, !=) BINARY_OP(IsGe, >=) BINARY_OP(IsGt, >) BINARY_OP(IsLe, <=) BINARY_OP(IsLt, <) #undef BINARY_OP // Attach the specified value to the error message if an error has occurred
 // 允许附加额外的信息 template <class V> Tester& operator<<(const V& value) { if (!ok_) { ss_ << " " << value; } return *this; } };
// 一些方便使用的宏,常见断言语句
#define ASSERT_TRUE(c) ::leveldb::test::Tester(__FILE__, __LINE__).Is((c), #c) #define ASSERT_OK(s) ::leveldb::test::Tester(__FILE__, __LINE__).IsOk((s)) #define ASSERT_EQ(a,b) ::leveldb::test::Tester(__FILE__, __LINE__).IsEq((a),(b)) #define ASSERT_NE(a,b) ::leveldb::test::Tester(__FILE__, __LINE__).IsNe((a),(b)) #define ASSERT_GE(a,b) ::leveldb::test::Tester(__FILE__, __LINE__).IsGe((a),(b)) #define ASSERT_GT(a,b) ::leveldb::test::Tester(__FILE__, __LINE__).IsGt((a),(b)) #define ASSERT_LE(a,b) ::leveldb::test::Tester(__FILE__, __LINE__).IsLe((a),(b)) #define ASSERT_LT(a,b) ::leveldb::test::Tester(__FILE__, __LINE__).IsLt((a),(b))

 

测试对象的管理与自动执行:

struct Test {       // 保存单个测试对象信息
  const char* base;
  const char* name;
  void (*func)();   // 指向执行入口函数
};
std::vector<Test>* tests;   // 保存所有的测试对象

// 注册一个测试对象
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; }
// 运行所有测试对象, 也可根据环境变量只运行指定的测试对象
// 如果定义了 LEVELDB_TESTS 这个环境变量,只会运行那些名字中包含了LEVELDB_TESTS的值的测试单例。
int RunAllTests() { const char* matcher = getenv("LEVELDB_TESTS"); int num = 0; if (tests != NULL) { for (size_t i = 0; i < tests->size(); i++) { const Test& t = (*tests)[i]; if (matcher != NULL) { std::string name = t.base; name.push_back(.); name.append(t.name); if (strstr(name.c_str(), matcher) == NULL) { continue; } } fprintf(stderr, "==== Test %s.%s\\n", t.base, t.name); (*t.func)(); ++num; } } fprintf(stderr, "====\\033[32m PASSED %d tests\\033[0m\\n", num); return 0; } #define TCONCAT(a,b) TCONCAT1(a,b) // 字符拼接 #define TCONCAT1(a,b) a##b
/*
使用这个宏可以定义、自动注册测试用例
  base 是基类名
TCONCAT(_Test_ignored_,name) 是一个静态变量, 因此它的初始化可以优先于main函数运行,完成注册
  void TCONCAT(_Test_,name)::_Run() 后面接测试用例的实现
*/
#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()                        // 

 

例如对上一篇内存分配器做简单测试:

namespace leveldb {

class ArenaTest { };

// 定义测试1 TEST(ArenaTest, Empty) { Arena arena; }
// 定义测试2 TEST(ArenaTest, Simple) { std::vector
<std::pair<size_t, char*> > allocated; Arena arena; const int N = 100000; size_t bytes = 0; Random rnd(301); for (int i = 0; i < N; i++) { size_t s; if (i % (N / 10) == 0) { s = i; } else { s = rnd.OneIn(4000) ? rnd.Uniform(6000) : (rnd.OneIn(10) ? rnd.Uniform(100) : rnd.Uniform(20)); } if (s == 0) { // Our arena disallows size 0 allocations. s = 1; } char* r; if (rnd.OneIn(10)) { r = arena.AllocateAligned(s); } else { r = arena.Allocate(s); } for (size_t b = 0; b < s; b++) { // Fill the "i"th allocation with a known bit pattern r[b] = i % 256; } bytes += s; allocated.push_back(std::make_pair(s, r)); ASSERT_GE(arena.MemoryUsage(), bytes); if (i > N/10) { ASSERT_LE(arena.MemoryUsage(), bytes * 1.10); // 断言按上面的申请内存方式,浪费的量不超过10% } } for (size_t i = 0; i < allocated.size(); i++) { size_t num_bytes = allocated[i].first; const char* p = allocated[i].second; for (size_t b = 0; b < num_bytes; b++) { // Check the "i"th allocation for the known bit pattern ASSERT_EQ(int(p[b]) & 0xff, i % 256);      // 断言与之前填写的内容一致,既分配的内存没有发生重叠、错乱。 } } } } // namespace leveldb int main(int argc, char** argv) { return leveldb::test::RunAllTests(); // 运行上面两个测试用例 }

测试结果:  通过

技术分享

假如我们要求内存浪费量不超过5%, 将第一句断言稍作修改:  

ASSERT_LE(arena.MemoryUsage(), bytes * 1.05)

得到的测试结果: 失败

技术分享

 

 














以上是关于leveldb 阅读笔记 简易测试框架的主要内容,如果未能解决你的问题,请参考以下文章

leveldb 阅读笔记 内存分配

LevelDB的源码阅读 Open操作

基于OpenGL编写一个简易的2D渲染框架02——搭建OpenGL环境

leveldb架构梳理及源码阅读规划

leveldb架构梳理及源码阅读规划

《逻辑思维简易入门》(第2版) 阅读笔记二