关于后端:C轻量级单元测试框架

44次阅读

共计 6073 个字符,预计需要花费 16 分钟才能阅读完成。

1. 概述

单元测试是构建稳固、高质量的程序、服务或零碎的必不可少的一环。通过单元测试,咱们能够在开发过程中及时发现和修复代码中的问题,进步代码的品质和可维护性。同时,单元测试也能够帮忙咱们更好地了解代码的性能和实现细节,从而更好地进行代码重构和优化。

然而,很多 C ++ 单元测试框架都是“重量级”的,应用起来比较复杂,而且很多状况下咱们并不需要那么多简单的性能。因而,开发一个轻量级的 C ++ 单元测试框架,能够缩小代码中不必要的依赖,进步代码的可维护性和可测试性,同时也能够放慢编译和运行速度。

轻量级的 C ++ 单元测试框架,能够帮忙咱们更加不便地编写和治理单元测试,进步代码的品质和可维护性。

2. 实现原理

在正式开始介绍实现原理之前,须要特别强调的是,在这个单元测试框架中,所有的代码都定义在 UnitTest 命名空间中。这样做的益处是能够防止与其余代码的命名抵触,同时也能够更好地组织和治理代码。

2.1 测试用例基类

咱们形象出一个测试用例基类,它的定义如下所示。

class TestCase {
 public:
  virtual void Run() = 0;
  virtual void TestCaseRun() { Run(); }
  bool Result() { return result_;}
  void SetResult(bool result) {result_ = result;}
  std::string CaseName() { return case_name_;}
  TestCase(std::string case_name) : case_name_(case_name) {}

 private:
  bool result_{true};
  std::string case_name_;
};

在下面的代码中咱们定义了一个 C ++ 中的测试用例基类 TestCase,它定义了一些虚函数和成员变量,用于派生出具体的测试用例类。

首先,它定义了一个纯虚函数Run(),用于执行测试用例的具体逻辑。这个函数须要在具体的测试用例类中实现。

其次,它定义了一个虚函数 TestCaseRun(),它调用了Run() 函数,并将执行后果保留在 result_ 成员变量中。这个函数能够在具体的测试用例类中重写,以实现特定的测试逻辑。

接着,它定义了一个 Result() 函数,用于获取测试后果。这个函数返回一个 bool 类型的值,示意测试是否通过。

而后,它定义了一个 SetResult() 函数,用于设置测试后果。这个函数承受一个 bool 类型的参数,示意测试是否通过。

最初,它定义了一个 CaseName() 函数,用于获取测试用例的名称。这个函数返回一个 std::string 类型的值,示意测试用例的名称。

在这个类的构造函数中,它承受一个 std::string 类型的参数 case_name,用于设置测试用例的名称。这个参数会被保留在case_name_ 成员变量中。

2.2 单元测试外围类

咱们实现了单元测试外围类,它的定义如下所示。

class UnitTestCore {
 public:
  static UnitTestCore *GetInstance() {
    static UnitTestCore instance;
    return &instance;
  }

  int Run(int argc, char *argv[]) {
    result_ = true;
    failure_count_ = 0;
    success_count_ = 0;
    std::cout << kGreenBegin << "[==============================] Running" << test_cases_.size() << "test case."
              << kColorEnd << std::endl;
    constexpr int kFilterArgc = 2;
    for (int i = 0; i < test_cases_.size(); i++) {if (argc == kFilterArgc) {
        // 第二参数时,做用例 CaseName 来做过滤
        if (not std::regex_search(test_cases_[i]->CaseName(), std::regex(argv[1]))) {continue;}
      }
      std::cout << kGreenBegin << "Run TestCase:" << test_cases_[i]->CaseName() << kColorEnd << std::endl;
      test_cases_[i]->TestCaseRun();
      std::cout << kGreenBegin << "End TestCase:" << test_cases_[i]->CaseName() << kColorEnd << std::endl;
      if (test_cases_[i]->Result()) {success_count_++;} else {
        failure_count_++;
        result_ = false;
      }
    }
    std::cout << kGreenBegin << "[==============================] Total TestCase:" << test_cases_.size() << kColorEnd
              << std::endl;
    std::cout << kGreenBegin << "Passed:" << success_count_ << kColorEnd << std::endl;
    if (failure_count_ > 0) {std::cout << kRedBegin << "Failed:" << failure_count_ << kColorEnd << std::endl;}
    return 0;
  }

  TestCase *Register(TestCase *test_case) {test_cases_.push_back(test_case);
    return test_case;
  }

 private:
  bool result_{true};
  int32_t success_count_{0};
  int32_t failure_count_{0};
  std::vector<TestCase *> test_cases_;  // 测试用例汇合
};

在下面的代码中咱们定义了一个 C ++ 中的单元测试框架外围类 UnitTestCore,它提供了注册测试用例、运行测试用例等性能。

首先,它定义了一个动态函数GetInstance(),用于获取单例对象。这个函数应用了动态局部变量,保障了线程平安。

接着,它定义了一个 Run() 函数,用于运行所有注册的测试用例。这个函数承受两个参数,别离是命令行参数的数量和参数数组。在函数外部,它会遍历所有注册的测试用例,并顺次执行它们的 TestCaseRun() 函数。在执行完每个测试用例后,它会依据测试后果更新 success_count_failure_count_成员变量,并输入测试后果。如果有测试用例执行失败,它会将 result_ 成员变量设置为 false。

而后,它定义了一个 Register() 函数,用于注册测试用例。这个函数承受一个 TestCase 类型的指针参数,示意要注册的测试用例。在函数外部,它会将测试用例指针保留在 test_cases_ 成员变量中,并返回测试用例指针。

最初,它定义了一些公有成员变量,包含 result_success_count_failure_count_test_cases_。这些成员变量用于保留测试后果和测试用例汇合。

UnitTestCore 类提供了注册测试用例、运行测试用例等基本功能,能够帮忙咱们更加不便地编写和治理单元测试。

2.3 单测宏定义

咱们的单元测试框架预约义了一系列的宏,用于疾速构建单元测试。这些宏的内容如下。

#define TEST_CASE_CLASS(test_case_name)                                                     \
  class test_case_name : public UnitTest::TestCase {                                        \
   public:                                                                                  \
    test_case_name(std::string case_name) : UnitTest::TestCase(case_name) {}                \
    virtual void Run();                                                                     \
                                                                                            \
   private:                                                                                 \
    static UnitTest::TestCase *const test_case_;                                            \
  };                                                                                        \
  UnitTest::TestCase *const test_case_name::test_case_ =                                    \
      UnitTest::UnitTestCore::GetInstance()->Register(new test_case_name(#test_case_name)); \
  void test_case_name::Run()

#define TEST_CASE(test_case_name) TEST_CASE_CLASS(test_case_name)

#define ASSERT_EQ(left, right)                                                                                  \
  if ((left) != (right)) {                                                                                      \
    std::cout << UnitTest::kRedBegin << "assert_eq failed at" << __FILE__ << ":" << __LINE__ << "." << (left) \
              << "!=" << (right) << UnitTest::kColorEnd << std::endl;                                           \
    SetResult(false);                                                                                           \
    return;                                                                                                     \
  }

#define ASSERT_NE(left, right)                                                                                  \
  if ((left) == (right)) {                                                                                      \
    std::cout << UnitTest::kRedBegin << "assert_ne failed at" << __FILE__ << ":" << __LINE__ << "." << (left) \
              << "==" << (right) << UnitTest::kColorEnd << std::endl;                                           \
    SetResult(false);                                                                                           \
    return;                                                                                                     \
  }

#define ASSERT_LT(left, right)                                                                                  \
  if ((left) >= (right)) {                                                                                      \
    std::cout << UnitTest::kRedBegin << "assert_lt failed at" << __FILE__ << ":" << __LINE__ << "." << (left) \
              << ">=" << (right) << UnitTest::kColorEnd << std::endl;                                           \
    SetResult(false);                                                                                           \
    return;                                                                                                     \
  }

#define ASSERT_LE(left, right)                                                                                         \
  if ((left) > (right)) {                                                                                              \
    std::cout << UnitTest::kRedBegin << "assert_le failed at" << __FILE__ << ":" << __LINE__ << "." << (left) << ">" \
              << (right) << UnitTest::kColorEnd << std::endl;                                                          \
    SetResult(false);                                                                                                  \
    return;                                                                                                            \
  }

#define ASSERT_GT(left, right)                                                                                  \
  if ((left) <= (right)) {                                                                                      \
    std::cout << UnitTest::kRedBegin << "assert_gt failed at" << __FILE__ << ":" << __LINE__ << "." << (left) \
              << "<=" << (right) << UnitTest::kColorEnd << std::endl;                                           \
    SetResult(false);                                                                                           \
    return;                                                                                                     \
  }

#define ASSERT_GE(left, right)                                                                                         \
  if ((left) < (right)) {                                                                                              \
    std::cout << UnitTest::kRedBegin << "assert_ge failed at" << __FILE__ << ":" << __LINE__ << "." << (left) << "<" \
              << (right) << UnitTest::kColorEnd << std::endl;                                                          \
    SetResult(false);                                                                                                  \
    return;                                                                                                            \
  }

#define ASSERT_TRUE(expr)                                                                                         \
  if (not(expr)) {                                                                                                \
    std::cout << UnitTest::kRedBegin << "assert_true failed at" << __FILE__ << ":" << __LINE__ << "." << (expr) \
              << "is false" << UnitTest::kColorEnd << std::endl;                                                 \
    SetResult(false);                                                                                             \
    return;                                                                                                       \
  }

#define ASSERT_FALSE(expr)                                                                                         \
  if ((expr)) {                                                                                                    \
    std::cout << UnitTest::kRedBegin << "assert_false failed at" << __FILE__ << ":" << __LINE__ << "." << (expr) \
              << "if true" << right << UnitTest::kColorEnd << std::endl;                                          \
    SetResult(false);                                                                                              \
    return;                                                                                                        \
  }

#define RUN_ALL_TESTS() \
  int main(int argc, char *argv[]) {return UnitTest::UnitTestCore::GetInstance()->Run(argc, argv); }

2.3.1 TEST\_CASE\_CLASS

这个宏用于定义测试用例类。它承受一个参数 test_case_name,示意测试用例类的名称。这个宏它定义了一个继承自UnitTest::TestCase 的测试用例类,并实现了 Run() 函数。同时,它还定义了一个动态成员变量 test_case_,用于注册测试用例。在宏定义的最初,它应用UnitTest::UnitTestCore::GetInstance()->Register() 函数将测试用例注册到测试框架中。

2.3.2 TEST\_CASE

这个宏用于定义测试用例。这个宏承受一个参数 test_case_name,示意测试用例的名称。在宏定义中,它应用TEST_CASE_CLASS 宏定义测试用例类,并将测试用例类的名称作为参数传递给 TEST_CASE_CLASS 宏。

2.3.3 ASSERT\_XXX

ASSERT_XXX是一系列的宏,用于在每个独自的测试用例中校验执行后果是否合乎预期。如果执行后果不合乎预期,宏会中断以后用例的执行,并标记测试用例执行失败。

2.3.4 RUN\_ALL\_TESTS

这个宏用于运行所有注册的测试用例。这个宏定义了一个 main() 函数,并调用 UnitTest::UnitTestCore::GetInstance()->Run() 函数来运行所有的测试用例。

3. demo 示例

这个简略的单元测试框架代码,咱们保留在 github 上,地址为:https://github.com/wanmuc/UnitTest,欢送大家 fork 和 star。在仓库中有残缺的示例代码文件 demo_test.cpp。

正文完
 0