从头实现一个简陋的单元测试框架

发表于:2023-1-16 09:51

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:亚瑟的微风    来源:知乎

  1. 目的
  日常用 GoogleTest 作为单元测试框架, 熟悉它的基本用法。想在练手的 C++ 工程中也写点单元测试, 想要一个比 GoogleTest 更轻量的测试框架: 只支持 >= C++11, header-only, 不带正则匹配, 输出有颜色区分等。 对于 C++ 不太熟悉, 那就尝试从头写一个来熟悉 C++ 吧!
  2. 思路
  颜色高亮输出: ascii escape chars 即可。
  检查表达式取值为 true 还是 false, 分别输出: 参照 assert 宏的实现,自行重新定义。
  注册单元测试函数:在 main() 函数运行前要注册每个单元测试函数, 每次注册时压栈一个函数到容器,容器放在全局唯一的一个类对象里(也就是Singleton)。容器元素类型是 std::function.
  一次性运行所有测试函数:获取Singleton后遍历容器里每个对象并调用执行。
  3. 实现
  最终完整实现代码如下:
  #include <stdlib.h>
  #include <functional>
  #include <string>
  #include <map>
  #include <vector>
  #if __ANDROID_API__ >= 8
  #include <android/log.h>
  #define MY_LOGE(...)                                                \
      do {                                                               \
          fprintf(stderr, ##__VA_ARGS__);                                \
          __android_log_print(ANDROID_LOG_WARN, "my", ##__VA_ARGS__); \
      } while (0)
  #else
  #include <stdio.h>
  #define MY_LOGE(...)                 \
      do {                                \
          fprintf(stderr, ##__VA_ARGS__); \
      } while (0)
  #endif
  #define MY_ESCAPE_COLOR_RED "\x1b[31m"
  #define MY_ESCAPE_COLOR_GREEN "\x1b[32m"
  #define MY_ESCAPE_COLOR_YELLOW "\x1b[33m"
  #define MY_ESCAPE_COLOR_END "\x1b[0m"
  void my_true_fail(const char* assertion, const char* filename, unsigned int linenumber, const char* function)
  {
      MY_LOGE("%s%s:%d: Failure%s\n", MY_ESCAPE_COLOR_RED, filename, linenumber, MY_ESCAPE_COLOR_END);
      MY_LOGE("Value of: %s\n  Actual: false\nExpected: true\n", assertion);
  }
  void my_true_success(const char* assertion, const char* filename, unsigned int linenumber, const char* function)
  {
      MY_LOGE("%s:%d %s(): Assertion %s`%s' ok%s.\n", filename, linenumber, function, MY_ESCAPE_COLOR_GREEN, assertion, MY_ESCAPE_COLOR_END);
  }
  void my_equal_fail(const char* expected, const char* actual, const char* filename, unsigned int linenumber, const char* function)
  {
      MY_LOGE("%s%s:%d: Failure%s\n", MY_ESCAPE_COLOR_RED, filename, linenumber, MY_ESCAPE_COLOR_END);
      MY_LOGE("Expected: %s\n  Actual: %s\nExpected: true\n", expected, actual);
  }
  void my_equal_success(const char* expected, const char* actual, const char* filename, unsigned int linenumber, const char* function)
  {
      MY_LOGE("%s:%d %s(): %s`MY_EXPECTED_EQ(%s, %s)' ok%s.\n", filename, linenumber, function, MY_ESCAPE_COLOR_GREEN, expected, actual, MY_ESCAPE_COLOR_END);
  }
  #define MY_EXPECT_TRUE(expr) \
      (static_cast<bool>(expr)    \
      ? my_true_success(#expr, __FILE__, __LINE__, __FUNCTION__) \
      : my_true_fail(#expr, __FILE__, __LINE__, __FUNCTION__))
  #define MY_ASSERT_TRUE(expr) \
      if (static_cast<bool>(expr)) {   \
          my_true_success(#expr, __FILE__, __LINE__, __FUNCTION__); \
      } \
      else { \
          my_true_fail(#expr, __FILE__, __LINE__, __FUNCTION__); \
          return; \
      }
  #define MY_EXPECT_EQ(expected, actual) \
      (static_cast<bool>(expected == actual)  \
      ? my_equal_success(#expected, #actual, __FILE__, __LINE__, __FUNCTION__) \
      : my_equal_fail(#expected, #actual, __FILE__, __LINE__, __FUNCTION__));
  #define MY_ASSERT_EQ(expected, actual) \
      if (static_cast<bool>(expected == actual)) {    \
          my_equal_success(#expected, #actual, __FILE__, __LINE__, __FUNCTION__); \
      } \
      else { \
          my_equal_fail(#expected, #actual, __FILE__, __LINE__, __FUNCTION__); \
          return; \
      }
  class TestEntity
  {
  private:
      TestEntity() { };
      ~TestEntity() { };
  public:
      TestEntity(const TestEntity& other) = delete;
      TestEntity operator=(const TestEntity& other) = delete;
      
      static TestEntity& get_instance()
      {
          static TestEntity entity;
          return entity;
      }
      int add(std::function<void()> f)
      {
          test_funcs.push_back(f);
          return 0;
      }
      int run_all_test_functions()
      {
          for (auto f : test_funcs)
          {
              f();
          }
          return 0;
      }
  private:
      std::vector<std::function<void()>> test_funcs;
  };
  #define MY_TEST(name) \
      void my_test_##name(); \
      int my_test_mark_##name = TestEntity::get_instance().add(my_test_##name); \
      void my_test_##name()
  #define MY_UNUSED(x) (void)(x)
  #define MY_RUN_ALL_TESTS() \
      int my_test_invoker = TestEntity::get_instance().run_all_test_functions(); \
      MY_UNUSED(my_test_invoker)
  调用代码举例:
  // Returns the factorial of n
  int Factorial(int n)
  {
      if (n == 1 || n == 2) return n;
      return Factorial(n - 1) + Factorial(n - 2);
  }
  MY_TEST(equal)
  {
      int p = 3;
      MY_EXPECT_TRUE(p == 3);
  }
  MY_TEST(factorial)
  {
      // MY_EXPECT_EQ(Factorial(1), 1);
      // MY_EXPECT_EQ(Factorial(2), 2);
      // MY_EXPECT_EQ(Factorial(3), 6);
      // MY_EXPECT_EQ(Factorial(8), 40320);
      // MY_ASSERT_EQ(Factorial(1), 1);
      // MY_ASSERT_EQ(Factorial(2), 2);
      // MY_ASSERT_EQ(Factorial(3), 6);
      // MY_ASSERT_EQ(Factorial(8), 40320);
      // MY_ASSERT_TRUE(Factorial(1) == 1);
      // MY_ASSERT_TRUE(Factorial(2) == 2);
      // MY_ASSERT_TRUE(Factorial(3) == 6);
      // MY_ASSERT_TRUE(Factorial(8) == 40320);
      MY_EXPECT_TRUE(Factorial(1) == 1);
      MY_EXPECT_TRUE(Factorial(2) == 2);
      MY_EXPECT_TRUE(Factorial(3) == 6);
      MY_EXPECT_TRUE(Factorial(8) == 40320);
  }
  int main()
  {
      MY_RUN_ALL_TESTS();
      return 0;
  }
  运行结果:
  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号