Google Test(GTest)の使用方法とソースコードの解析-前処理技術の分析と応用

11274 ワード

プリプロセッシング

『Google Test(GTest)の使用方法とソースコード解析-概要』の最後の部分で、GTestの前処理特性を紹介した.次に、この特性の使用と関連するソースコードについて詳しく説明します.(転載breaksoftwareからのcsdnブログを明記してください)

特例レベルの事前処理のテスト


Test Fixturesは、テストが繰り返され、所望の方法で実行されることを保証するために、固定/既知の環境状態を確立する装置である.これにより、テスト特例レベルと後述するテスト例レベルの前処理ロジックを実現できます.
比較的一般的な例を挙げると、データベースへの挿入(id,name,location)のような3つのデータをテストするには、まず基礎データ(0,Fang,Beijing)を構築します.私たちの最初のテスト特例はidというフィールドに注目する必要があるかもしれません.そこで、基礎データを変更し、(1,Fang,Beijing)をデータベースに挿入します.2番目のテスト特例では、nameフィールドに注目する必要がある場合があります.したがって、ベースデータを変更し、(0,Wang,Beijing)をデータベースに挿入します.3番目のテスト特例は、locationフィールドに注目する必要がある場合があります.そこで、ベースデータを変更し、(0,Fang,Nanjing)をデータベースに挿入します.無謀なことをした場合は、各テストの特例の前に、すべてのデータを入力してから操作します.しかし、それを精製すると、特例ごとに実行する前に基礎データを取得し、今回のテストで関心のあるものを修正すればいいことがわかりました.同時に、この基礎データは、各試験特例において修正されてはならない.すなわち、今回の試験特例で取得された基礎データは、以前の試験特例が基礎データの修正に影響を及ぼさない.一定のデータを取得する.
Test Fixturesクラスの定義と使用ルールを見てみましょう.
  • Test Fixturesクラスは、:testing::Testクラスに継承されます.
  • クラス内でpublicまたはprotectedを使用してメンバーを記述し、実際に実行されるテストサブクラスがメンバー変数を使用できることを保証するために(これは後で分析する)
  • は、コンストラクション関数または継承::testing::TestクラスのSetUpメソッドで、構築する必要があるデータを実現することができます.
  • は、構造関数または継承::testing::TestクラスのTearDownメソッドで、いくつかのリソース解放コード(3で申請されたリソース)を実現することができる.
  • TEST_を使用Fマクロはテスト特例を定義し、その最初のパラメータは1で定義されたクラス名であることを要求する.2番目のパラメータはテスト特例名です.

  • このうち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("
    TestFixtures
    "); }; ~TestFixtures() { printf("
    ~TestFixtures
    "); } protected: void SetUp() { printf("
    SetUp
    "); data = 0; }; void TearDown() { printf("
    TearDown
    "); } 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("
    TestFixturesS
    "); }; ~TestFixturesS() { printf("
    ~TestFixturesS
    "); } 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
    ", 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
    ", 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メソッドを実装します.この2つの方法の関係は,先に紹介したTest Fixturesクラスと同じである.
    例を見てみましょう
  • 試験開始時出力Start Test
  • 試験終了時統計結果
  • namespace testing {
    namespace internal {
    class EnvironmentTest : public ::testing::Environment {
    public:
        EnvironmentTest() {
            printf("
    EnvironmentTest
    "); }; ~EnvironmentTest() { printf("
    ~EnvironmentTest
    "); } public: void SetUp() { printf("
    ~Start Test
    "); }; 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
    ", test_case.name(), suc_tests, failed_tests); } }; }; } } GTEST_API_ int main(int argc, char **argv) { printf("Running main() from gtest_main.cc
    "); ::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があることを証明しています.