gmockについて勉強してみた(5):ポインタ引数で渡された値のコピー

どもです。

前回までは、出力となるポインタ引数を持つメソッドのテストダブルをgmockで定義する方法を書いてきました。
今回は、入力となるポインタ引数を持つメソッドのテストダブルを、gmockで定義してみます。

0. 作業環境:

作業環境です。
これまでと同様に、以下の環境で作業を行っています。

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

1. サンプルメソッド

入力となるポインタ引数を持つメソッドを呼び出すメソッドを考えます。

1.1. テストダブルを使用するメソッド

今回は、次のような配列を指すポインタ引数を持つメソッドを呼び出すメソッドを例に、話を進めます。

// テスト対象クラス
class TargetClass {
    SubFunctionInterface* subfunc_;
public:
    explicit TargetClass(SubFunctionInterface* subfunc) : subfunc_(subfunc) {}

    void DoSomething(int* ptr, size_t size)
    {
        subfunc_->SubFunc(ptr, size);
    }
};

DoSomething()メソッドは、特に何もしません。
呼び出し元から渡された値を、そのまま下位関数に渡します。
ここで、下位関数であるSubFuncが、テストダブル化の対象となるメソッドです。

1.2. テストダブル

テストダブル化するSubFunc()メソッドは、以下のI/Fを持ちます。

void SubFunc(int* ptr, size_t size)

このメソッドのテストダブルは、gmockを用いて以下のように定義/実装できます。

// インターフェース
class SubFunctionInterface {
public:
    virtual ~SubFunctionInterface() = default;
    virtual void SubFunc(int* ptr, size_t size) = 0;
};

// モッククラス
class MockSubFunction : public SubFunctionInterface {
public:
    MOCK_METHOD(void, SubFunc, (int* ptr, size_t size), (override));
};

2. テストドライバ

前出のテストダブルがテスト実行時に呼び出された際の動作を、テストドライバで指定します。
ここで、入力となるポインタ引数に対して、テストダブルがどのように動作するのか、どの値を保持しなければならないのかを確認します。

過去の投稿では、

  • ポインタの場合は、「入力」であれば渡されたアドレスを代入/保持するためのバッファを用意する。

と、結論づけています。
これに加えて最近では、ポインタの実体の値も保持するようにしています。
(テスト対象関数の出力であるため。この辺の結論の変更は、別途投稿を作成する予定です…。)

「渡されたアドレスを代入/保持する」動作は、gmockのSaveArgメソッドで指定します。
具体的なコードは、以下のようになります。

SaveArg<0>(&captured_ptr)

ここでcaptured_ptrは、予め以下のように定義します。

int* captured_ptr = nullptr;

次に、「ポインタの実体の値も保持する」動作は、以下のように定義します。

Invoke([&captured_values](int* ptr, size_t size) {
    captured_values.assign(ptr, ptr + size);
})

これらの処理を、テストダブルの動作を指定するDoAllメソッドの引数に渡します。

DoAll(
    SaveArg<0>(&captured_ptr),
    SaveArg<1>(&captured_size),
    Invoke([&captured_values](int* ptr, size_t size) {
        captured_values.assign(ptr, ptr + size);
    })
)

captured_valuesは、予め以下のように定義します。

std::vector<int> captured_values;

また、途中でSaveArg()メソッドを使用した、以下のコードが登場しています。

SaveArg<1>(&captured_size)

このコードは、テストダブルの第2引数の値を保持する動作を指定しています。

テストドライバ全体は、以下のようになります。

TEST(GMockPointerArrayCaptureTest, CapturePointerArrayContents)
{
    MockSubFunction mock_subfunc;
    TargetClass target(&mock_subfunc);

    int actual_array[] = { 11, 22, 33, 44 };
    size_t actual_size = sizeof(actual_array) / sizeof(actual_array[0]);

    int* captured_ptr = nullptr;
    size_t captured_size = 0;
    std::vector<int> captured_values;

    EXPECT_CALL(mock_subfunc, SubFunc(_, _))
        .WillOnce(DoAll(
            SaveArg<0>(&captured_ptr),
            SaveArg<1>(&captured_size),
            Invoke([&captured_values](int* ptr, size_t size) {
                captured_values.assign(ptr, ptr + size);
            })
        ));

    target.DoSomething(actual_array, actual_size);

    // ポインタの一致確認
    EXPECT_EQ(captured_ptr, actual_array);
    EXPECT_EQ(captured_size, actual_size);

    // 配列の中身確認
    ASSERT_EQ(captured_values.size(), actual_size);
    for (size_t i = 0; i < actual_size; ++i) {
        EXPECT_EQ(captured_values[i], actual_array[i]);
    }
}

3. 使用しているメソッド

3.1. SaveArg

SaveArg<N>(pointer)は、「N-番目の引数そのものをコピーして保存する」動作を指定します。
保存先はユーザーが渡した変数のアドレスとなります。

3.2. Invoke

Invoke(f)は、「モック関数に渡された引数を与えて f を呼び出す」動作を定義します。
fはラムダ式で指定できるため、動作を柔軟に制御できます。

4. まとめ

今回は、入力となるポインタ引数を持つメソッドのテストダブルをgmockで定義してみました。
定義するテストダブルでは、以下の値を保持するようにしました。

  • 渡されたアドレス
  • ポインタの実体の値

gmockでは、SaveArg() および Invoke() を使用することで、これらを保持するテストダブルが実装できます。
特に Invoke() はラムダ式を利用できるため、柔軟に挙動を指定することが可能です。

このようにgmockにより、入力となるポインタ引数を持つメソッドのテストダブルを定義できることが確認できました。

今回の投稿が、誰かの助けになれば幸いです。
ではっ!