公司现在需要一套成型的测试框架,选中了现在开源的gtest测试框架,公司将这个任务交给了我,要在移植gtest框架基础上进行修改,增加新的框架功能,这几天一直在看gtest源码,不懂C++,看起来有点难,不过还是有一些感悟,写下来以备后用
gtest测试框架是在不同平台上(Linux,Mac OS X,Windows,Cygwin,Windows CE和Symbian)为编写C++测试而生成的。它是基于xUnit架构的测试框架,丰富的断言集,用户定义的断言,death测试,
致命与非致命的失败,类型参数化测试,各类运行测试的选项和XML的测试报告。
经过一段时间的开发,在gtest原框架基础上添加了本公司自己需求的框架功能。
新功能主要有:
(1)命令行选项增加--gtest_order_filter,与原有的--gtest_filter过滤选项配合,可以指定选中test case的运行顺序,便于运行固定顺序的测试
(2)增加一套test参数传递机制,使用INI文件为所有case配置参数,参数支持多种类型,gtest架构中加入利用静态变量动态初始化的参数注册、运行前参数分析、运行中获取参数等功能,用户仅需注册以及获取即可,还可列出所有参数,并根据注册参数生成INI模板
(3)支持test unit、test case、test info这3级每一项的repeat,便于针对某一项反复测试,repeat次数由INI中指定,并且test info支持repeat时有多套参数,每一次repeat传入参数不一样。
对于死亡测试,公司暂时不需要这个功能,因此没有细看
以我的理解,gtest的运行分为大体分为2部分,注册和运行,首先来说注册
1 test case register —— TEST_F/TEST
对于test case,我们是通过使用宏TEST/TEST_F等宏定义来将断言语句进行注册的。咱们以TEST_F为例,如下:
47 TEST_F(Zk_Env_test, zk1) 48 { 49 EXPECT_EQ(1, test_func(1, 2)); 50 }
48-50行就是咱们test case的函数体,那这个TEST_F宏是如何做的呢,
gtest源码中有大量的宏定义,跟踪TEST_F宏,层层递进:
include/gtest/gtest.h:
2324 #define GTEST_TEST(test_case_name, test_name)\ 2325 GTEST_TEST_(test_case_name, test_name, \ 2326 ::testing::Test, ::testing::internal::GetTestTypeId()) 2327 2328 // Define this macro to 1 to omit the definition of TEST(), which 2329 // is a generic name and clashes with some other libraries. 2330 #if !GTEST_DONT_DEFINE_TEST 2331 # define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name) 2332 #endif 。。。 2359 2360 #define TEST_F(test_fixture, test_name)\ 2361 GTEST_TEST_(test_fixture, test_name, test_fixture, \ 2362 ::testing::internal::GetTypeId<test_fixture>())
include/gtest/internal/gtest_internal.h
1131 // Expands to the name of the class that implements the given test. 1132 #define GTEST_TEST_CLASS_NAME_(test_case_name, test_name) \ 1133 test_case_name##_##test_name##_Test 1134 1135 // Helper macro for defining tests. 1136 #define GTEST_TEST_(test_case_name, test_name, parent_class, parent_id)\ 1137 class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {\ 1138 public:\ 1139 GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {}\ 1140 private:\ 1141 virtual void TestBody();\ 1142 static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;\ 1143 GTEST_DISALLOW_COPY_AND_ASSIGN_(\ 1144 GTEST_TEST_CLASS_NAME_(test_case_name, test_name));\ 1145 };\ 1146 \ 1147 ::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)\ 1148 ::test_info_ =\ 1149 ::testing::internal::MakeAndRegisterTestInfo(\ 1150 #test_case_name, #test_name, NULL, NULL, \ 1151 (parent_id), \ 1152 parent_class::SetUpTestCase, \ 1153 parent_class::TearDownTestCase, \ 1154 new ::testing::internal::TestFactoryImpl<\ 1155 GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>);\ 1156 void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody()
从TEST_F到GTEST_TEST_,最后到test_case_name##_##test_name##_Test类的创建,这段宏定义看了老久才弄明白,宏定义我总结一定要记住2点:
(1)宏定义在预处理展开,写或者看宏定义时可以想象将该宏定义展开到宏定义位置语法是否正确,只要正确,怎么写都可以
(2)对于多行宏定义,每行最后肯定要有‘\’,只有最后一行可以没有,这样可以很简单的确定宏定义在哪里结束。
根据这两点,可以看出上面最大的宏定义是在gtest_internal.h的1156行结束,想象将它在咱们的例子中展开,很明显,咱们48-50行的函数体真正的是GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody的函数体!
其实后来我发现了一个更简单的办法来查看这种复杂宏定义的展开情况,那就是利用编译器,只让它与处理一下,如下:
gcc -E zk_test.cc -I ../include/ -o zk_test.i
生成.i文件,这个文件是预处理所有宏都已展开,看起来更方便
这个宏定义搞明白之后,需要看一下展开后做了什么,可以看出定义了一个class,并且对该类的静态变量test_info_调用MakeAndRegisterTestInfo赋值。
对于这个操作按照C语言的语法来说,静态变量初始化是在编译时进行的,必须是常量,编译完成,静态变量的初值就确定了,但是这里却是调用函数来赋值,按照C语法肯定编译不过呀。
我写了一个简单的C程序测试,如下:
1 #include <stdio.h> 2 3 int myadd(int a, int b) 4 { 5 return a + b; 6 } 7 8 static int num = myadd(2, 3); 9 10 int main(void) 11 { 12 printf("my main : num = %d\n", num); 13 return 0; 14 }
用gcc编译,的确是编译出错,静态变量赋值不是常量。
但是用g++编译(C++是C语言的超集,所以g++可以直接编译C源码),编译通过,运行打印都没有问题!
这个让我很纳闷。。。在网上搜了一通,找到一种说法
g++编译器比较智能,对于动态赋值的静态变量会做为未初始化的静态变量存储,在运行时调用动态赋值函数来赋值,而不会报错!
按照上面测试程序,还可以推测这个动态赋值是在执行main函数之前进行的,这个可以通过反汇编进行验证,有时间需要验证一下。
有这样一个功能,gtest就可以在运行main函数之前利用静态变量赋值的方法来调用MakeAndRegisterTestInfo函数。
2808 TestInfo* MakeAndRegisterTestInfo( 2809 const char* test_case_name, 2810 const char* name, 2811 const char* type_param, 2812 const char* value_param, 2813 TypeId fixture_class_id, 2814 SetUpTestCaseFunc set_up_tc, 2815 TearDownTestCaseFunc tear_down_tc, 2816 TestFactoryBase* factory) { 2817 TestInfo* const test_info = 2818 new TestInfo(test_case_name, name, type_param, value_param, 2819 fixture_class_id, factory); 2820 GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info); 2821 return test_info; 2822 } 676 void AddTestInfo(Test::SetUpTestCaseFunc set_up_tc, 677 Test::TearDownTestCaseFunc tear_down_tc, 678 TestInfo* test_info) { 679 // In order to support thread-safe death tests, we need to 680 // remember the original working directory when the test program 681 // was first invoked. We cannot do this in RUN_ALL_TESTS(), as 682 // the user may have changed the current directory before calling 683 // RUN_ALL_TESTS(). Therefore we capture the current directory in 684 // AddTestInfo(), which is called to register a TEST or TEST_F 685 // before main() is reached. 686 if (original_working_dir_.IsEmpty()) { 687 original_working_dir_.Set(FilePath::GetCurrentDir()); 688 GTEST_CHECK_(!original_working_dir_.IsEmpty()) 689 << "Failed to get the current working directory."; 690 } 691 692 GetTestCase(test_info->test_case_name(), 693 test_info->type_param(), 694 set_up_tc, 695 tear_down_tc)->AddTestInfo(test_info); 696 }
首先创建新的test info,调用AddTestInfo,AddTestInfo函数会调用GetTestCase,从test_cases_数组中查找想要获取的test case,如果没有就新建一个,添加到test_cases_数组中,然后调用对应TestCase的addTestInfo函数来添加test_info到test_info_list_。
这里需要解释一下的是,在gtest的框架中TestInfo类就是对应咱们所说的test case,TestCase类对应咱们所说的test suit,gtest框架有2级,一级是全局TestCase数组test_cases_,包含所有咱们注册的test suit,二级是每个TestCase类中的TestInfo数组test_info_list_,包含所有咱们注册的test case。
注册就说完了,总结一下,gtest是利用静态变量动态初始化,调用函数MakeAndRegisterTestInfo,来实现了对test case注册的。
2 all case run —— RunAllTests
这是gtest的核心部分,gtest如何来调度所有case的运行,我做了一张流程图如下:
未完待续。。。