gmockについて勉強してみた(4):ポインタの実体が配列であった場合

どもです。

今回は、配列を指すポインタ引数を持つメソッドのテストダブルを、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 Processor {
public:
    Processor(IDataProvider& provider) : provider_(provider) {}

    int Process()
    {
        int buffer[3] = { 0 };
        if (provider_.GetValues(buffer, 3)) {
            return buffer[0] + buffer[1] + buffer[2];
        }
        return -1;
    }

private:
    IDataProvider& provider_;
};

Process()メソッドが呼び出したGetValues()というメソッドにおいて、引数として渡した配列に値が格納されます。
Process()メソッドは、配列の要素の総和を返します。

このGetValues()が、テストダブル化するメソッドです。

1.2. テストダブル

テストダブル化するGetValues()メソッドは、以下のようなI/Fを持つメソッドを考えます。

bool GetValues(int* out, int count)

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

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

// インターフェース
class IDataProvider {
public:
    virtual ~IDataProvider() = default;
    virtual bool GetValues(int* out, int count) = 0;
};

// モッククラス
class MockDataProvider : public IDataProvider {
public:
    MOCK_METHOD(bool, GetValues, (int* out, int count), (override));
};

2. テストドライバ

前出のテストダブルがテスト実行時に呼び出された際の動作を、テストドライバで指定します。
動作を指定する際には、gmockのSetArrayArgument()メソッドを使用します。具体的には、以下のようなコードです。

SetArrayArgument<0>(arr, arr + 3)

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

int arr[3] = { 1, 2, 3 };

これらのコードにより、GetValues()メソッドは、テスト実行時に、引数で指定された配列に対して、それぞれ1、2、3、という値を格納します。
結果として、テスト対象のメソッドは、1~3の値の和である6を返します。

この内容をテストするテストドライバ全体の実装は、以下になります。

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

    int arr[3] = { 1, 2, 3 };

    // GetValues 呼び出し時、out[0..2] に arr の内容をコピーし、true を返す
    EXPECT_CALL(mock, GetValues(_, 3))
        .WillOnce(DoAll(SetArrayArgument<0>(arr, arr + 3), Return(true)));

    Processor proc(mock);
    EXPECT_EQ(proc.Process(), 6); // 1+2+3=6
}

3. gmockのメソッド

配列を指すポインタ引数を持つメソッドが呼び出された際の動作を指定するSetArrayArgument()メソッドを見てみます。
gmockでは、以下のように定義されています。

template <size_t k, typename I1, typename I2>
internal::SetArrayArgumentAction<k, I1, I2> SetArrayArgument(I1 first, I2 last)

この関数は、gmockの公式サイト/チートシートでは、以下のように解説されています。

SetArrayArgument<N>(first, last):入力範囲 [first、 last) の要素を、N-番目(0基準)の argument (これは、ポインタまたはイテレータ)によって指される配列にコピーします。Action は、入力範囲内の要素の所有権を持ちません。

要は、「数の引数として渡された配列ポインタに対して、任意のデータを書き込む」という動作を指定するためのメソッドです。

4. gmockのメソッドの応用

SetArrayArgument()の基本的な使い方が分かったところで、少し応用について考えてみます。

4.1. コピー開始位置の指定

まず、コピー開始位置を先頭以外に指定してみます。この場合、第1引数、第2引数ともに変更する必要があります。
例えば配列のインデックス1の要素から3要素ぶんコピーする、という動作を指定する場合には、以下のように指定します。

SetArrayArgument<0>(arr + 1, arr + 4)

このことから、「N番目の要素からM個の要素をコピーする」という動作は、以下のように指定します。

SetArrayArgument<0>(arr + N, arr + (N + M))

4.2. 「<0>」を変更してみる

次に、「<0>」を変更してみます。
ただし今回のテストダブル化するメソッド bool GetValues(int* out, int count) では、テンプレート引数の <0> は「コピー先にする引数のインデックス(0始まり)」を表します。
すなわち <0>out を、<1>count を指します。count はポインタでもイテレータでもないため、<1> に変更するとコンパイルエラーになります。
コピー開始位置はテンプレート引数ではなく、関数引数の first/last(例:arr + Narr + (N + M))で制御します。

5. まとめ

今回は、配列を指すポインタ引数を持つメソッドのテストダブルの動作をgmockで指定する方法を確認しました。結果として、以下の方法で設定することができました。

  • SetArrayArgument() メソッドを使用する。
  • 第1引数にはコピー開始位置(first)を指定する。
  • 第2引数には「コピー開始+コピーする要素数」に相当する終端(last)を指定する。

コピー開始位置は任意に設定できるため、柔軟に動作を指定することができます。
そのため、gmockのSetArrayArgument()メソッドを使用することで、配列を指すポインタ引数を持つメソッドのテストダブルを、効率的かつ簡潔に作成できます。

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