GAIO CLUB

2024年10月10日

Google Mock 2 – Google Testのカバレッジ計測を試してみた #5

Google Test
Google Testのカバレジ計測を試してみた
前回は、Google Mockで子関数の戻り値をあらかじめ決めた後にテストを実施してみました。Google Mockの感じをつかんだので、今回はGoogle Mockでどんなことができるのか、いくつか見て行こうと思います。

ExpectationとAction

前回はEXPECT_CALLを使って以下のようにモックの動作を定義しました。EXPECT_CALLはモックに対する呼ばれ方と動作を定義します。
EXPECT_CALL(mock, GetTemperature())   // GetTemperatureモックの動作定義
   .Times(AtLeast(2))	        // 呼出し回数は最低2回
   .WillOnce(Return(29))	// 最初のGetTemperature(モック)は29℃を返す
   .WillOnce(Return(21));	// 2度目の GetTemperature(モック)は21℃を返す
この例は、モック関数のGetTemperature()がテスト中に少なくとも2度呼ばれ、最初は29、2度目は21を返すという定義でした。
子関数を呼出す回数はテストに重要な意味を持ちますので、例えばこのテスト中に3度呼ばれると警告メッセージが出ます。この重要なモック呼出し回数を定義するTimesにはAtLeastのほかにも以下の設定方法があり、これらのことをCardinalitiesと呼びます。

・AnyNumber() → 何回も呼出される(回数指定無し)
・AtLeast(n) → 最低でもn回呼出される
・AtMost(n) → 最高n回呼出される
・Between(m, n) → 呼出される回数はmからnの間
・Exactly(n) → 正確にn回呼出される

Timesは省略することも可能ですが、その際にはWillOnceとWillRepeatedlyの数によってその値が決まります。WillRepeatedlyは、同じ動作を繰り返すと言う指定です。これは、デフォルトの処理を指定するような意味を持ちます。WillOnceやWillRepeatedlyに指定するのはアクションです。この例の場合、戻り値を指定していますが、代入処理を行うなど、他のアクションを定義することも可能です。

モックにデフォルト処理をさせるには、いくつかの方法がありますのでご紹介します。

DefaultValueとON_CALL

以下の例では2種類の方法でデフォルトの戻り値を設定し、繰り返し(WillRepeatedly)も使用しました。
その関係で、各テストフィクスチャ内のWillOnceの定義は1つにしています。
TEST_F(AirConditionerControllerTest, OffModeTest) {
      DefaultValue<int>::Set(29);	// デフォルトの戻り値は 29℃
      EXPECT_CALL(mock, GetTemperature())
        .Times(AtLeast(1))	// 呼出し回数は1回以上
        .WillOnce(Return(21));	// 最初のGetTemperature(モック)は21℃を返す

      airConditionerController.AutoMode();
      EXPECT_EQ(MODE_OFF, airConditionerController.GetMode());
      airConditionerController.AutoMode();
      EXPECT_EQ(MODE_OFF, airConditionerController.GetMode());
      DefaultValue<int>::Clear();	// デフォルト設定のクリア
  }

  TEST_F(AirConditionerControllerTest, HeattingTest) {
      ON_CALL(mock, GetTemperature())
        .WillByDefault(Return(19));	// GetTemperature(モック)デフォルト戻り値は19℃
      EXPECT_CALL(mock, GetTemperature())
        .Times(AtLeast(1))	// 呼出し回数は1回以上
        .WillOnce(Return(20));	// 最初のGetTemperature(モック)は20℃を返す

      airConditionerController.AutoMode();
      EXPECT_EQ(MODE_HEATING, airConditionerController.GetMode());
      airConditionerController.AutoMode();
      EXPECT_EQ(MODE_HEATING, airConditionerController.GetMode());
  }

  TEST_F(AirConditionerControllerTest, CoolingTest) {
      EXPECT_CALL(mock, GetTemperature())
        .Times(AtLeast(2))             // 呼出し回数は2回以上
        .WillOnce(Return(30))          // 最初のGetTemperature(モック)は30℃を返す
        .WillRepeatedly(Return(31));   // 2回目以降は繰り返し31℃を返す

      airConditionerController.AutoMode();
      EXPECT_EQ(MODE_COOLING, airConditionerController.GetMode());
      airConditionerController.AutoMode();
      EXPECT_EQ(MODE_COOLING, airConditionerController.GetMode());
  }
OffModeTestでは、DefaultValueによりモック関数GetTemperatureのデフォルト戻り値を29℃とし、最初の戻り値をWillOnceで21℃に指定してあるので、2度目の呼出しでは29℃が返ります。
HeattingTestでは、WillByDefaultでモック関数のデフォルト戻り値を19℃とし、同様にWillOnceで20℃に設定してあるので、2度目の呼出しでは19℃が返ります。
CoolingTestでは、WillRepeatedlyを使って2回目以降の戻り値を31℃に指定しています。

このテスト設定により、前回お見せしたテスト記述と同じようにテストはPASSするのですが、以下の警告が出ます。
Running main() from /usr/local/src/googletest-1.14.0/googletest/src/gtest_main.cc
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from AirConditionerControllerTest
[ RUN      ] AirConditionerControllerTest.OffModeTest

GMOCK WARNING:
AirConditionerController_test.cpp:28: Actions ran out in EXPECT_CALL(mock, GetTemperature())...
Called 2 times, but only 1 WillOnce() is specified - returning default value.
Stack trace:
[       OK ] AirConditionerControllerTest.OffModeTest (0 ms)
[ RUN      ] AirConditionerControllerTest.HeattingTest

GMOCK WARNING:
AirConditionerController_test.cpp:42: Actions ran out in EXPECT_CALL(mock, GetTemperature())...
Called 2 times, but only 1 WillOnce() is specified - taking default action specified at:
AirConditionerController_test.cpp:40:
Stack trace:
[       OK ] AirConditionerControllerTest.HeattingTest (0 ms)
[ RUN      ] AirConditionerControllerTest.CoolingTest
[       OK ] AirConditionerControllerTest.CoolingTest (0 ms)
[----------] 3 tests from AirConditionerControllerTest (0 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (0 ms total)
[  PASSED  ] 3 tests.
警告はOffModeTestHeattingTestに対して出ており、いずれもモックが2度呼ばれたけどWillOnce()が1つしかないのでデフォルト処理が動いたよという意味のようです。

このように、モックを使用する際にデフォルト処理を使用することは可能なのですが、デフォルト処理は明示的な指定を忘れた時に動いてしまうこともあり、Googleとしてはあまりお勧めではないのだろうと思います。マニュアルにも注意して使用するようにとの記述があります。

デフォルトアクションの移譲

もうひとつ、デフォルトアクションの定義を試してみます。
using ::testing::AtLeast;
using ::testing::Return;
using ::testing::Test;

  // テストフィクスチャクラスの定義
  class AirConditionerControllerTest : public Test {
  protected:
    MockTemperatureSensor mock;
    AirConditionerController airConditionerController = AirConditionerController(&mock);
        
    virtual void SetUp() {
        airConditionerController.Init();  
    }
  };
    
  TEST_F(AirConditionerControllerTest, OffModeTest) {
      mock.DelegateToFake();    // デフォルト処理を行うメソッドの設定

      airConditionerController.AutoMode();
      EXPECT_EQ(MODE_OFF, airConditionerController.GetMode());
      airConditionerController.AutoMode();
      EXPECT_EQ(MODE_OFF, airConditionerController.GetMode());
  }

  TEST_F(AirConditionerControllerTest, HeattingTest) {
      mock.DelegateToFake();    // デフォルト処理を行うメソッドの設定

      airConditionerController.AutoMode();
      EXPECT_EQ(MODE_HEATING, airConditionerController.GetMode());
      airConditionerController.AutoMode();
      EXPECT_EQ(MODE_HEATING, airConditionerController.GetMode());
  }

  TEST_F(AirConditionerControllerTest, CoolingTest) {
      mock.DelegateToFake();    // デフォルト処理を行うメソッドの設定

      airConditionerController.AutoMode();
      EXPECT_EQ(MODE_COOLING, airConditionerController.GetMode());
      airConditionerController.AutoMode();
      EXPECT_EQ(MODE_COOLING, airConditionerController.GetMode());
  }
ここでは、デフォルト処理を行うメソッドを使用します。
各テストの最初にmock.DelegateToFake()でデフォルト処理メソッドを指定しており、DelegateToFake()はこのように書いてあります。
using ::testing::_;
using ::testing::Invoke;

/**
 * TemperatureSensor
 * 温度センサのMock
 */
class MockTemperatureSensor : public TemperatureSensor {
  public:
    MOCK_METHOD(int , GetTemperature, ());

    // メソッドのデフォルトActionをFakeTemperatureSensorオブジェクトに委譲
    // これは必ずON_CALL()文よりも前に呼ばれる必要がある
    void DelegateToFake() {
        ON_CALL(*this, GetTemperature())
                .WillByDefault(Invoke(&fake_, &FakeTemperatureSensor::GetTemperature));
  }
 private:
  FakeTemperatureSensor fake_;  // フェイクのインスタンスはモック内部に保持
};
DelegateToFake()内で、GetTemperature()の処理をFakeTemperatureSensor::GetTemperature()に移譲し、移譲されたGetTemperature()は以下のようにstatic配列の値を返すメソッドになっています。
/**
 * TemperatureSensor
 * 温度センサ
 */
class TemperatureSensor {
  public:
        virtual int GetTemperature() = 0;
};

static int ret_idx=0,ret[]={29,21,20,19,30,31,0};
// デフォルト処理を行うメソッド、上記配列の値を順に返す
class FakeTemperatureSensor : public TemperatureSensor {
  public:
    virtual int GetTemperature() {
        return ret[ret_idx++];
    }
};
これらの定義により、GetTemperature()のモックは呼ばれる度に29,21,20,19,30,31の値を順に返します。
テスト結果はこのようになりました。
Running main() from /usr/local/src/googletest-1.14.0/googletest/src/gtest_main.cc
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from AirConditionerControllerTest
[ RUN      ] AirConditionerControllerTest.OffModeTest

GMOCK WARNING:
Uninteresting mock function call - taking default action specified at:
MockTemperatureSensor.h:21:
    Function call: GetTemperature()
          Returns: 29
NOTE: You can safely ignore the above warning unless this call should not happen.  Do not suppress it by blindly adding an EXPECT_CALL() if you don't mean to enforce the call.  See https://github.com/google/googletest/blob/main/docs/gmock_cook_book.md#knowing-when-to-expect-useoncall for details.

GMOCK WARNING:
Uninteresting mock function call - taking default action specified at:
MockTemperatureSensor.h:21:
    Function call: GetTemperature()
          Returns: 21
NOTE: You can safely ignore the above warning unless this call should not happen.  Do not suppress it by blindly adding an EXPECT_CALL() if you don't mean to enforce the call.  See https://github.com/google/googletest/blob/main/docs/gmock_cook_book.md#knowing-when-to-expect-useoncall for details.
[       OK ] AirConditionerControllerTest.OffModeTest (0 ms)
[ RUN      ] AirConditionerControllerTest.HeattingTest

GMOCK WARNING:
Uninteresting mock function call - taking default action specified at:
MockTemperatureSensor.h:21:
    Function call: GetTemperature()
          Returns: 20
NOTE: You can safely ignore the above warning unless this call should not happen.  Do not suppress it by blindly adding an EXPECT_CALL() if you don't mean to enforce the call.  See https://github.com/google/googletest/blob/main/docs/gmock_cook_book.md#knowing-when-to-expect-useoncall for details.

GMOCK WARNING:
Uninteresting mock function call - taking default action specified at:
MockTemperatureSensor.h:21:
    Function call: GetTemperature()
          Returns: 19
NOTE: You can safely ignore the above warning unless this call should not happen.  Do not suppress it by blindly adding an EXPECT_CALL() if you don't mean to enforce the call.  See https://github.com/google/googletest/blob/main/docs/gmock_cook_book.md#knowing-when-to-expect-useoncall for details.
[       OK ] AirConditionerControllerTest.HeattingTest (0 ms)
[ RUN      ] AirConditionerControllerTest.CoolingTest

GMOCK WARNING:
Uninteresting mock function call - taking default action specified at:
MockTemperatureSensor.h:21:
    Function call: GetTemperature()
          Returns: 30
NOTE: You can safely ignore the above warning unless this call should not happen.  Do not suppress it by blindly adding an EXPECT_CALL() if you don't mean to enforce the call.  See https://github.com/google/googletest/blob/main/docs/gmock_cook_book.md#knowing-when-to-expect-useoncall for details.

GMOCK WARNING:
Uninteresting mock function call - taking default action specified at:
MockTemperatureSensor.h:21:
    Function call: GetTemperature()
          Returns: 31
NOTE: You can safely ignore the above warning unless this call should not happen.  Do not suppress it by blindly adding an EXPECT_CALL() if you don't mean to enforce the call.  See https://github.com/google/googletest/blob/main/docs/gmock_cook_book.md#knowing-when-to-expect-useoncall for details.
[       OK ] AirConditionerControllerTest.CoolingTest (0 ms)
[----------] 3 tests from AirConditionerControllerTest (0 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (0 ms total)
[  PASSED  ] 3 tests.
こちらも、テストはPASSしますが、同じように大量の警告が出ています。
警告にあるとおり、意図したものなら警告を抑制する方法がありますので、使ってみます。
ここを書き換えます。
using ::testing::AtLeast;
using ::testing::Return;
using ::testing::Test;
using ::testing::NiceMock;

  // テストフィクスチャクラスの定義
  class AirConditionerControllerTest : public Test {
  protected:
    // MockTemperatureSensor mock;
    NiceMock mock;
    AirConditionerController airConditionerController = AirConditionerController(&mock);
        
    virtual void SetUp() {
        airConditionerController.Init();  
    }
  };
この変更により、警告なしでテストできました。
Running main() from /usr/local/src/googletest-1.14.0/googletest/src/gtest_main.cc
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from AirConditionerControllerTest
[ RUN      ] AirConditionerControllerTest.OffModeTest
[       OK ] AirConditionerControllerTest.OffModeTest (0 ms)
[ RUN      ] AirConditionerControllerTest.HeattingTest
[       OK ] AirConditionerControllerTest.HeattingTest (0 ms)
[ RUN      ] AirConditionerControllerTest.CoolingTest
[       OK ] AirConditionerControllerTest.CoolingTest (0 ms)
[----------] 3 tests from AirConditionerControllerTest (0 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (0 ms total)
[  PASSED  ] 3 tests.
警告を消すことはできますが、Googleのドキュメントにもしつこく書いてある通り、警告を消してしまうことは危険を伴うので、十分に確認したうえで行うべきでしょう。

Matcher

Matcherはモックライブラリの機能ですが、アサーションでも使用可能で、オリジナルのMatcherも作成可能と書いてあります。ここではアサーションで使ってみます。
テスト記述を変更しました。赤字の部分です。
using ::testing::AtLeast;
using ::testing::Return;
using ::testing::Test;
using ::testing::AnyOf;
using ::testing::Eq;

// テストフィクスチャクラスの定義
  class AirConditionerControllerTest : public Test {
  protected:
    MockTemperatureSensor mock;
    AirConditionerController airConditionerController = AirConditionerController(&mock);
    virtual void SetUp() {
        airConditionerController.Init();  
    }
  };
  TEST_F(AirConditionerControllerTest, OffModeTest) {	// オフモードのテスト
      EXPECT_CALL(mock, GetTemperature())
        .Times(AtLeast(2))       // 呼び出し回数は2回以上
        .WillOnce(Return(29))    // 最初は、29℃を返す
        .WillOnce(Return(21));   // 2回目は、21℃を返す
      airConditionerController.AutoMode();
//    設定モードが OFF・COOLING・HEATINGのいずれかになること
      EXPECT_THAT(airConditionerController.GetMode(),
              AnyOf(Eq(MODE_OFF),Eq(MODE_COOLING),Eq(MODE_HEATING)));
      airConditionerController.AutoMode();
//    設定モードが 0・1・3 のいずれかになること
      EXPECT_THAT(airConditionerController.GetMode(),AnyOf(Eq(0),Eq(1),Eq(3)));
  }
  TEST_F(AirConditionerControllerTest, HeattingTest) {	// 暖房モードのテスト
      EXPECT_CALL(mock, GetTemperature())
        .Times(AtLeast(2))       // 呼び出し回数は2回以上
        .WillOnce(Return(20))    // 最初は、20℃を返す
        .WillOnce(Return(19));   // 2回目は、19℃を返す
      airConditionerController.AutoMode();
//    設定モードが OFF・COOLING・HEATINGのいずれかになること
      EXPECT_THAT(airConditionerController.GetMode(),
              AnyOf(Eq(MODE_OFF),Eq(MODE_COOLING),Eq(MODE_HEATING)));
      airConditionerController.AutoMode();
//    設定モードが 0・1・3 のいずれかになること
      EXPECT_THAT(airConditionerController.GetMode(),AnyOf(Eq(0),Eq(1),Eq(3)));
  }
  TEST_F(AirConditionerControllerTest, CoolingTest) {	// 冷房モードのテスト
      EXPECT_CALL(mock, GetTemperature())
        .Times(AtLeast(2))       // 呼び出し回数は2回以上
        .WillOnce(Return(30))    // 最初は、30℃を返す
        .WillOnce(Return(31));   // 2回目は、31℃を返す
      airConditionerController.AutoMode();
//    設定モードが OFF・COOLING・HEATINGのいずれかになること
      EXPECT_THAT(airConditionerController.GetMode(),
              AnyOf(Eq(MODE_OFF),Eq(MODE_COOLING),Eq(MODE_HEATING)));
      airConditionerController.AutoMode();
//    設定モードが 0・1・3 のいずれかになること
      EXPECT_THAT(airConditionerController.GetMode(),AnyOf(Eq(0),Eq(1),Eq(3)));
  }
各テストの期待値判定処理を3つの候補のいずれかであるか判定するようにしました。
EXPECT_THAT(airConditionerController.GetMode(),
       AnyOf(Eq(MODE_OFF),Eq(MODE_COOLING),Eq(MODE_HEATING)));
の記述は、3種のconst変数値で確認していますが、
EXPECT_THAT(airConditionerController.GetMode(),AnyOf(Eq(0),Eq(1),Eq(3)));
の記述では、0・1・2ではなく、0・1・3の定数値で確認しているのでどこかのテストがFAILEDになるはずです。テスト結果は以下の通りです。
Running main() from /usr/local/src/googletest-1.14.0/googletest/src/gtest_main.cc
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from AirConditionerControllerTest
[ RUN      ] AirConditionerControllerTest.OffModeTest
[       OK ] AirConditionerControllerTest.OffModeTest (0 ms)
[ RUN      ] AirConditionerControllerTest.HeattingTest
AirConditionerController_test.cpp:53: Failure
Value of: airConditionerController.GetMode()
Expected: (is equal to 0) or (is equal to 1) or (is equal to 3)
  Actual: 2 (of type int)

[  FAILED  ] AirConditionerControllerTest.HeattingTest (0 ms)
[ RUN      ] AirConditionerControllerTest.CoolingTest
[       OK ] AirConditionerControllerTest.CoolingTest (0 ms)
[----------] 3 tests from AirConditionerControllerTest (1 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (1 ms total)
[  PASSED  ] 2 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] AirConditionerControllerTest.HeattingTest

 1 FAILED TEST
2回にわたって、Google Mockを使ってみました。
Google Mockにもいろんな便利機能がありますので、ぜひマニュアルを確認してみてください。
次回は、C言語関数のテストでGoogle Mockを使ってみます。

本コラムで使用したソースプログラム・コマンドファイル・ログは以下にあります(ソースプログラム中のコメントは全く同じではありません)。サンプルソースは4種類です。

Google Test資産を活かした車載品質テストの効率化を実現
「QTE(Quality Town for Embedded grade)」

「QTE」は、ユーザ環境で作成したGoogleTest資産を活用し、機能安全(ISO 26262)で求められるマイコンターゲットオブジェクト実行とカバレッジ計測が可能です。また、テストエビデンス及びレポートを自動出力し、テストエビデンスの作成も効率化します。

筆者紹介

浅野 昌尚(あさの まさなお)

ガイオ・テクノロジー株式会社

開発1部 QTXグループ

1980年代から30年以上にわたり汎用構造のCコンパイラ開発に従事し、その間に8ビットマイコンからRISC・VLIW・画像処理プロセッサまで、さまざまなCPU向けのクロスCコンパイラを開発。

人気のコラム

最新のコラム