gtest测试用例打印台内容重定向
需求描述
在使用 gtest
做测试时,有时候需要记录测试过程中,测试对象运行时打印在控制台的相关内容,并根据打印的内容做校验,判断用例是否通过。
由于测试用例很多,我们希望能够根据测试集合和测试用例,自行建立具有层级关系的目录文件夹,将打印的内容以 txt 文档格式保存起来,便于后期整理测试报告。
需求分析
为解决上述需求,拆解出两个待实现功能:
- 打印内容重定向
- 测试集合和测试用例的名字获取
功能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
的文档。
参考资料
未经作者授权,禁止转载
THE END