gmockについて勉強してみた(2):ポインタ引数を持つメソッドのモック

どもです。

前回の投稿で、Windows上でgmockをビルドし、簡単なサンプルプログラムを動かしてみました。今回はその続きとして、ポインタの引数を持つメソッドのテストダブルをgmockで作成してみます。


0. 作業環境

まず作業環境です。今回も、前回と同様に以下の環境で作業を行っています。

項目 内容
CPU Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz 3.20 GHz
RAM 16.0 GB (15.9 GB 使用可能)
OS Windows10 Professional
22H2 (19045.5487)
Visual Studio Visual Studio Community 2022 (64bit), Version 17.14.8
CMake version 4.1.0

1. サンプルメソッド

今回は、ポインタ引数を持つメソッドのテストダブルを考えます。

1.1. サンプルメソッド(テストダブル)

ポインタ引数を持つメソッドのテストダブルとして、以下のようなインターフェースを持つメソッドを考えます。

bool GetValue(int* out);

ポインタ引数 out にはメソッドの出力値が格納されます。また、格納処理の実行結果(成功/失敗)が戻り値の bool で返されます。

このメソッドのテストダブルは gmock では以下のように定義できます。

// インターフェース

class IDataProvider {
public:
    virtual ~IDataProvider() = default;
    virtual bool GetValue(int* out) = 0;
};

// モッククラス

class MockDataProvider : public IDataProvider {
public:
    MOCK_METHOD(bool, GetValue, (int* out), (override));
};

1.2. サンプルメソッドを使用するクラス(テスト対象)

次に、上記のサンプルメソッドを使用するクラスを考えます。以下が単体テストの対象となるクラスです。

// テスト対象
class Processor {
public:
    Processor(IDataProvider& provider) : provider_(provider) {}

    int Process()
    {
        int value = 0;
        if (provider_.GetValue(&value)) {
            return value * 2; // 取得値を2倍して返す
        }
        return -1;
    }

private:
    IDataProvider& provider_;
};

Process メソッドは、GetValue が正常に完了した場合は取得した値を2倍して返し、そうでない場合は -1 を返します。


2. テストドライバ

前節のサンプルメソッドの単体テスト用テストドライバ(テストコード)の実装を示します。

2.1. テストダブルが返す値

テストダブルが設定すべき値は主に次の2点です。

  • 戻り値(true / false)
  • ポインタ引数の実体に格納する値

gmock では、これらに対してそれぞれ SetArgPointeetesting::Return を使って設定します。例:

SetArgPointee<0>(42)
testing::Return(true)

SetArgPointee はポインタ引数の実体に値をセットするアクションで、テンプレート引数の <0> は引数位置(ここでは最初の引数)を指します。例では「引数の指す先に 42 をセット」します。
testing::Return は戻り値を指定します(ここでは true)。

2.2. テストダブルが値を返す動作の設定

複数のアクションを順に実行させたい場合は DoAll を使います。例:

DoAll(SetArgPointee<0>(42), testing::Return(true));

このアクションは、対象のモックが呼ばれるたびに実行されます(実行回数は次節で制御します)。

2.3. 呼び出し回数とアクションの組み合わせ

モックの呼び出し回数や、その回ごとの振る舞いは WillOnce / WillRepeatedly 等で制御します。今回のように「一度だけ特定の動作をさせる」場合は WillOnce を使います。例:

WillOnce(DoAll(SetArgPointee<0>(42), testing::Return(true)));\

2.4. テストコード(全体)

// テスト
TEST(ProcessorTest, ReturnsDoubleOfProvidedValue)
{
    MockDataProvider mock;

    // GetValue の呼び出し時、引数(out)に 42 をセットし、true を返す
    EXPECT_CALL(mock, GetValue(_))
        .WillOnce(DoAll(SetArgPointee<0>(42), testing::Return(true)));

    Processor proc(mock);
    EXPECT_EQ(proc.Process(), 84);
}

3. 提案した内容との対応

以前の記事では、ポインタ引数(出力用)のテストを以下のように実装すると良いと提案しました。

  • ポインタが出力である場合、ポインタの実体に設定する値を保持するバッファを用意する
  • 呼び出される度に、用意したバッファの値をポインタの実体に代入して返す

gmock を用いることで、上記の動作は同等に実現できます。具体的には SetArgPointee を使うことでポインタの指す先に値を代入でき、testing::Return で戻り値を返せます。なお「呼び出される度に異なる値を返す」といったケースは本投稿では未検証のため、完全に同等と断言はできません(この点は別投稿で確認予定です)。


4. 結論

今回は、gmock によるポインタ引数を持つメソッドのテストダブルについて解説しました。gmock では SetArgPointeetesting::Return を組み合わせることで、以前の記事で示した「バッファを用意してポインタに代入する」方式と同等の振る舞いを簡潔に実装できることを確認しました。

gmock を使う利点として、テストダブル本体でバッファや初期化処理を自前で実装する必要がなく、結果としてテストコードの行数や複雑さを抑えられる点が挙げられます。より簡潔で読みやすいテストが書けるため、メンテナンス性も向上します。

なお、「呼び出される度に異なる値を返す」ケースについては、今回は検証していないため、別の投稿で確認ができれば…と考えています。

ではっ!