gtest: SetUpTestCase, TearDownTestCase

gtestではテストケース毎に、テストケース全体で共有するリソースを初期化(確保)・解放する仕組みを提供している*1。これは、SetUpTestCaseとTearDownTestCaseを使用することで実現できる。

サンプルコード

class TestCase : public testing::Test {
protected:
  static void SetUpTestCase() {
    // 初期化
  }
  static void TearDownTestCase() {
    // 解放
  }
  // SetUp, TearDownとの併用も可能

  static Type resource; // テストケース全体で共有されるリソース(staticメンバにすること)
};
Type TestCase::resource = hogehoge;

TEST_F(TestCase, test1) {}
TEST_F(TestCase, test2) {}
...
TEST_F(TestCase, testn) {}

上記のコードではtest1〜testnが実行される順番は定義されていない(g++では上から順に実行された)。しかし、SetUpTestCaseはTestCaseの最初のテストが実行される前に、TearDownTestCaseはTestCaseの最後のテストが実行された後に、一度だけ呼び出されることが保証されている。これを利用して、テストケース内では同じデータを使うが、データの準備に時間がかかる場合などに、初期化・解放の処理を一度行うだけで済ませることが可能になる。しかも安全。

EXPECT, ASSERTの使用

SetUpTestCase/TearDownTestCaseの中でもEXPECT_**やASSERT_**などの機能を使うことが可能である。

#include <gtest/gtest.h>
class TestCase1 : public testing::Test {
protected:
  static void SetUpTestCase() { EXPECT_EQ(0, resource); }
  static void TearDownTestCase() { EXPECT_EQ(1, resource); }
  static int resource;
};
int TestCase1::resource = 0;
TEST_F(TestCase1, test1) { resource = 1; } // 本当は共有リソースに対する変更は禁止されている

上記のコードの出力は

[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from TestCase1
[ RUN      ] TestCase1.test1
[       OK ] TestCase1.test1
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran.
[  PASSED  ] 1 test.

このようになる。

失敗した場合

SetUpTestCaseで失敗した場合は

[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from TestCase1
test.cpp:4: Failure
Value of: resource
  Actual: 1
Expected: 0
[ RUN      ] TestCase1.test1
[       OK ] TestCase1.test1
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran.
[  PASSED  ] 1 test.
[  FAILED  ] 0 tests, listed below:

 0 FAILED TESTS

TearDownTestCaseで失敗した場合は

[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from TestCase1
[ RUN      ] TestCase1.test1
[       OK ] TestCase1.test1
test.cpp:5: Failure
Value of: resource
  Actual: 0
Expected: 1
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran.
[  PASSED  ] 1 test.
[  FAILED  ] 0 tests, listed below:

 0 FAILED TESTS

このような出力になる。今回は実験のためにEXPECTを用いたが、SetUp(TearDown)TestCaseではすべてASSERTを用いることが望ましいと考えられる。SetUpTestCaseが失敗した場合、テストケースで必要な共有リソースを用意できなかったことを意味するため、続くコードを実行する意味がないからである。しかし、SetUpTestCaseでASSERTが失敗しても各テストは実行されてしまう。そのため、それぞれのテスト内で、共有リソースが確保されているかどうかをASSERTを用いて最初にチェックすべきである。

TEST_F(TestCase1, test1)
{
  ASSERT_***(リソースが確保されているか最初にチェック);
  // ↓テストコード
  ...
}

今回のサンプルコードでは、各テストでEXPECT,ASSERTなどを利用していないため、どのテストケースが失敗した、という情報は表示されていない。だが、SetUpTestCaseが失敗した場合に備え、各テストで上記のリソースチェックを行っていれば、テストは失敗するので問題ない。

複数のテストケース

次に複数のテストケースを用いた場合に関して調査した。

#include <gtest/gtest.h>
class TestCase1 : public testing::Test {
protected:
  static void SetUpTestCase() { ASSERT_EQ(0, resource,); }
  static void TearDownTestCase() { ASSERT_EQ(1, resource); }
  static int resource;
};
int TestCase1::resource = 0;
TEST_F(TestCase1, test1) { resource = 1; }

class TestCase2 : public testing::Test {
protected:
  static void SetUpTestCase() { ASSERT_EQ(1, resource); }
  static void TearDownTestCase() { ASSERT_EQ(0, resource); }
  static int resource;
};
int TestCase2::resource = 1;
TEST_F(TestCase2, test2) { resource = 0; }

このようなテストを実行すると次のような出力が得られる。

[==========] Running 2 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 1 test from TestCase1
[ RUN      ] TestCase1.test1
[       OK ] TestCase1.test1
[----------] 1 test from TestCase2
[ RUN      ] TestCase2.test2
[       OK ] TestCase2.test2
[----------] Global test environment tear-down
[==========] 2 tests from 2 test cases ran.
[  PASSED  ] 2 tests.

成功したSetUp/TearDownTestCaseに関する情報は出力されない。

複数のテストケースで失敗した場合

まず、TestCase1::SetUpTestCaseが失敗した場合。

[==========] Running 2 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 1 test from TestCase1
test.cpp:4: Failure
Value of: resource

  Actual: 1
Expected: 0
[ RUN      ] TestCase1.test1
[       OK ] TestCase1.test1
[----------] 1 test from TestCase2
[ RUN      ] TestCase2.test2
[       OK ] TestCase2.test2
[----------] Global test environment tear-down
[==========] 2 tests from 2 test cases ran.
[  PASSED  ] 2 tests.
[  FAILED  ] 0 tests, listed below:

 0 FAILED TESTS

次にTestCase1::TearDownTestCaseが失敗した場合。

[==========] Running 2 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 1 test from TestCase1
[ RUN      ] TestCase1.test1
[       OK ] TestCase1.test1
test.cpp:5: Failure
Value of: resource
  Actual: 0
Expected: 1
[----------] 1 test from TestCase2
[ RUN      ] TestCase2.test2
[       OK ] TestCase2.test2
[----------] Global test environment tear-down
[==========] 2 tests from 2 test cases ran.
[  PASSED  ] 2 tests.
[  FAILED  ] 0 tests, listed below:

 0 FAILED TESTS

TestCase2::SetUpTestCaseが失敗した場合。

[==========] Running 2 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 1 test from TestCase1
[ RUN      ] TestCase1.test1
[       OK ] TestCase1.test1
[----------] 1 test from TestCase2
test.cpp:13: Failure
Value of: resource
  Actual: 0
Expected: 1
[ RUN      ] TestCase2.test2
[       OK ] TestCase2.test2
[----------] Global test environment tear-down
[==========] 2 tests from 2 test cases ran.
[  PASSED  ] 2 tests.
[  FAILED  ] 0 tests, listed below:

 0 FAILED TESTS

TestCase2::TearDownTestCaseが失敗した場合。

[==========] Running 2 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 1 test from TestCase1
[ RUN      ] TestCase1.test1
[       OK ] TestCase1.test1
[----------] 1 test from TestCase2
[ RUN      ] TestCase2.test2
[       OK ] TestCase2.test2
test.cpp:14: Failure
Value of: resource
  Actual: 1
Expected: 0
[----------] Global test environment tear-down
[==========] 2 tests from 2 test cases ran.
[  PASSED  ] 2 tests.
[  FAILED  ] 0 tests, listed below:

 0 FAILED TESTS

全部失敗した場合。

[==========] Running 2 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 1 test from TestCase1
test.cpp:4: Failure
Value of: resource
  Actual: 1
Expected: 0
[ RUN      ] TestCase1.test1
[       OK ] TestCase1.test1
test.cpp:5: Failure
Value of: resource
  Actual: 0
Expected: 1
[----------] 1 test from TestCase2
test.cpp:13: Failure
Value of: resource
  Actual: 0
Expected: 1
[ RUN      ] TestCase2.test2
[       OK ] TestCase2.test2
test.cpp:14: Failure
Value of: resource
  Actual: 1
Expected: 0
[----------] Global test environment tear-down
[==========] 2 tests from 2 test cases ran.
[  PASSED  ] 2 tests.
[  FAILED  ] 0 tests, listed below:

 0 FAILED TESTS

このように、どこで失敗したのか分かるようになっている。だが、少し見ただけではどこで失敗しているか分からない。そのため、SetUp(TearDown)TestCaseでは以下のようにASSERTで追加情報を出力しておくと、テストが増えたときも簡単に問題点を把握することができると思われる。

ASSERT(条件) << "SetUpTestCaseで失敗したことがわかる情報を出力";

まとめ

テストケース内で共有したいリソースがある場合は、SetUpTestCaseとTearDownTestCaseを利用する。SetUpTestCase, TearDownTestCaseと共有リソースはstaticメンバにすること。また、SetUpTestCaseでは、ASSERTに(リソースの確保に)失敗してもテストケース内のテストは実行されてしまうため、各テスト内でリソースが確保されているかどうかを最初にチェックする。そして、SetUp(TearDown)TestCase内でASSERTが失敗したときは、そのことを示す出力をしておくこと。

追記

SetUp(TearDown)TestCaseの中でSCOPED_TRACEを使うと、わざわざ全部のASSERTに対してメッセージを書かずにすむので便利。