Google Test(GTest)使用方法和源码解析——预处理技术分析和应用
預處理
? ? ? ? 在《Google Test(GTest)使用方法和源碼解析——概況》最后一部分,我們介紹了GTest的預處理特性?,F在我們就詳細介紹該特性的使用和相關源碼。(轉載請指明出于breaksoftware的csdn博客)
測試特例級別預處理
? ? ? ??Test Fixtures是建立一個固定/已知的環境狀態以確保測試可重復并且按照預期方式運行的裝置。通過它,我們可以實現測試特例級別和之后介紹的測試用例級別的預處理邏輯。
? ? ? ? 舉一個比較常見的例子:我們要測試向數據庫插入(id,name,location)這樣的三個數據,那要先構建一個基礎數據(0,Fang,Beijing)。我們第一個測試特例可能需要關注于id這個字段,于是它要在基礎數據上做出修改,將(1,Fang,Beijing)插入數據庫。第二個測試特例可能需要關注于name字段,于是它要在基礎數據上做出修改,將(0,Wang,Beijing)插入數據庫。第三個測試特例可能需要關注于location字段,于是它要修改基礎數據,將(0,Fang,Nanjing)插入數據庫。如果做得魯莽點,我們在每個測試特例前,先將所有數據填充好,再去操作。但是如果我們將其提煉一下,其實我們發現我們只要在每個特例執行前,獲取一份基礎數據,然后修改其中本次測試關心的一項就可以了。同時這份基礎數據不可以在每個測試特例中被修改——即本次測試特例獲取的基礎數據不會受之前測試特例對基礎數據修改而影響——獲取的是一個恒定的數據。
? ? ? ? 我們看下Test Fixtures類定義及使用規則:
- Test Fixtures類繼承于::testing::Test類。
- 在類內部使用public或者protected描述其成員,為了保證實際執行的測試子類可以使用其成員變量(這個我們后面會分析下)
- 在構造函數或者繼承于::testing::Test類中的SetUp方法中,可以實現我們需要構造的數據。
- 在析構函數或者繼承于::testing::Test類中的TearDown方法中,可以實現一些資源釋放的代碼(在3中申請的資源)。
- 使用TEST_F宏定義測試特例,其第一個參數要求是1中定義的類名;第二個參數是測試特例名。
? ? ? ? 其中4這步并不是必須的,因為我們的數據可能不是申請來的數據,不需要釋放。還有就是“構造函數/析構函數”和“SetUp/TearDown”的選擇,對于什么時候選擇哪對,本文就不做詳細分析了,大家可以參看https://github.com/google/googletest/blob/master/googletest/docs/FAQ.md#should-i-use-the-constructordestructor-of-the-test-fixture-or-the-set-uptear-down-function。一般來說就是構造/析構函數里忌諱做什么就不要在里面做,比如拋出異常等。
? ? ? ? 我們以一個例子來講解
class TestFixtures : public ::testing::Test {
public:TestFixtures() {printf("\nTestFixtures\n");};~TestFixtures() {printf("\n~TestFixtures\n");}
protected:void SetUp() {printf("\nSetUp\n");data = 0;};void TearDown() {printf("\nTearDown\n");}
protected:int data;
};TEST_F(TestFixtures, First) {EXPECT_EQ(data, 0);data = 1;EXPECT_EQ(data, 1);
}TEST_F(TestFixtures, Second) {EXPECT_EQ(data, 0);data = 1;EXPECT_EQ(data, 1);
}
? ? ? ? First測試特例中,我們修改了data的數據(23行),第24行驗證了修改的有效性和正確性。在second的測試特例中,一開始就檢測了data數據(第28行),如果First特例中修改data(23行)影響了基礎數據,則本次檢測將失敗。我們將First和Second測試特例的實現定義成一樣的邏輯,可以避免編譯器造成的執行順序不確定從而影響測試結果。我們看下測試輸出
[----------] 2 tests from TestFixtures
[ RUN ] TestFixtures.First
TestFixtures
SetUp
TearDown
~TestFixtures
[ OK ] TestFixtures.First (9877 ms)
[ RUN ] TestFixtures.Second
TestFixtures
SetUp
TearDown
~TestFixtures
[ OK ] TestFixtures.Second (21848 ms)
[----------] 2 tests from TestFixtures (37632 ms total)
? ? ? ? 可以見得,所有局部測試都是正確的,驗證了Test Fixtures類中數據的恒定性。我們從輸出應該可以看出來,每個測試特例都是要新建一個新的Test Fixtures對象,并在該測試特例結束時銷毀它。這樣可以保證數據的干凈。
? ? ? ? 我們來看下其實現的源碼,首先我們看下TEST_F的實現
#define TEST_F(test_fixture, test_name)\GTEST_TEST_(test_fixture, test_name, test_fixture, \::testing::internal::GetTypeId<test_fixture>())
? ? ? ?我們再回顧下在《Google Test(GTest)使用方法和源碼解析——自動調度機制分析》中分析的TEST宏的實現
#define GTEST_TEST(test_case_name, test_name)\GTEST_TEST_(test_case_name, test_name, \::testing::Test, ::testing::internal::GetTestTypeId())
? ? ? ? 可以見得它們的區別就是聲明的測試特例類繼承于不同的父類。同時使用的是public繼承方式,所以子類可以使用父類的public和protected成員。這也是我們在介紹Test Fixtures類編寫規則時說的,讓使用到的變量置于protected域之下的原因。
#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 {\
? ? ? ? 我們再看下Test Fixtures類對象在框架中是怎么創建、使用和銷毀的。
? ? ? ? 在TestInfo::Run()函數中有Test Fixtures對象和銷毀的代碼
// Creates the test object.Test* const test = internal::HandleExceptionsInMethodIfSupported(factory_, &internal::TestFactoryBase::CreateTest,"the test fixture's constructor");// Runs the test only if the test object was created and its// constructor didn't generate a fatal failure.if ((test != NULL) && !Test::HasFatalFailure()) {// This doesn't throw as all user code that can throw are wrapped into// exception handling code.test->Run();}// Deletes the test object.impl->os_stack_trace_getter()->UponLeavingGTest();internal::HandleExceptionsInMethodIfSupported(test, &Test::DeleteSelf_, "the test fixture's destructor");
? ? ? ? 因為測試特例類繼承于Test Fixtures類,Test Fixtures類繼承于Test類,所以我們可以通過廠類生成一個Test類對象的指針,這就是它創建的過程。在測試特例運行結束后,第16~17行將銷毀該對象。
? ? ? ? 在Test類的Run方法中,除了調用了子類定義的虛方法,還執行了SetUp和TearDown方法
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 Fixtures。不同的是,我們需要定義幾個靜態成員:
- 靜態成員變量,用于指向數據。
- 靜態方法SetUpTestCase()
- 靜態方法TearDownTestCase()
? ? ? ?舉個例子,我們需要自定義測試用例開始和結束時的行為
- 測試開始時輸出Start Test Case
- 測試結束時統計結果
class TestFixturesS : public ::testing::Test {
public:TestFixturesS() {printf("\nTestFixturesS\n");};~TestFixturesS() {printf("\n~TestFixturesS\n");}
protected:void SetUp() {};void TearDown() {};static void SetUpTestCase() {UnitTest& unit_test = *UnitTest::GetInstance();const TestCase& test_case = *unit_test.current_test_case();printf("Start Test Case %s \n", test_case.name());};static void TearDownTestCase() {UnitTest& unit_test = *UnitTest::GetInstance();const TestCase& test_case = *unit_test.current_test_case();int failed_tests = 0;int suc_tests = 0;for (int j = 0; j < test_case.total_test_count(); ++j) {const TestInfo& test_info = *test_case.GetTestInfo(j);if (test_info.result()->Failed()) {failed_tests++;}else {suc_tests++;}}printf("End Test Case %s. Suc : %d, Failed: %d\n", test_case.name(), suc_tests, failed_tests);};};TEST_F(TestFixturesS, SUC) {EXPECT_EQ(1,1);
}TEST_F(TestFixturesS, FAI) {EXPECT_EQ(1,2);
}
? ? ? ? 測試用例中,我們分別測試一個成功結果和一個錯誤的結果。然后輸出如下
[----------] 2 tests from TestFixturesS
Start Test Case TestFixturesS
[ RUN ] TestFixturesS.SUC
TestFixturesS
~TestFixturesS
[ OK ] TestFixturesS.SUC (2 ms)
[ RUN ] TestFixturesS.FAI
TestFixturesS
..\test\gtest_unittest.cc(126): error: Expected: 1
To be equal to: 2
~TestFixturesS
[ FAILED ] TestFixturesS.FAI (5 ms)
End Test Case TestFixturesS. Suc : 1, Failed: 1
[----------] 2 tests from TestFixturesS (12 ms total)
? ? ? ? 從輸出上看,SetUpTestCase在測試用例一開始時就被執行了,TearDownTestCase在測試用例結束前被執行了。我們看下源碼中怎么實現的
// Runs every test in this TestCase.
void TestCase::Run() {
......internal::HandleExceptionsInMethodIfSupported(this, &TestCase::RunSetUpTestCase, "SetUpTestCase()");
......for (int i = 0; i < total_test_count(); i++) {GetMutableTestInfo(i)->Run();}
......internal::HandleExceptionsInMethodIfSupported(this, &TestCase::RunTearDownTestCase, "TearDownTestCase()");
......
}
? ? ? ? 代碼之前了無秘密,以上節選的內容可以說明其執行的先后關系以及執行的區域。
全局級別預處理
? ? ? ? 顧名思義,它是在測試用例之上的一層初始化邏輯。如果我們要使用該特性,則要聲明一個繼承于::testing::Environment的類,并實現其SetUp/TearDown方法。這兩個方法的關系和之前介紹Test Fixtures類是一樣的。
? ? ? ? 我們看一個例子,我們例子中的預處理
- 測試開始時輸出Start Test
- 測試結束時統計結果
namespace testing {
namespace internal {
class EnvironmentTest : public ::testing::Environment {
public:EnvironmentTest() {printf("\nEnvironmentTest\n");};~EnvironmentTest() {printf("\n~EnvironmentTest\n");}
public:void SetUp() {printf("\n~Start Test\n");};void TearDown() {UnitTest& unit_test = *UnitTest::GetInstance();for (int i = 0; i < unit_test.total_test_case_count(); ++i) {int failed_tests = 0;int suc_tests = 0;const TestCase& test_case = *unit_test.GetTestCase(i);for (int j = 0; j < test_case.total_test_count(); ++j) {const TestInfo& test_info = *test_case.GetTestInfo(j);// Counts failed tests that were not meant to fail (those without// 'Fails' in the name).if (test_info.result()->Failed()) {failed_tests++;}else {suc_tests++;}}printf("End Test Case %s. Suc : %d, Failed: %d\n", test_case.name(), suc_tests, failed_tests);}};
};
}
}GTEST_API_ int main(int argc, char **argv) {printf("Running main() from gtest_main.cc\n");::testing::AddGlobalTestEnvironment(new testing::internal::EnvironmentTest);testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}
? ? ? ??EnvironmentTest的代碼我們就不講解了,我們可以關注下::testing::AddGlobalTestEnvironment(new testing::internal::EnvironmentTest);這句,我們要在調用RUN_ALL_TESTS之前,使用該函數將全局初始化對象加入到框架中。通過這種方式,可以猜測出,我們可以加入多個對象到框架中。我們看下源碼中對它們的調度
bool UnitTestImpl::RunAllTests() {
........ForEach(environments_, SetUpEnvironment);
........// Runs the tests only if there was no fatal failure during global// set-up.if (!Test::HasFatalFailure()) {for (int test_index = 0; test_index < total_test_case_count();test_index++) {GetMutableTestCase(test_index)->Run();}}
........std::for_each(environments_.rbegin(), environments_.rend(),TearDownEnvironment);
........
}
static void SetUpEnvironment(Environment* env) { env->SetUp(); }
static void TearDownEnvironment(Environment* env) { env->TearDown(); }
? ? ? ? 截取的源碼已經解釋的很清楚了。我們看到environments_是個容器,這也印證了我們對于框架中可以有多個Environment的預期。
總結
以上是生活随笔為你收集整理的Google Test(GTest)使用方法和源码解析——预处理技术分析和应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Google Test(GTest)使用
- 下一篇: Google Test(GTest)使用