GAIO CLUB

2024年09月10日

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

Google Test
Google Testのカバレジ計測を試してみた
Google Mockは、単体テストの対象関数が使用している子関数の代理関数とその関数の振る舞いを定義する環境を提供します。今回は簡単な例を見ながらGoogle Mockの使い方を見てみます。
使用したバージョンは、1.14.0 です。

テスト対象プログラムの構造

下に書いたクラス図内のエアコンコントローラクラスのメンバ関数である温度によるモード切替()のテストをするために、子関数(温度の取得())をモック化します。
テスト対象プログラムの構造
実際の関数名
テスト対象プログラムの構造

Mockの定義

テスト対象関数は、下に示したAirConditionerControllerクラスのメンバ関数AutoModeです。
/**
 * TemperatureSensor
 * 温度センサ
 */
class TemperatureSensor {
    public:
        virtual int GetTemperature() = 0;
};
// モード:オフ
const int MODE_OFF = 0;
// モード:冷房
const int MODE_COOLING = 1;
// モード:暖房
const int MODE_HEATING = 2;

/**
 * AirConditionerController
 * エアコンコントローラ
 */
class AirConditionerController {	// エアコンコントローラクラス
  private:
    // 温度センサ・インターフェイスのポインタ
    TemperatureSensor *pTemperatureSensor_ = nullptr;
    // 冷房をつける基準値(単位:℃)
    const int COOLING_THRESHOLD = 30;
    // 暖房をつける基準値(単位:℃)
    const int HEATING_THRESHOLD = 20;
    // エアコンのモード (オフ・冷房・暖房)
    int Mode;
public:
  AirConditionerController(TemperatureSensor*pTemperatureSensor)
  {
     pTemperatureSensor_ = pTemperatureSensor;
  }
  virtual ~AirConditionerController() {}

  // 初期化処理
  void Init();
  // 自動運転判定
  void AutoMode();	// 温度によるモード切替
  // モード取得
  int GetMode();
};
AutoMode関数は、現在の温度から動作モードを決める処理を行います。
このような関数です。
void AirConditionerController::AutoMode() {
  // 温度を取得
  int temperature = pTemperatureSensor_->GetTemperature();	// 温度センサの呼出し
  
  if (temperature >= COOLING_THRESHOLD) {
      // 温度が30℃以上の時は、冷房をオンにする
      Mode = MODE_COOLING;
  } else if (temperature <= HEATING_THRESHOLD) {
      // 温度が20℃以下の時は、暖房をオンにする 
      Mode = MODE_HEATING;
  } else {
      // それ以外の時は、停止する
      Mode = MODE_OFF;
  } 
  return;
}
しかし、このAutoMode関数を動かすためには、子関数のGetTempretureが必要です。
GetTempretureはTemperatureSensorクラスのメンバ関数として定義されているので、
/**
 * TemperatureSensor
 * 温度センサ
 */
class TemperatureSensor {
   public:
      virtual int GetTemperature() = 0;
};
このクラスの派生クラスとして、モッククラスを作ります。
#include <gmock/gmock.h>
/**
 * TemperatureSensor
 * 温度センサのMock
 */
class MockTemperatureSensor : public TemperatureSensor {
   public:
      MOCK_METHOD(int , GetTemperature, ()); // int GetTemperatue() のモック定義
                       // GetTemperatureはint型の値を返し、引数が無いことを示す
};
TemperatureSensorの派生クラスとしてモッククラスMockTemperatureSensorを作り、MOCK_METHODマクロでモック化する関数を指定します。少し変わった書き方ですが、こんな形式で記述します。
MOCK_METHOD( 戻り値の型, モックで動かす関数名, (引数情報) );

Mockによるテストの実施

以下にサンプルを記載しました。
マクロの説明は次回に改めて説明しますが、コメントでおおよその意味がお分かりいただけると思います。
テスト記述では、3つのテストフィクスチャでGetTemperature関数がそれぞれ2回呼ばれ、そのリターン値はWillOnceで指定する値にするようにと定義しています。その後にテストを実行する仕組みです。
namespace
{
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) {   // テストフィクスチャ
      EXPECT_CALL(mock, GetTemperature())   // GetTemperatureモックの動作定義
        .Times(AtLeast(2))       // 呼出し回数は最低2回
        .WillOnce(Return(29))    // 最初のGetTemperature(モック)は29℃ を返す
        .WillOnce(Return(21));   // 2度目の GetTemperature(モック)は21℃ を返す

      airConditionerController.AutoMode();	// 温度が29℃ のテスト
      EXPECT_EQ(MODE_OFF, airConditionerController.GetMode());	// 結果確認
      airConditionerController.AutoMode();	// 温度が21℃ のテスト
      EXPECT_EQ(MODE_OFF, airConditionerController.GetMode());	// 結果確認
  }
  TEST_F(AirConditionerControllerTest, HeatingTest) {   // テストフィクスチャ
      EXPECT_CALL(mock, GetTemperature())   // GetTemperatureモックの動作定義
        .Times(AtLeast(2))       // 呼出し回数は最低2回
        .WillOnce(Return(20))    // 最初のGetTemperature(モック)は20℃ を返す
        .WillOnce(Return(19));   // 2度目の GetTemperature(モック)は19℃ を返す

      airConditionerController.AutoMode();	// 温度が20℃ のテスト
      EXPECT_EQ(MODE_HEATING, airConditionerController.GetMode());	// 結果確認
      airConditionerController.AutoMode();	// 温度が19℃ のテスト
      EXPECT_EQ(MODE_HEATING, airConditionerController.GetMode());	// 結果確認
  }
  TEST_F(AirConditionerControllerTest, CoolingTest) {   // テストフィクスチャ
      EXPECT_CALL(mock, GetTemperature())   // GetTemperatureモックの動作定義
        .Times(AtLeast(2))       // 呼出し回数は最低2回
        .WillOnce(Return(30))    // 最初のGetTemperature(モック)は30℃ を返す
        .WillOnce(Return(31));   // 2度目の GetTemperature(モック)は31℃ を返す

      airConditionerController.AutoMode();	// 温度が30℃ のテスト
      EXPECT_EQ(MODE_COOLING, airConditionerController.GetMode());	// 結果確認
      airConditionerController.AutoMode();	// 温度が31℃ のテスト
      EXPECT_EQ(MODE_COOLING, airConditionerController.GetMode());	// 結果確認
  }
}

OffModeTestでは、GetTemperature()が29℃ と21℃を返すので、エアコンモードがMODE_OFFに設定されることを確認します。HeatingTestでは、20℃ と19℃を返すので、MODE_HEATINGに設定されることを確認し、CoolingTestでは、30℃ と31℃の時にMODE_COOLINGに設定されることを確認します。
なお、テスト結果のエアコンモード値を参照するために同じクラス内のメンバ関数GetMode()を使用しています。
Mockによるテストの実施
モックを使うテストドライバーでは、コンパイル時にGoogle Testが提供するインクルードファイルgtest.hとmock.hを使用し、リンク時にはライブラリlibgtestとlibgmockを指定します。
下は、テスト結果です。
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.HeatingTest
[       OK ] AirConditionerControllerTest.HeatingTest (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.
本コラムで使用したソースプログラム・コマンドファイル・ログは以下にあります(ソースプログラム中のコメントは全く同じではありません)。

筆者紹介

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

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

開発1部 QTXグループ

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

人気のコラム

最新のコラム