• 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
    ", t.base, t.name);
          (*t.func)();
          ++num;
        }
      }  // 遍历整个Tests
      fprintf(stderr, "==== PASSED %d tests
    ", 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
    ", 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;
      }
    };
    
  • 相关阅读:
    Problem: 数字的拆分之二
    Problem: 数字的拆分之一
    Problem : [Usaco2014 Dec]Piggy Back
    Problem: 八中厕所的门
    Problem : [Usaco2007 Open]Catch That Cow 抓住那只牛
    vim设置
    生活致富靠传销?北海警方:排版错误已整改
    SpringBoot(二)——配置文件
    SpringBoot(一)——IDEA创建项目
    Redis(一)——安装与使用
  • 原文地址:https://www.cnblogs.com/bofengqiye/p/8589077.html
Copyright © 2020-2023  润新知