gtest测试用例打印台内容重定向

需求描述

在使用 gtest 做测试时,有时候需要记录测试过程中,测试对象运行时打印在控制台的相关内容,并根据打印的内容做校验,判断用例是否通过。

由于测试用例很多,我们希望能够根据测试集合和测试用例,自行建立具有层级关系的目录文件夹,将打印的内容以 txt 文档格式保存起来,便于后期整理测试报告。

需求分析

为解决上述需求,拆解出两个待实现功能:

  1. 打印内容重定向
  2. 测试集合和测试用例的名字获取

功能1:打印台内容重定向。

使用 freopen来实现。以下是一个小 demo。

int conSoleId; 
char logSaveFile[50] = "log.txt";
// [1]. 记录打印台id
conSoleId = dup(1);  
// [2]. 控制台信息重定向到文本
FILE* fp = freopen(logSaveFile, "w", stdout); 
// [3]. 测试代码
// your test code here
// [4]. 重新定向到控制台
dup2(conSoleId, 1);

功能2:测试集合和测试用例的名字获取。

gtest 的 TEST_F 是预处理宏定义,通过一层层的分析代码调用关系,可以发现,我们使用该宏时,实际上是先根据输入的测试集合和测试用例名字构建类,使之构成继承关系。接着注册用例信息,最后再把类中的测试内容函数TestBody()留给我们自己来写测试内容。

  • 首先 TEST_F 的内容实际由 GTEST_TEST_ 来实现
#define TEST_F(test_fixture, test_name)\
  GTEST_TEST_(test_fixture, test_name, test_fixture, \
              ::testing::internal::GetTypeId<test_fixture>())
  • 然后 GTEST_TEST_ 组织代码,实现了类的继承,和类内成员变量、函数的构建,最后将 TestBody() 函数体的具体内容交给使用者去定义。
// Helper macro for defining tests.
#define GTEST_TEST_(test_case_name, test_name, parent_class, parent_id)\
class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {\
 public:\
  GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {}\
 private:\
  virtual void TestBody();\
  static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;\
  GTEST_DISALLOW_COPY_AND_ASSIGN_(\
      GTEST_TEST_CLASS_NAME_(test_case_name, test_name));\
};\
\
::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)\
  ::test_info_ =\
    ::testing::internal::MakeAndRegisterTestInfo(\
        #test_case_name, #test_name, NULL, NULL, \
        ::testing::internal::CodeLocation(__FILE__, __LINE__), \
        (parent_id), \
        parent_class::SetUpTestCase, \
        parent_class::TearDownTestCase, \
        new ::testing::internal::TestFactoryImpl<\
            GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>);\
void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody()

在该类中的成员变量类型 TestInfo,定义如下:

class GTEST_API_ TestInfo {
 public:
  // Destructs a TestInfo object.  This function is not virtual, so
  // don't inherit from TestInfo.
  ~TestInfo();

  // Returns the test case name.
  const char* test_case_name() const { return test_case_name_.c_str(); }

  // Returns the test name.
  const char* name() const { return name_.c_str(); }

  // Returns the name of the parameter type, or NULL if this is not a typed
  // or a type-parameterized test.
  const char* type_param() const {
    if (type_param_.get() != NULL)
      return type_param_->c_str();
    return NULL;
  }

其中的 test_case_name() 可获取测试集合名,name() 可获取测试用例名。

因此在测试内容部分,我们可这样获取测试集合名和测试用例名:

TEST_F(PROCUT_TEST_EXAMPLE, say_hello)
{
	char test_case_name[50] = this->test_info_->test_case_name();
    char test_name[50] = this->test_info_->name();
}

需求实现

为了便于使用,将以上代码整合成一组宏:

// 测试结果存放的目录路径
std::string rootPath = "..\\..\\..\\data\\sit_test";

#define CONSOLE_TO_TXT_START \
int conSoleId;\
char logSavePath[100],mkDirCmd[100],nowTime[100],logSaveFile[100];\
sprintf(logSavePath,"%s\\%s\\%s",rootPath.c_str(),this->test_info_->test_case_name(),test_info_->name());\
sprintf(mkDirCmd,"if not exist \"%s\" (mkdir \"%s\")",logSavePath,logSavePath);\
system(mkDirCmd);\
time_t t = time(nullptr);\
struct tm* now = localtime(&t);\
strftime(nowTime, sizeof(nowTime), "%Y_%m_%d@%H_%M_%S", now);\
sprintf(logSaveFile,"%s\\log_%s.txt",logSavePath,nowTime);\
conSoleId = dup(1);\
FILE* fp = freopen(logSaveFile, "w", stdout);\
fflush(fp);\

#define CONSOLE_TO_TXT_END \
dup2(conSoleId, 1);\

在使用时可以这样调用:

TEST_F(PROCUT_TEST_EXAMPLE, say_hello)
{
    CONSOLE_TO_TXT_START
	// your test code here
    CONSOLE_TO_TXT_END
    SUCCEED();
}

这样我们在运行完该用例后,可以在 "..\..\..\data\sit_test\PROCUT_TEST_EXAMPLE\say_hello"路径下获得一份名字形如 log_2023_01_15@13_21_49.txt 的文档。

参考资料

posted @ 2023-01-15 23:29  GShang  阅读(857)  评论(0编辑  收藏  举报