C++單元測試框架——gtest

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框架運行機制??

3? ?gtest實現(xiàn)框架簡單分析

4?gtest的介紹和使用

5?GTest源碼剖析(二)——TEST宏

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吴菠,一起剝皮案震驚了整個濱河市者填,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌做葵,老刑警劉巖占哟,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異酿矢,居然都是意外死亡榨乎,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門瘫筐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜜暑,“玉大人,你說我怎么就攤上這事严肪∈芳澹” “怎么了?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵驳糯,是天一觀的道長篇梭。 經(jīng)常有香客問我,道長酝枢,這世上最難降的妖魔是什么恬偷? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮帘睦,結(jié)果婚禮上袍患,老公的妹妹穿的比我還像新娘坦康。我一直安慰自己,他們只是感情好诡延,可當我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布滞欠。 她就那樣靜靜地躺著,像睡著了一般肆良。 火紅的嫁衣襯著肌膚如雪筛璧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天惹恃,我揣著相機與錄音夭谤,去河邊找鬼。 笑死巫糙,一個胖子當著我的面吹牛朗儒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播参淹,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼醉锄,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了承二?” 一聲冷哼從身側(cè)響起榆鼠,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎亥鸠,沒想到半個月后妆够,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡负蚊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年神妹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片家妆。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡鸵荠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出伤极,到底是詐尸還是另有隱情蛹找,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布哨坪,位于F島的核電站庸疾,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏当编。R本人自食惡果不足惜届慈,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧金顿,春花似錦臊泌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至礁凡,卻和暖如春高氮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背顷牌。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留塞淹,地道東北人窟蓝。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像饱普,于是被迫代替她去往敵國和親运挫。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內(nèi)容