C++單元測試框架——gtest——v_raw
gtest簡介
gtest是一個跨平臺的C++單元測試框架话瞧。它提供了豐富的斷言、致命和非致命判斷、參數(shù)化几晤、死亡測試等等。
調(diào)用流程和框架
gtest的運行可大體分為兩部分:注冊和運行植阴。首先來說注冊蟹瘾。
注冊流程:
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???}
有這樣一個功能,gtest就可以在運行main函數(shù)之前利用靜態(tài)變量賦值的方法來調(diào)用MakeAndRegisterTestInfo函數(shù)掠手。
在MakeAndRegisterTestInfo函數(shù)中憾朴,首先創(chuàng)建新的test_info,調(diào)用AddTestInfo惨撇,AddTestInfo函數(shù)會調(diào)用GetTestCase伊脓,從test_cases_數(shù)組中查找想要獲取的test_case府寒,如果沒有就新建一個魁衙,添加到test_cases_數(shù)組中,然后調(diào)用對應的TestCase的addTestInfo函數(shù)來添加test_info到test_info_list_.
這里需要解釋一下的是株搔,在gtest的框架中TestInfo類就是我們所說的test_case剖淀,TestCase類對應我們所說的test_suit,gtest框架有2級纤房,一級是全局TestCase數(shù)組test_cases_纵隔,包含所有我們注冊的test_suit,二級是每個TestCase類中的TestInfo數(shù)組test_info_list_炮姨,包含所有我們注冊的test_case.
注冊就說完了捌刮,總結(jié)一下,gtest是利用靜態(tài)變量動態(tài)初始化舒岸,調(diào)用函數(shù)MakeAndRegisterTestInfo绅作,來實現(xiàn)了對test_case注冊的。
(注意點:
全局變量的初始化順序是不確定的蛾派。編譯器不能定義不同編譯單元全局變量的初始化順序俄认。
在同一個cpp文件里,編譯器會保證按照他們出現(xiàn)的順序初始化洪乍。但是如果兩個全局變量在不同的編譯單元眯杏,編譯器不能控制他們的構(gòu)造順序,如果項目對其順序有要求壳澳,則我們需要想辦法確定他們的構(gòu)造順序岂贩。)
運行框架
TEST宏總結(jié)
首先聲明一個以test_case_name和test_name拼接成的類,所以需要保證test_case_name和test_name在代碼中唯一巷波,避免命名沖突萎津。
拼接的類中有一個靜態(tài)成員變量test_info_科平,該成員變量的唯一作用是利用類靜態(tài)變量在main之前初始化,對測試用例的信息進行注冊姜性。這是GTEST的最精華部分
通過MakeAndRegisterTestInfo函數(shù)對test_info_進行賦初值的過程瞪慧,對測試用例的信息進行注冊。因為UnitTest和UnitTestImpl均實現(xiàn)為單例部念,實現(xiàn)了注冊測試用例信息到GTest中弃酌。
通過宏定義,實現(xiàn)了TestBody()的邏輯儡炼,即我們需要的測試代碼妓湘,這部分會在運行測試用例時被調(diào)用。
TEST宏的主要目的在于提取注冊測試用例信息乌询,為后續(xù)運行測試用例做準備榜贴。
GTest自動化測試
對于一個測試自動化框架,主要通過三個方面對其進行評估妹田,分別是數(shù)據(jù)驅(qū)動唬党,測試結(jié)果和異常處理。
數(shù)據(jù)驅(qū)動能力
GTest中有三種方法:分別是測試固件類(Test Fixture)鬼佣、值參數(shù)化測試(Value-Parameterized Test)和類型參數(shù)化測試(Type-Parameterized Test)
測試固件 (Test Fixture)
在TEST()的使用中驶拱,我們接觸了一個測試用例包含多個測試實例的組織方式。多個測試實例可能需要進行相似的數(shù)據(jù)配置和初始化操作晶衷,為此蓝纲,Gtest提供了測試固件(Test fixture)幫助我們進行數(shù)據(jù)管理。測試固件類可以使得不同的測試共享相同的測試數(shù)據(jù)配置晌纫。
所有的fixture的定義都必須繼承::testing::test類税迷,該類其實就是一個接口,定義了SetUp和TearDown接口函數(shù)锹漱,對負責測試用例執(zhí)行前后環(huán)境的生成和撤銷箭养。
值參數(shù)化測試
值參數(shù)化測試和Fixture類的作用類似,也是使得不同的測試共享相同的測試數(shù)據(jù)配置凌蔬,但是略有不同露懒。
類型參數(shù)化測試
類型參數(shù)化測試,和值參數(shù)化測試相似砂心,不同的是用類型來作為參數(shù)懈词,故需要使用模板技術(shù)來實現(xiàn)。其優(yōu)點是可以對操作類似或者相同的類型辩诞,使用一個Fixture模板來實現(xiàn)所有類的測試用例坎弯。
上面的三種機制可以使得在編寫測試程序的時候減少代碼量。但是,GTest目前仍然沒有提供一套內(nèi)置的完整的數(shù)據(jù)驅(qū)動機制抠忘,故仍存在測試用例和測試數(shù)據(jù)維護管理麻煩等問題撩炊。
gtest解析
TEST(test_case_name, test_name)
可以看到TEST宏經(jīng)過預處理器處理后展開為:
定義了一個繼承自::testing::test類的新類test_case_name_test_name_Test,該類的名字為TEST宏兩個形參的拼接而成.
TEST宏中的測試代碼被展開并定義為生成類的成員函數(shù)TestBody的函數(shù)體。
生成類的靜態(tài)數(shù)據(jù)成員test_info_被初始化為函數(shù)MakeAndRegisterTestInfo的返回值崎脉。具體意義后面再介紹拧咳。
聲明了:private類型 的copy函數(shù)和assign函數(shù),因此不允許copy和assign操作
MakeAndRegisterTestInfo函數(shù)
從上面來看MakeAndRegisterTestInfo函數(shù)是一個比較關(guān)鍵的函數(shù)了囚灼,從字面意思上看就是生成并注冊該案例的信息骆膝,在頭文件gtest.cc中可以找到關(guān)于它的定義,它是一個testing命名空間中嵌套命名空間internal中的非成員函數(shù):
TestInfo* MakeAndRegisterTestInfo(
????const?char* test_case_name,?const?char* name,
????const?char* type_param,
????const?char* value_param,
????TypeId fixture_class_id,
????SetUpTestCaseFunc set_up_tc,
????TearDownTestCaseFunc tear_down_tc,
????TestFactoryBase* factory) {
??TestInfo*?const?test_info =
??????new?TestInfo(test_case_name, name, type_param, value_param,
???????????????????fixture_class_id, factory);
??GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info);
??return?test_info;
}
其中形參的意義如下:
test_case_name:測試套名稱灶体,即TEST宏中的第一個形參阅签。
name:測試案例名稱。
type_param:測試套的附加信息蝎抽。默認為無
value_param:測試案例的附加信息政钟。默認為無
fixture_class_id:test fixture類的id
set_up_tc :函數(shù)指針,指向函數(shù)SetUpTestCaseFunc
tear_down_tc:函數(shù)指針樟结,指向函數(shù)TearDownTestCaseFunc
factory:指向工廠對象的指針养交,該工廠對象創(chuàng)建上面TEST宏生成的測試類的對象
我們看到在MakeAndRegisterTestInfo函數(shù)體中定義了一個TestInfo對象,該對象包含了一個TEST宏中標識的測試案例的測試套名稱狭吼、測試案例名稱层坠、測試套附加信息殖妇、測試案例附加信息刁笙、創(chuàng)建測試案例類對象的工廠對象的指針這些信息。
所謂的工廠對象谦趣,可以在gtest-internal.h中找到它的定義
template?<class?TestClass>
class?TestFactoryImpl :?public?TestFactoryBase {
?public:
??virtual?Test* CreateTest() {?return?new?TestClass; }
};
TestFactoryImpl類是一個模板類疲吸,它的作用就是單純的創(chuàng)建對應于模板形參類型的測試案例對象。因為模板的存在也大大簡化了代碼前鹅,否則可能就要寫無數(shù)個TestFactoryImpl類
GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info);
乍一看似乎是對test_info對象的一些熟悉信息進行設(shè)置摘悴。究竟是怎么樣呢?源碼面前舰绘,了無秘密蹂喻,我們還是得去找到它的源碼,在gtest-internal-inl中可以找到它的定義
inline?UnitTestImpl* GetUnitTestImpl() {
??return?UnitTest::GetInstance()->impl();
}
它的實現(xiàn)也是非常簡單捂寿,關(guān)鍵還是在UnitTest類的成員函數(shù)GetInstance和返回類型的成員函數(shù)impl口四,繼續(xù)追蹤下去
class?GTEST_API_ UnitTest {
public:
// Gets the singleton UnitTest object.? The first time this method
// is called, a UnitTest object is constructed and returned.
// Consecutive calls will return the same object.
??static?UnitTest* GetInstance();
??internal::UnitTestImpl* impl() {?return?impl_; }
??const?internal::UnitTestImpl* impl()?const?{?return?impl_; }
private:
??mutable?internal::Mutex mutex_;
??internal::UnitTestImpl* impl_;
}
UnitTest * UnitTest::GetInstance() {
#if (_MSC_VER == 1310 && !defined(_DEBUG)) || defined(__BORLANDC__)
???static?UnitTest*?const?instance =?new?UnitTest;
???return?instance;
#else
???static?UnitTest instance;
???return?&instance;
}
根據(jù)代碼和注釋可知GetInstance是Unitest類的成員函數(shù),它僅僅是生成一個靜態(tài)的UniTest對象然后返回秦陋。實際上這么做是為了實現(xiàn)UniTest類的單例(Singleton)實例蔓彩。而impl只是單純的返回UniTest的UnitTestImpl類型的指針數(shù)據(jù)成員impl_。
再聯(lián)系之前的代碼,通過UnitTestImpl類的AddTestInfo設(shè)置Test_Info類對象的信息赤嚼。其實繞了一圈旷赖,最終就是通過AddTestInfo設(shè)置Test_info類對象的信息,自然地更卒,我們需要知道AddTestInfo的實現(xiàn)啦:
void?AddTestInfo(Test::SetUpTestCaseFunc set_up_tc,
???????????????????Test::TearDownTestCaseFunc tear_down_tc,
???????????????????TestInfo* test_info) {
????GetTestCase(test_info->test_case_name(),
????????????????test_info->type_param(),
????????????????set_up_tc,
????????????????tear_down_tc)->AddTestInfo(test_info);
}
而AddTestInfo是通過GetTestCase函數(shù)實現(xiàn)的
TestCase* UnitTestImpl::GetTestCase(const?char* test_case_name,
????????????????????????????????????const?char* type_param,
????????????????????????????????????Test::SetUpTestCaseFunc set_up_tc,
????????????????????????????????????Test::TearDownTestCaseFunc tear_down_tc) {
??// Can we find a TestCase with the given name?
??const?std::vector<TestCase*>::const_iterator test_case =
??????std::find_if(test_cases_.begin(), test_cases_.end(),
???????????????????TestCaseNameIs(test_case_name));
??if?(test_case != test_cases_.end())
????return?*test_case;
??// No.? Let's create one.
??TestCase*?const?new_test_case =
??????new?TestCase(test_case_name, type_param, set_up_tc, tear_down_tc);
??// Is this a death test case?
??if?(internal::UnitTestOptions::MatchesFilter(String(test_case_name),
???????????????????????????????????????????????kDeathTestCaseFilter)) {
????// Yes.? Inserts the test case after the last death test case
????// defined so far.? This only works when the test cases haven't
????// been shuffled.? Otherwise we may end up running a death test
????// after a non-death test.
????++last_death_test_case_;
????test_cases_.insert(test_cases_.begin() + last_death_test_case_,
???????????????????????new_test_case);
??}?else?{
????// No.? Appends to the end of the list.
????test_cases_.push_back(new_test_case);
??}
??test_case_indices_.push_back(static_cast<int>(test_case_indices_.size()));
??return?new_test_case;
}
從上面代碼可以看出其實并不是一開始猜測的設(shè)置Test_Info對象的信息等孵,而是判斷包含Test_info對象中的測試套名稱、測試案例名稱等信息的TestCase對象的指針是否在一個vector向量中蹂空,若存在就返回這個指針流济;若不存在就把創(chuàng)建一個包含這些信息的TestCase對象的指針加入到vector向量中,并返回這個指針腌闯。
至于vector向量test_cases_绳瘟,它是UnitTestImpl中的私有數(shù)據(jù)成員,在這個向量中存放了整個測試項目中所有包含測試套姿骏、測試案例等信息的TestCase對象的指針糖声。
緊接著我們看到從GetTestCase返回的TestCase指針調(diào)用TestCase類中的成員函數(shù)AddTestInfo,在gtest.cc中可以找到它的定義如下:
void?TestCase::AddTestInfo(TestInfo * test_info) {
??test_info_list_.push_back(test_info);
??test_indices_.push_back(static_cast<int>(test_indices_.size()));
}
調(diào)用這個函數(shù)的目的是在于將Test_info對象添加到test_info_list_中分瘦,而test_info_list_是類TestCase中的私有數(shù)據(jù)成員蘸泻,它也是一個vector向量。原型為
std::vector<TestInfo*> test_info_list_;
該向量保存著整個項目中所有包含測試案例對象各種信息的Test_Info對象的指針嘲玫。
而test_indices_也是類TestCase中的私有數(shù)據(jù)成員悦施,保存著test_info_list中每個元素的索引號。它仍然是一個vector向量去团,原型為
std::vector<int> test_indices_;
gtest系列之TEST宏
看一下TEST宏的具體定義實現(xiàn):
#if !GTEST_DONT_DEFINE_TEST
# define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name)
#endif
#define GTEST_TEST(test_case_name, test_name)\
??GTEST_TEST_(test_case_name, test_name, \
??????????????::testing::Test, ::testing::internal::GetTestTypeId())
#define TEST_F(test_fixture, test_name)\
??GTEST_TEST_(test_fixture, test_name, test_fixture, \
??????????????::testing::internal::GetTypeId<test_fixture>())
可以看到抡诞,TEST宏和事件機制對應的TEST_F宏都是調(diào)用了GTEST_TEST_宏,我們再追蹤這個宏的定義
#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, \
????????(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()
可以看到在預處理展開中的代碼了土陪,其中GTEST_TEST_CLASS_NAME_昼汗,從字面意思可以知道這宏就是獲得類的名字
#define GTEST_TEST_CLASS_NAME_(test_case_name, test_name) \
??test_case_name##_##test_name##_Test
果不其然,宏GTEST_TEST_CLASS_NAME的功能就是把兩個參數(shù)拼接為一個參數(shù)鬼雀。
RUN_ALL_TEST宏
我們的測試程序就是從main函數(shù)的RUN_ALL_TESTS的調(diào)用開始的顷窒,在gtest.h中可以找到該宏的定義
#define RUN_ALL_TESTS()\
??(::testing::UnitTest::GetInstance()->Run())
RUN_ALL_TESTS就是簡單的調(diào)用UnitTest的成員函數(shù)GetInstance,我們知道GetInstance就是返回一個單例(Singleton)UnitTest對象源哩,該對象調(diào)用成員函數(shù)Run
int?UnitTest::Run() {
??impl()->set_catch_exceptions(GTEST_FLAG(catch_exceptions));
??return?internal::HandleExceptionsInMethodIfSupported(
??????impl(),
??????&internal::UnitTestImpl::RunAllTests,
?????"auxiliary test code (environments or event listeners)") ? 0 : 1;
}
Run()函數(shù)也是簡單的調(diào)用HandleExceptionInMethodIfSupported函數(shù)鞋吉,追蹤它的實現(xiàn)
template?<class?T,?typename?Result>
Result HandleExceptionsInMethodIfSupported(
????T* object, Result (T::*method)(),?const?char* location) {
????if?(internal::GetUnitTestImpl()->catch_exceptions()) {
?????......??//異常處理省略
?????}?else?{
?????return?(object->*method)();
???}
?}
HandleExceptionsInMethodIfSupported是一個模板函數(shù),他的模板形參具現(xiàn)化為調(diào)用它的UnitTestImpl和int励烦,也就是T = UnitTestImpl谓着, Result = int。在函數(shù)體里調(diào)用UnitTestImpl類的成員函數(shù)RunAllTests
bool?UnitTestImpl::RunAllTests() {
????......
????const?TimeInMillis start = GetTimeInMillis();??//開始計時
????if?(has_tests_to_run && GTEST_FLAG(shuffle)) {
???????random()->Reseed(random_seed_);
???????ShuffleTests();
?????}
?????repeater->OnTestIterationStart(*parent_, i);
?????if?(has_tests_to_run) {
???????//初始化全局的SetUp事件
???????repeater->OnEnvironmentsSetUpStart(*parent_);
???????//順序遍歷注冊全局SetUp事件
???????ForEach(environments_, SetUpEnvironment);
???????//初始化全局TearDown事件
???????repeater->OnEnvironmentsSetUpEnd(*parent_);
???????//
???????// set-up.
???????if?(!Test::HasFatalFailure()) {
?????????for?(int?test_index = 0; test_index < total_test_case_count();
??????????????test_index++) {
???????????GetMutableTestCase(test_index)->Run();?//TestCase::Run
?????????}
???????}
??????// 反向遍歷取消所有全局事件.
??????repeater->OnEnvironmentsTearDownStart(*parent_);
?????std::for_each(environments_.rbegin(), environments_.rend(),
????????????????????TearDownEnvironment);
??????repeater->OnEnvironmentsTearDownEnd(*parent_);
????}
????elapsed_time_ = GetTimeInMillis() - start;?//停止計時
????......
}
如上面代碼所示崩侠,UnitTestImpl::RunAllTests主要進行全局事件的初始化漆魔,以及變量注冊坷檩。而真正的執(zhí)行部分在于調(diào)用GetMutableTestCase
TestCase* UnitTest::GetMutableTestCase(int?i) {
??return?impl()->GetMutableTestCase(i);?//impl返回UnitTestImpl類型指針
}
TestCase* UnitTestImpl:: GetMutableTestCase(int?i) {
????const?int?index = GetElementOr(test_case_indices_, i, -1);
????return?index < 0 ? NULL : test_cases_[index];
}
經(jīng)過兩次調(diào)用返回vector向量test_cases_中的元素,它的元素類型為TestCase類型改抡。然后調(diào)用TestCase::Run
void?TestCase::Run() {
??......??//省略
??const?internal::TimeInMillis start = internal::GetTimeInMillis();
??for?(int?i = 0; i < total_test_count(); i++) {
????GetMutableTestInfo(i)->Run();?//調(diào)用TestCase::GetMutableTestInfo
??}?????????????????????????????????????//以及Test_Info::Run
??......?//省略
}
TestInfo* TestCase::GetMutableTestInfo(int?i) {
??const?int?index = GetElementOr(test_indices_, i, -1);
??return?index < 0 ? NULL : test_info_list_[index];
}
看到又轉(zhuǎn)向調(diào)用TestCase::GetMutableTestInfo矢炼,返回向量test_info_list_的元素。而它的元素類型為Test_info阿纤。進而又轉(zhuǎn)向了Test_info::Run
void?TestInfo::Run() {
??......??//省略
??Test*?const?test = internal::HandleExceptionsInMethodIfSupported(
??????factory_, &internal::TestFactoryBase::CreateTest,
??????"the test fixture's constructor");
??......??//省略
????test->Run();??// Test::Run
??......???//省略
??}
在TestInfo::Run中調(diào)用了HandleExceptionsInMethodIfSupported句灌,通過上文中的分析可以得知該函數(shù)在這個地方最終的作用是調(diào)用internal::TestFactoryBase::CreateTest將factor_所指的工廠對象創(chuàng)建的測試案例對象的地址賦給Test類型的指針test。所以最后調(diào)用了Test::Run欠拾。
void?Test::Run() {
??if?(!HasSameFixtureClass())?return;
??internal::UnitTestImpl*?const?impl = internal::GetUnitTestImpl();
??impl->os_stack_trace_getter()->UponLeavingGTest();
??internal::HandleExceptionsInMethodIfSupported(this, &Test::SetUp,?"SetUp()");
??// We will run the test only if SetUp() was successful.
??if?(!HasFatalFailure()) {
????impl->os_stack_trace_getter()->UponLeavingGTest();
????internal::HandleExceptionsInMethodIfSupported(
????????this, &Test::TestBody,?"the test body");
??}
??// However, we want to clean up as much as possible.? Hence we will
??// always call TearDown(), even if SetUp() or the test body has
??// failed.
??impl->os_stack_trace_getter()->UponLeavingGTest();
??internal::HandleExceptionsInMethodIfSupported(
??????this, &Test::TearDown,?"TearDown()");
}
在Test::Run函數(shù)體中我們看到通過HandleExceptionsInMethodIfSupported調(diào)用了TestBody胰锌,先來看看Test中TestBody的原型聲明
virtual?void?TestBody() = 0;
TestBody被聲明為純虛函數(shù)。一切都明朗了藐窄,在上文中通過test調(diào)用Test::Run资昧,進而通過test::調(diào)用TestBody,而test實際上是指向繼承自Test類的案例類對象荆忍,進而發(fā)生了多態(tài)格带,調(diào)用的是Test_foo_test_normal_Test::TestBody,也就是我們最初在TEST或者TEST_F宏中所寫的測試代碼刹枉。
如此遍歷叽唱,就是順序執(zhí)行測試demo程序中所寫的每一個TEST宏的函數(shù)體啦。
總結(jié)
經(jīng)過對預處理得到的TEST宏進行逆向跟蹤微宝,到正向跟蹤RUN_ALL_TESTS宏棺亭,了解了gtest的整個運行過程,里面涉及到一下GOF涉及模式的運用蟋软,比如工廠函數(shù)镶摘、Singleton、Impl等钟鸵。
另外本文沒有提到的地方如斷言宏钉稍、輸出log日志等,因為比較簡單就略過了棺耍。斷言宏和輸出log就是在每次遍歷調(diào)用TestBody的時候進行相應的判斷和輸出打印。
下圖是一個簡單的TEST宏展開后的流程圖
最后再簡單將gtest的運行過程簡述一遍:
整個測試項目只有一個UnitTest對象种樱,因而整個項目也只有一個UnitTestImpl對象
每一個TEST宏生成一個測試案例類蒙袍,繼承自Test類
對于每一個測試案例類,由一個工廠類對象創(chuàng)建該類對象
由該測試案例類對象創(chuàng)建一個Test_Info類對象
由Test_Info類對象創(chuàng)建一個Test_case對象
創(chuàng)建Test_case對象的指針嫩挤,并將其插入到UnitTestImpl對象的數(shù)據(jù)成員vector向量的末尾位置
對每一個TEST宏進行2-6步驟害幅,那么對于唯一一個UnitTestImpl對象來說,它的數(shù)據(jù)成員vector向量中的元素按順序依次指向每一個包含測試案例對象信息的TestCase對象岂昭。
執(zhí)行RUN_ALL_TESTS宏以现,開始執(zhí)行用例。從頭往后依次遍歷UnitTestImpl對象中vector向量中的元素,對于其中的每一個元素指針邑遏,經(jīng)過一系列間接的方式最終調(diào)用其所對應的測試案列對象的TestBody成員函數(shù)佣赖,即測試用例代碼。
gtest提供了多種事件機制记盒,非常方便我們在案例之前或之后做一些操作憎蛤。
總結(jié)一下gtest的事件一共有3種:
全局的,所有案例執(zhí)行前后
TestSuite級別的纪吮,在某一批案例中第一個案例前俩檬,最后一個案例執(zhí)行后
TestCase級別的,每個TestCase前后
測試程序:一個測試程序只有一個main函數(shù)碾盟,也可以說是一個可執(zhí)行程序是一個測試程序棚辽。該級別的事件機制會在程序的開始和結(jié)束執(zhí)行。
測試套件:代表一個測試用例的集合體冰肴,該級別的事件機制會在整體的測試案例開始和結(jié)束執(zhí)行晚胡。
測試用例:該級別的的事件機制會在每個測試用例開始和結(jié)束都執(zhí)行。
全局的事件機制(針對整個測試程序)
局部的事件機制(針對一個個測試套件)
個體的事件機制(針對一個個測試用例)
一嚼沿、全局的事件機制
實現(xiàn)全局的事件機制骡尽,需要創(chuàng)建一個自己的類攀细,然后繼承testing::Environment類境钟,然后分別實現(xiàn)成員函數(shù)SetUp()和TearDown()慨削,同時在main函數(shù)內(nèi)執(zhí)行調(diào)用缚态,即“testing::AddGlobalTestEnvironment(new MyEnvironment);”,通過調(diào)用函數(shù)我們可以添加多個全局的事件機制。
SetUp()函數(shù)是在所有測試開始前執(zhí)行桥帆。TearDown()函數(shù)是在所有測試結(jié)束后執(zhí)行老虫。
示例:
二、局部的事件機制
測試套件的事件機制我們同樣需要去創(chuàng)建一個類邓萨,繼承testing::Test缔恳,實現(xiàn)兩個靜態(tài)函數(shù)SetUpTestCase()和TearDownTestCase()洁闰,測試套件的事件機制不需要像全局事件機制一樣在main注冊扑眉,而是需要將我們平時使用的TEST宏改為TEST_F宏聘裁。
SetUpTestCase()函數(shù)是在測試套件第一個測試用例開始前執(zhí)行衡便。TearDownTestCase()函數(shù)是在測試套件最后一個測試用例結(jié)束后執(zhí)行。
需要注意TEST_F的第一個參數(shù)使我們創(chuàng)建的類名呆抑,也就是當前測試套件的名稱。
示例:
三善镰、個體事件機制
測試用例的事件機制的創(chuàng)建和測試套件的基本一樣炫欺,不同地方在于測試用例實現(xiàn)的兩個函數(shù)分別是SetUp()和TearDown(),這兩個函數(shù)不是靜態(tài)函數(shù)了品洛。
SetUp()函數(shù)是在一個測試用例的開始前執(zhí)行。TearDown()函數(shù)是在一個測試用例的結(jié)束后執(zhí)行桥状。
四帽揪、總結(jié)
gtest的三種事件機制總的來說還是簡單的辅斟,而且也比較靈活转晰,通過上面的例子也能看出我們可以在事件機制中實現(xiàn)一些資源共享,使我們的測試更加靈活士飒。
gtest系列之斷言
gtest中斷言的宏可以分為兩類:一類是ASSERT宏,另一類是EXPECT宏。
ASSERT_系列:如果當前點檢測失敗則退出當前函數(shù)
EXCEPT_系列:如果當前點檢測失敗則繼續(xù)往下執(zhí)行
如果你對自動輸出的錯誤信息不滿意的話芳撒,也是可以通過operator<<能夠在失敗的時候打印日志,將一些自定義的信息輸出舌菜。
ASSERT_系列:
bool值檢查
1讶凉、 ASSERT_TRUE(參數(shù)),期待結(jié)果是true
2山孔、ASSERT_FALSE(參數(shù))懂讯,期待結(jié)果是false
數(shù)值型數(shù)據(jù)檢查
3、ASSERT_EQ(參數(shù)1台颠,參數(shù)2)褐望,傳入的是需要比較的兩個數(shù) equal
4、ASSERT_NE(參數(shù)1串前,參數(shù)2)瘫里,not equal,不等于才返回true
5荡碾、ASSERT_LT(參數(shù)1谨读,參數(shù)2),less than坛吁,小于才返回true
6、ASSERT_GT(參數(shù)1,參數(shù)2)玫恳,greater than,大于才返回true
7宣增、ASSERT_LE(參數(shù)1,參數(shù)2)矛缨,less equal爹脾,小于等于才返回true
8、ASSERT_GE(參數(shù)1箕昭,參數(shù)2)灵妨,greater equal,大于等于才返回true
字符串檢查
9盟广、ASSERT_STREQ(expected_str, actual_str)闷串,兩個C風格的字符串相等才正確返回
10、ASSERT_STRNE(str1, str2)筋量,兩個C風格的字符串不相等時才正確返回
11烹吵、ASSERT_STRCASEEQ(expected_str, actual_str)
12、ASSERT_STRCASENE(str1, str2)
13桨武、EXPECT_系列肋拔,也是具有類似的宏結(jié)構(gòu)的
gtest系列之死亡測試
這里的”死亡”指的是程序的奔潰。通常在測試的過程中呀酸,我們需要考慮各種各樣的輸入凉蜂,有的輸入可能直接導致程序奔潰,這個時候我們就要檢查程序是否按照預期的方式掛掉性誉,這也就是所謂的”死亡測試”窿吩。
死亡測試所用到的宏:
1、ASSERT_DEATH(參數(shù)1错览,參數(shù)2)纫雁,程序掛了并且錯誤信息和參數(shù)2匹配,此時認為測試通過倾哺。如果參數(shù)2為空字符串轧邪,則只需要看程序掛沒掛即可。
2羞海、ASSERT_EXIT(參數(shù)1忌愚,參數(shù)2,參數(shù)3)却邓,語句停止并且錯誤信息和被提前給的信息匹配硕糊。
玩轉(zhuǎn)Google單元測試框架gtest系列之六:運行參數(shù)
一、前言
使用gtest編寫的測試案例通常本身就是一個可執(zhí)行文件,因此運行起來非常方便癌幕。同時衙耕,gtest也為我們提供了一系列的運行參數(shù)(環(huán)境變量昧穿、命令行參數(shù)或代碼里指定)勺远,使得我們可以對案例的執(zhí)行進行一些有效的控制
二、基本介紹
前面提到时鸵,對于運行參數(shù)胶逢,gtest提供了三種設(shè)置的途徑:
系統(tǒng)環(huán)境變量
命令行參數(shù)
代碼中指定FLAG
因為提供了三種途徑,就會有優(yōu)先級的問題饰潜,有一個原則是初坠,最后設(shè)置的那個會生效。不過總結(jié)一下彭雾,通常情況下碟刺,比較理想的優(yōu)先級為:
命令行參數(shù) > 代碼中指定FLAG > 系統(tǒng)環(huán)境變量
為什么我們編寫的測試案例能夠處理這些命令行參數(shù)呢?是因為我們在main函數(shù)中薯酝,將命令行參數(shù)交給了gtest半沽,由gtest來搞定命令行參數(shù)的問題。
參考:
1??Gtest三種事件機制
2?解析gtest框架運行機制??