leveldb源码分析之单元测试实现

在util/env_test.cc中,我们跟踪TEST一个单元测试的实现。首先,在main函数中:

// 一个测试示例
int main(int argc, char** argv) {
  return leveldb::test::RunAllTests();
}

点击RunAllTests(),查看源码:

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;
    }
  }  // 遍历整个Tests
  fprintf(stderr, "==== PASSED %d tests\n", num);
  return 0;
}

首先获取LEVELDB_TESTS的环境变量:先不看 if (matcher != NULL)的流程,是打印出test.base和 test.name,然后执行test中的回调函数(*t.func)(),最后打印了成功执行的单元测试数目。我们看看,Test结构体的定义:

struct Test {
  const char* base;  //TEST宏中的base
  const char* name;   // 
  void (*func)(); // 回调
};

在 RunAllTests中出现的tests是std::vector*类型 是一个全局变量。我们在看看一个具体的TEST使用的代码:

// EnvTest应该就是base, ReopenAppendableFile就是name
TEST(EnvTest, ReopenAppendableFile) {
  std::string test_dir;
  ASSERT_OK(env_->GetTestDirectory(&test_dir));
  std::string test_file_name = test_dir + "/reopen_appendable_file.txt";
  .....  
}

在看看TEST定义的宏:实际上是一个宏:

#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()

这个宏比较长,对于每一个每一个TEST单元实际上都是声明了一个类。注意TCONCAT(TEST, name)也是一个宏,表示连接_TEST_ 和name,来给这个类取个独一无二的名字:

#define TCONCAT(a,b) TCONCAT1(a,b)
#define TCONCAT1(a,b) a##b

下面这个函数中的宏,定义了类t,并调用了其Run_()方法。

static void _RunIt() {                                                \
    TCONCAT(_Test_,name) t;                                             \
    t._Run();                                                           \
}  

bool TCONCAT(_Test_ignored_,name) = ::leveldb::test::RegisterTest(#base, #name, &TCONCAT(_Test_,name)::_RunIt);

这句一条语句,调用RegisterTest函数,并注册该类的_RunIt方法,将注册的值给了_Test_igonored, 最后是实现void TCONCAT(TEST, name)::_Run()的方法,实现的内容通过就是TEST(xxx, yyyy)大括号后面的内容。很精彩。RegisterTest应该猜得到,怎么实现,就是push一个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;
}

的确很巧妙,这里主要是如何实现TEST(xxxx, yyy) {} 来定义一段单元测试代码,思路是这样的,TEST(xxxx, yyy)其实定义的是一个宏 ,{}后面是要测试的代码,其实就是就把他当成一个函数,每声明这个宏就可以定义了一个函数并注册到全局的tests中去。

单元测试中少不了宏,就是判断ASSERT_TRUE, ASSERT_FALSE之类的,我们看看其定义了了哪些宏:

#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))

这些宏,都是定义了Tester的临时对象对应的各个方法,我们看看该类的实现:

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, "%s:%d:%s\n", fname_, line_, ss_.str().c_str());
      exit(1);
    }
  }
  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;
  }
};
posted @ 2018-03-17 12:57  BruceChen7  阅读(678)  评论(0编辑  收藏  举报