leveldb 阅读笔记 (2) 简易测试框架

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

得到的测试结果: 失败

 
时间: 2024-08-26 13:46:49

leveldb 阅读笔记 (2) 简易测试框架的相关文章

jdk源码阅读笔记之java集合框架(二)(ArrayList)

关于ArrayList的分析,会从且仅从其添加(add)与删除(remove)方法入手. ArrayList类定义: p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Monaco } span.s1 { color: #931a68 } public class ArrayList<E> extends AbstractList<E> implements List<E> ArrayList基本属性: /** *

软件测试学习笔记week 3 --- 测试框架初体验

测试框架初体验 在这周的软件测试课上,第一次了解了软件测试框架的概念.软件测试框架包含的范围非常广,从自动化测试框架到单元测试框架以及性能测试框架.在上个寒假中,在学习Coursera的在线课程时发现普林斯顿的单元测试做得非常强大,从程序正确性到Time consuming甚至Memory consuming,几乎能发现程序中的每一处错误或者缺陷.因此,在上完了这周的课程后,我查阅了一些资料,做了这篇随笔记录了解到的单元测试的知识. 一.什么是测试框架 要认识测试框架,首先要对所谓框架有概念.框

《构建之法》阅读笔记07-软件测试

在读软件测试之前,我认为软件测试不重要,甚至可以省略.但是读了这一部分之后,我深深地认识到软件测试对于开发等的作用是非常大的,而且是必不可少的.下面就软件测试做一下简单介绍. 一.基本名词解释及分类 1.Bug:软件的缺陷 Bug可以分为这三个组成部分:症状(Symptom).程序错误(Fault).根本原因(Root cause). (1)Symptom:即从用户的角度看,软件出了什么问题. 例如,在输入(3 2 1 1)的时候,程序错误退出. (2)Fault:即从代码的角度看,代码的什么错

jdk源码阅读笔记之java集合框架(一)(基础篇)

结合<jdk源码>与<thinking in java>,对java集合框架做一些简要分析(本着实用主义,精简主义,遂只会挑出个人认为是高潮的部分). 先上一张java集合框架的简图: 会从以下几个方面来进行分析: java 数组; ArrayList,LinkedList与Vector; HashMap; HashSet 关于数组array: 数组的解释是:存储固定大小的同类型元素.由于是"固定大小",所以对于未知数目的元素存储就显得力不从心,遂有了集合.

effective OC2.0 52阅读笔记(七 系统框架)

47 熟悉系统框架 总结:将代码封装为动态库,并提供接口的头文件,就是框架.平时的三方应用都用静态库(因为iOS应用程序不允许在其中包含动态库),并不是真正的框架,然而也经常视为框架.例如:NSLinguisticTagger可以解析字符串并找到其中的全部名词.动词.代词等.无缝桥接:将CoreFoundation中的C语言数据结构平滑转换为Foundation中的Objective-C对象,也可反向转换.OC编程一个重要特点是,经常需要使用底层的C语言级API,用C语言来实现API的好处是,可

leveldb 阅读笔记(1) 内存分配

内存管理对于任何程序都是很重要的一块,leveldb自己也实现了一个简单了内存分配器,而不是使用一些其他开源软件tcmalloc等,避免了对其他软件的依赖. 自己实现内存分配器有什么好处呢? 我认为主要有以下几点: 1. 内存池的主要作用是减少new  . delete 等的调用次数,也就是减少系统调用的开销. 2. 减少内存碎片. 3. 方便内存使用统计和监控. Arena 按块申请内存,每块大小为4k(这个值应该是和分页大小相关),然后使用vector保存这些块的首地址,模型如下: 成员变量

jdk源码阅读笔记之java集合框架(四)(LinkedList)

关于LinkedList的分析,会从且仅从其添加(add)方法入手. 因为上一篇已经分析过ArrayList,相似的地方就不再叙述,关注点在LinkedList的特点. 属性: /** *链表头 */ transient Node<E> first; /** * 链表尾 */ transient Node<E> last; 从以上两个属性可以得出LinkedList是基于双向链表实现的. 节点代码(无需过多解释): private static class Node<E>

IOS测试框架之:athrun的InstrumentDriver源码阅读笔记

athrun的InstrumentDriver源码阅读笔记 作者:唯一 athrun是淘宝的开源测试项目,InstrumentDriver是ios端的实现,之前在公司项目中用过这个框架,没有深入了解,现在回来记录下. 官方介绍:http://code.taobao.org/p/athrun/wiki/instrumentDriver/ 优点:这个框架是对UIAutomation的java实现,在代码提示.用例维护方面比UIAutomation强多了,借junit4的光,我们可以通过junit4的

CI框架源码阅读笔记2 一切的入口 index.php

上一节(CI框架源码阅读笔记1 - 环境准备.基本术语和框架流程)中,我们提到了CI框架的基本流程,这里这次贴出流程图,以备参考: 作为CI框架的入口文件,源码阅读,自然由此开始.在源码阅读的过程中,我们并不会逐行进行解释,而只解释核心的功能和实现. 1.       设置应用程序环境 define('ENVIRONMENT', 'development'); 这里的development可以是任何你喜欢的环境名称(比如dev,再如test),相对应的,你要在下面的switch case代码块中