leveldb unit test macros source analysis

Foreword

leveldb a library is no main () function entry, it is very difficult to sort out which code logic. But the good news library has a lot of unit test code, which help the reader understand the function of each module. However, the test code personally feel very puzzling start watching, in particular the very complex macros people caught in foggy in general. Research leveldb time also for some time, but has been unwilling to go do not want to understand. Today regarded as the most drastic of courage to come to understand the principles of this part of the Great God Google can not let hard to write test code did not play its proper value. In fact, unit testing role for the development of very important, it is beyond doubt. But I have not the knowledge to understand this part of the code are usually wrote some simple test call only to get away.

In fact, unit testing code because the code is not well understood by the more complex macros, very difficult to understand if deep enough, then the macro definition to understand. However, if the patient, the macro code to replace all of the characters, and finally sort it is actually very simple.

Source code analysis

In db / db_test.cc, we realize TEST tracking a unit test. First, in the main () function:

int main(int argc, char** argv) {
  return leveldb::test::RunAllTests();
}

RunAllTests () is defined

int RunAllTests() {
  int num = 0;
  if (tests != NULL) {
    for (int i = 0; i < tests->size(); i++) {
      const Test& t = (*tests)[i];
      fprintf(stderr, "==== Test %s.%s\n", t.base, t.name);
      (*t.func)();
      ++num;
    }
  }
  fprintf(stderr, "==== PASSED %d tests\n", num);
  return 0;
}

Test definitions

struct Test {
  const char* base;
  const char* name;
  void (*func)();
};

Look simplest of a code TEST

TEST(DBTest, Empty) {
  ASSERT_TRUE(db_ != NULL);
  ASSERT_EQ("NOT_FOUND", Get("foo"));
}

Look TEST definition, is a more complex macro definitions

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

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

// Register the specified test.  Typically not used directly, but
// invoked via the macro expansion of TEST.
extern bool RegisterTest(const char* base, const char* name, void (*func)());

It is defined in the macro # shows a replacement back into the parameter string, such as: #abc as "abc". ## represents a blocking character, i.e. a and b together into a string, such as: a = abc, b = 123, a ## b is abc123. #define preprocessor is defined an identifier and a string, when the source program identifiers each encounter, which are defined string substitution. Macro replacement during pre-compilation:

class _Test_Empty: public DBTest {
  public :
   void the _run ();
   static  void _RunIt () { 
    _Test_Empty T; 
    t._Run (); 
  } 
}; 
BOOL _Test_ignored_name =  
  :: :: RegisterTest leveldb :: Test ( " DBTest " , " Empty " , & _Test_Empty :: _ RunIt);   // global variable () function is performed before run main 
void _Test_Empty :: _ the run () { 
  ASSERT_TRUE (DB_ =! NULL); 
  ASSERT_EQ ( " NOT_FOUND " , the Get ( " foo"));
}

RegisterTest () is defined, the role of this test is registered, it is a global Tests std :: vector <Test> pointer storage test.

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;
}

The above code is actually very cleverly realized, with the main purpose TEST (xxx, yyy) {} means to define a test code section. The idea is this, TEST (xxx, yyy) is in fact the definition of a macro, {} followed by the code under test, in fact, I took him as a function of each statement can define a macro function and registered to a global the tests go. This process will define a unique class xxxyyy test, and defined by the macro for this process is mainly to reduce code redundancy. Because so many test cases, if the definition of a class are devoted to coding for each test case, the redundant code that is unacceptable. In fact, this practice can learn from other places to reduce code redundancy to achieve results.

 

Guess you like

Origin www.cnblogs.com/evenleee/p/11990028.html