leveldb学习:测试代码续

本文深入探讨了leveldb测试代码的内部机制,特别是_Test_name类的_Run函数和Tester类的使用。 Tester类用于保存测试状态,结合ASSERT_OK等断言宏,确保PUT、GET等操作正确执行。文章提到了如何批量写成员函数的技巧,并指出DBTest类作为封装,展示了使用leveldb接口的示例。通过对db_test测试程序的理解,有助于更好地掌握leveldb的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

通过上篇博客的学习,我知道了leveldb的测试程序主函数是如何利用宏TEST和容器tests调用测试程序,今天来看看测试程序,也就是写进_Test_name类的_Run( )究竟实现了什么。

TEST(DBTest, ReadWrite) {
  do {
    ASSERT_OK(Put("foo", "v1"));
    ASSERT_EQ("v1", Get("foo"));
    ASSERT_OK(Put("bar", "v2"));
    ASSERT_OK(Put("foo", "v3"));
    ASSERT_EQ("v3", Get("foo"));
    ASSERT_EQ("v2", Get("bar"));
  } while (ChangeOptions());
}

db_test几乎每一个测试函数都是一个循环结构,在testharness.h找到宏定义:

#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是一个类,FILE, LINE 是C定义的宏,表示当前文件名和行号。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, "%s:%d:%s\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;
  }

stringstream是C++标准库里实现字符转换的字符串流,用于取代c的sprintf()函数,stringstream无需像sprintf那样确保证目标缓冲区有足够大空间,以及确保使用正确的格式化符。只要把需要转换成字符的对象输入stringstream,再通过str()函数得到字符串输出。
注释上写着,tester类是用来保存状态,所谓state就是测试指令的返回值,还是上面的例子:

TEST(DBTest, ReadWrite) {
  do {
    ASSERT_OK(Put("foo", "v1"));
    ASSERT_EQ("v1", Get("foo"));
    ASSERT_OK(Put("bar", "v2"));
    ASSERT_OK(Put("foo", "v3"));
    ASSERT_EQ("v3", Get("foo"));
    ASSERT_EQ("v2", Get("bar"));
  } while (ChangeOptions());
}

这里的Get、Put就是需要测试的指令(函数)。好了,在这里总结一下:
leveldb的测试代码首先把需要测试的接口写入_TEST_ReadWrite类的成员函数_Run中,并登记这个类,以等待测试程序的主函数的调用。这里的test做的是一系列的Put和Get操作,而ASSERT_OK、ASSERT_EQ等是一个断言宏,代码置换后,它产生一个Tester类临时对象。如果PUT、GET操作没有返回理想的state值,断言失败,则在_Run函数结束时,通过Tester对象的析构函数,输出错误信息,包括定位出错的文件名和行号、错误信息。

在Tester类定义中教大家一个批量写成员函数的方法:

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

当然我们还不能忘了大部分TEST宏定义的_Test_name类派生自DBTest类,测试的函数(如上文中的Put、Get)来自DBTest。注意DBTest本身只是一个封装类,不含leveldb的实现代码,只是调用leveldb的接口,但它教你了使用leveldb的方法。例如:
DBTest中有一个指针:

DB* db_;

它告诉你把leveldb使用进自己的项目时,你需要定义一个DB对象或指针。还有:

Status Put(const std::string& k, const std::string& v) {
    return db_->Put(WriteOptions(), k, v);
  }

  Status Delete(const std::string& k) {
    return db_->Delete(WriteOptions(), k);
  }

  std::string Get(const std::string& k, const Snapshot* snapshot = NULL) {
    ReadOptions options;
    options.snapshot = snapshot;
    std::string result;
    Status s = db_->Get(options, k, &result);
    if (s.IsNotFound()) {
      result = "NOT_FOUND";
    } else if (!s.ok()) {
      result = s.ToString();
    }
    return result;
  }

这些都是使用leveldb进行读、写、查询的规范代码,值得借鉴。
再如:

// Return a string that contains all key,value pairs in order,
  // formatted like "(k1->v1)(k2->v2)".
  std::string Contents() {
    std::vector<std::string> forward;
    std::string result;
    Iterator* iter = db_->NewIterator(ReadOptions());
    for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
      std::string s = IterStatus(iter);
      result.push_back('(');
      result.append(s);
      result.push_back(')');
      forward.push_back(s);
    }

教你如何遍历leveldb数据库。诸如此类,我们还可以从db_test这一测试程序中学到很多,对于leveldb的用户来说,有些时候这些代码比说明文档还有用,当然前提是你测试程序的结构。

好了,db_test的代码有2000多行,在这里我就不在继续解读下去了,我认为看完我的两篇分析,弄清test程序的流程和结构,其余的代码理解应该不难,欢迎有兴趣的同学一起来补充和讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值