随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-10-29 04:41:16