Google test初步分析.
现在常用的C++单元测试框架有 CppUnit,CxxTest,boost::test和google test。不像java/C#的测试框架,由于C++不支持reflection,所以,必须要做一些额外的工作,让框架知道相关内容的存在。CppUnit的做法是用宏进行注册。这种做法要求我们每添加一个测试,就要考虑用相应的宏进行注册,这种做法很繁琐,最大的问题在于由于疏忽而遗漏,这种靠人工保证的东西不可靠。在这点上,CxxTest 做得要好一些,有一个专门的脚本做这件事。通过这个脚本扫描这个自己编写的文件,生成一些新的文件,完成这个工作。从代码的表现力和可靠度来说,要好得多。唯一的问题是引入了一个脚本,而且这个脚本一般是由某些动态语言写成的(目前的CxxTest有Perl和Python的脚本),从而引入了对这种语言的依赖。而boost::test和google test 则是通过一些宏机制,来实现用户只需写一次就可以自动完成注册。我大概分析了一下gtest的实现机制。由于里面用了很多宏,比较费解,因此首先用cl /P /C命令将之展开,我的测试程序如下
#include <gtest/gtest.h>
TEST(FactorialTest, Negative) {
EXPECT_EQ(1, 1);
}
TEST(FactorialTest, Negative) {
EXPECT_EQ(1, 1);
}
宏展开完后主要部分如下
class FactorialTest_Negative_Test : public ::testing::Test
{
public: FactorialTest_Negative_Test() {}
private: virtual void TestBody();
static ::testing::TestInfo* const test_info_;
FactorialTest_Negative_Test(const FactorialTest_Negative_Test &);
void operator=(const FactorialTest_Negative_Test &);
};
//init test_info_
::testing::TestInfo* const FactorialTest_Negative_Test ::test_info_ = ::testing::internal::MakeAndRegisterTestInfo( "FactorialTest", "Negative", "", "", (::testing::internal::GetTestTypeId()), ::testing::Test::SetUpTestCase, ::testing::Test::TearDownTestCase, new ::testing::internal::TestFactoryImpl< FactorialTest_Negative_Test>);
void FactorialTest_Negative_Test::TestBody() {
// This test is named "Negative", and belongs to the "FactorialTest"
// test case.
switch (0) case 0: if (const ::testing::AssertionResult gtest_ar = (::testing::internal:: EqHelper<(sizeof(::testing::internal::IsNullLiteralHelper(1)) == 1)>::Compare("1", "Factorial(-5)", 1, Factorial(-5)))) ; else ::testing::internal::AssertHelper(::testing::TPRT_NONFATAL_FAILURE, "sample1_unittest.cc", 82, gtest_ar.failure_message()) = ::testing::Message();
}
{
public: FactorialTest_Negative_Test() {}
private: virtual void TestBody();
static ::testing::TestInfo* const test_info_;
FactorialTest_Negative_Test(const FactorialTest_Negative_Test &);
void operator=(const FactorialTest_Negative_Test &);
};
//init test_info_
::testing::TestInfo* const FactorialTest_Negative_Test ::test_info_ = ::testing::internal::MakeAndRegisterTestInfo( "FactorialTest", "Negative", "", "", (::testing::internal::GetTestTypeId()), ::testing::Test::SetUpTestCase, ::testing::Test::TearDownTestCase, new ::testing::internal::TestFactoryImpl< FactorialTest_Negative_Test>);
void FactorialTest_Negative_Test::TestBody() {
// This test is named "Negative", and belongs to the "FactorialTest"
// test case.
switch (0) case 0: if (const ::testing::AssertionResult gtest_ar = (::testing::internal:: EqHelper<(sizeof(::testing::internal::IsNullLiteralHelper(1)) == 1)>::Compare("1", "Factorial(-5)", 1, Factorial(-5)))) ; else ::testing::internal::AssertHelper(::testing::TPRT_NONFATAL_FAILURE, "sample1_unittest.cc", 82, gtest_ar.failure_message()) = ::testing::Message();
}
可以看出,gtest是利用static变量的初始化来实现函数注册的,主要函数为MakeAndRegisterTestInfo(),该函数定义在src\gtest.cc里面
TestInfo* MakeAndRegisterTestInfo(
const char* test_case_name, const char* name,
const char* test_case_comment, const char* comment,
TypeId fixture_class_id,
SetUpTestCaseFunc set_up_tc,
TearDownTestCaseFunc tear_down_tc,
TestFactoryBase* factory)
{
TestInfo* const test_info =
new TestInfo(test_case_name, name, test_case_comment, comment,
fixture_class_id, factory);
GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info); //关键是这一行
return test_info;
}
const char* test_case_name, const char* name,
const char* test_case_comment, const char* comment,
TypeId fixture_class_id,
SetUpTestCaseFunc set_up_tc,
TearDownTestCaseFunc tear_down_tc,
TestFactoryBase* factory)
{
TestInfo* const test_info =
new TestInfo(test_case_name, name, test_case_comment, comment,
fixture_class_id, factory);
GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info); //关键是这一行
return test_info;
}
果然不出所料,GetUnitTestImpl()->AddTestInfo(xx)这一句就是做注册,下面我仿照gtest自己写了一个简单的测试框架
#include <string>
#include <vector>
#include <iostream>
using namespace std;
class Test
{
public:
virtual bool testBody() = 0;
};
class TestInfo
{
public:
TestInfo(const char *fun,const char *file,int line, Test * pTest):
szFunction_(fun),szFile_(file),line_(line),test_(pTest)
{
}
bool RunTest()
{
return test_->testBody();
}
string szFunction_;
string szFile_;
int line_;
private:
Test *test_;
};
typedef vector < TestInfo *> TestInfosType;
TestInfosType testInfos;
TestInfo * MakeAndRegisterTestInfo(const char *fun,const char *file,int line, Test * pTest)
{
//cout << "MakeAndRegisterTestInfo..\n";
TestInfo * pInfo = new TestInfo(fun, file,line,pTest);
testInfos.push_back(pInfo);
return pInfo;
}
#define TEST(testName) \
class TEST_##testName: public Test \
{ \
public: \
virtual bool testBody(); \
static TestInfo * test_info_; \
}; \
TestInfo * TEST_##testName::test_info_ = MakeAndRegisterTestInfo(#testName,__FILE__,__LINE__,new TEST_##testName); \
bool TEST_##testName::testBody()
#define TEST_ASSERT(x) {if(!(x)) {cout << "AssertFail:" << #x << " File:" << test_info_->szFile_.c_str() << " Line:" << test_info_->line_ << " Function:" << test_info_->szFunction_.c_str() << endl;return false;}else cout<<"."<<endl;}
#define RUN_ALL_TEST() \
for(TestInfosType::iterator it = testInfos.begin(); \
it != testInfos.end(); ++it) \
{ \
TestInfo * pInfo = *it; \
pInfo->RunTest(); \
} \
#include <vector>
#include <iostream>
using namespace std;
class Test
{
public:
virtual bool testBody() = 0;
};
class TestInfo
{
public:
TestInfo(const char *fun,const char *file,int line, Test * pTest):
szFunction_(fun),szFile_(file),line_(line),test_(pTest)
{
}
bool RunTest()
{
return test_->testBody();
}
string szFunction_;
string szFile_;
int line_;
private:
Test *test_;
};
typedef vector < TestInfo *> TestInfosType;
TestInfosType testInfos;
TestInfo * MakeAndRegisterTestInfo(const char *fun,const char *file,int line, Test * pTest)
{
//cout << "MakeAndRegisterTestInfo..\n";
TestInfo * pInfo = new TestInfo(fun, file,line,pTest);
testInfos.push_back(pInfo);
return pInfo;
}
#define TEST(testName) \
class TEST_##testName: public Test \
{ \
public: \
virtual bool testBody(); \
static TestInfo * test_info_; \
}; \
TestInfo * TEST_##testName::test_info_ = MakeAndRegisterTestInfo(#testName,__FILE__,__LINE__,new TEST_##testName); \
bool TEST_##testName::testBody()
#define TEST_ASSERT(x) {if(!(x)) {cout << "AssertFail:" << #x << " File:" << test_info_->szFile_.c_str() << " Line:" << test_info_->line_ << " Function:" << test_info_->szFunction_.c_str() << endl;return false;}else cout<<"."<<endl;}
#define RUN_ALL_TEST() \
for(TestInfosType::iterator it = testInfos.begin(); \
it != testInfos.end(); ++it) \
{ \
TestInfo * pInfo = *it; \
pInfo->RunTest(); \
} \
使用该测试框架的方法如下
TEST(test1)
{
TEST_ASSERT(2==2);
TEST_ASSERT(2==1);
}
TEST(test2)
{
TEST_ASSERT(3==3);
TEST_ASSERT(2==3);
}
void main()
{
cout << "start test..\n";
RUN_ALL_TEST();
}
{
TEST_ASSERT(2==2);
TEST_ASSERT(2==1);
}
TEST(test2)
{
TEST_ASSERT(3==3);
TEST_ASSERT(2==3);
}
void main()
{
cout << "start test..\n";
RUN_ALL_TEST();
}
输出结果:
I:\VC2008\gtest-1.2.1>mySimulate.exe
start test..
.
AssertFail:2==1 File:mySimulate.cpp Line:66 Function:test1
.
AssertFail:2==3 File:mySimulate.cpp Line:72 Function:test2
尚有点问题就是,现实的错误行号其实为函数第一行的行号。