sioaji2012のブログ

普段は組み込み開発でC言語のみです。主にプログラムや勉強日記です

組込C言語でUnitTest 5 GoogleMock

モック

内部でsum()関数 をコールするcheck()関数 をテストする場合に、まだsum()関数が実装されていない場合などで、実際のsum()関数の代わりになるモックを作成してcheck() 関数のテストを行う。 という事をしてみます。

今回は、モックの作成方法を確認する為に、モックを作成しないで、 実際のsum()関数をコールしてcheck()関数をテストするコードを先に書きました。

check.c

#include "check.h"
#include "sum.h"

int check(int a, int b, int c)
{
  int result = 0;

  if ( sum(a,b) >= c ) 
  {
      result = 1;
  }
  else
  {
      result = 0;
  }

  return (result);
}

check.h

#ifndef CHECK_H
#define CHECK_H

extern int check(int a, int b, int c);

#endif //CHECK_H

テストコード

// テスト対象となる関数 check のためのフィクスチャ
class CheckTest : public ::testing::Test {
protected:
// 以降の関数で中身のないものは自由に削除できます.

    // コンストラクタ(初期化用)
    CheckTest()
    {// テスト毎に実行される set-up をここに書きます.
        printf("[ 初期化   ]\n");
    }

    // デストラクタ(終了処理用)
    virtual ~CheckTest()
    {// テスト毎に実行される,例外を投げない clean-up をここに書きます.
        printf("[ 終了処理 ]\n");

    }

    // コンストラクタとデストラクタでは不十分な場合.
    // 以下のメソッドを定義することができます:

    virtual void SetUp()
    {// このコードは,コンストラクタの直後(各テストの直前)に呼び出されます.
        printf("[ SetUp    ]\n");
    }

    virtual void TearDown()
    {// このコードは,各テストの直後(デストラクタの直前)に呼び出されます.
        printf("[ TearDown ]\n");

    }
// ここで宣言されるオブジェクトは,テストケース内の全てのテストで利用できます.
};

TEST_F(CheckTest, add1)
{
    printf("■CheckTest 加算テスト\n");
    EXPECT_EQ(1, check(1, 2, 3));
    EXPECT_EQ(0, check(1, 2, 4));
}

TEST_F(CheckTest, minus1)
{
    printf("■CheckTest 減算テスト\n");
    EXPECT_EQ(1, check(2, -1, 1) );
    EXPECT_EQ(0, check(2, -1, 2) );
}

テスト結果

■GoogleTestを開始しました
[==========] Running 4 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 2 tests from SumTest
[ RUN      ] SumTest.add1
[ 初期化   ]
[ SetUp    ]
■SumTest 加算テスト
[ TearDown ]
[ 終了処理 ]
[       OK ] SumTest.add1 (0 ms)
[ RUN      ] SumTest.minus1
[ 初期化   ]
[ SetUp    ]
■SumTest 減算テスト
[ TearDown ]
[ 終了処理 ]
[       OK ] SumTest.minus1 (0 ms)
[----------] 2 tests from SumTest (0 ms total)

[----------] 2 tests from CheckTest
[ RUN      ] CheckTest.add1
[ 初期化   ]
[ SetUp    ]
■CheckTest 加算テスト
[ TearDown ]
[ 終了処理 ]
[       OK ] CheckTest.add1 (0 ms)
[ RUN      ] CheckTest.minus1
[ 初期化   ]
[ SetUp    ]
■CheckTest 減算テスト
[ TearDown ]
[ 終了処理 ]
[       OK ] CheckTest.minus1 (0 ms)
[----------] 2 tests from CheckTest (0 ms total)

[----------] Global test environment tear-down
[==========] 4 tests from 2 test cases ran. (0 ms total)
[  PASSED  ] 4 tests.

モック作成

テストコードのファイル CheckTest.cpp にモックを追加します。 (namespace 内に追加)

リターンを制御する為のクラスを使う

using ::testing::Return;

モッククラスを追加

/* Mocksum モッククラス */
class MockSum
{
public:
  MOCK_METHOD2(sum, int(int a, int b) );
}mock;
  • MOCK_METHOD2 の 2 は引数の数です。
  • モック対象の関数名:sum
  • モック関数の型:int(int a, int b)

モック関数を定義

/* モック関数 */
int Mock_sum(int a, int b)
{
    return mock.sum(a, b);
}

テストフィクスチャ

// テスト対象となる関数 check のためのフィクスチャ
class CheckTest : public ::testing::Test {
protected:
// 以降の関数で中身のないものは自由に削除できます.
    int (*saved_sum)(int a, int b);

    // コンストラクタ(初期化用)
    CheckTest()
    {// テスト毎に実行される set-up をここに書きます.
        printf("[ 初期化   ]\n");
    }

    // デストラクタ(終了処理用)
    virtual ~CheckTest()
    {// テスト毎に実行される,例外を投げない clean-up をここに書きます.
        printf("[ 終了処理 ]\n");

    }

    // コンストラクタとデストラクタでは不十分な場合.
    // 以下のメソッドを定義することができます:

    virtual void SetUp()
    {// このコードは,コンストラクタの直後(各テストの直前)に呼び出されます.
        printf("[ SetUp    ]\n");
        saved_sum = sum;
        sum = Mock_sum;
    }

    virtual void TearDown()
    {// このコードは,各テストの直後(デストラクタの直前)に呼び出されます.
        printf("[ TearDown ]\n");
        sum = saved_sum;
    }
// ここで宣言されるオブジェクトは,テストケース内の全てのテストで利用できます.
};
  • SetUpで、関数ポインタをモックに切り替え
  • TearDownで、元の関数ポインタに戻し

テストケース

TEST_F(CheckTest, add1)
{
    printf("■CheckTest 加算テスト\n");
    EXPECT_CALL(mock, sum(1,2)).WillRepeatedly(Return(3));
    EXPECT_EQ(1, check(1, 2, 3));
    EXPECT_EQ(0, check(1, 2, 4));
}

TEST_F(CheckTest, minus1)
{
    printf("■CheckTest 減算テスト\n");
    EXPECT_CALL(mock, sum(2,-1)).WillRepeatedly(Return(1));
    EXPECT_EQ(1, check(2, -1, 1) );
    EXPECT_EQ(0, check(2, -1, 2) );
}

EXPECT_CALL(mock, sum(1,2)).WillRepeatedly(Return(3));

  • mockのsum(1,2)がコールされる度に3を返す。

EXPECT_CALL(mock, sum(2,-1)).WillRepeatedly(Return(1));

  • mockのsum(2,-1)がコールされる度に1を返す。

注意

これを実現するには、モックにする関数を関数ポインタに修正する必要があります。

つまり、テストの為に、既存の製品コードを修正する必要があります。

  • ここがネックで、テストの自動実行を設定するのが難しいです。

sum.c

#include "sum.h"

/*
int sum(int a, int b)

*/
int sum_imple(int a, int b) //★実装関数を名前変更
{
  return (a+b);
}

int (*sum)(int a, int b) = sum_imple; // ★sumを関数ポインタで定義して、実装関数のポインタで初期化

sum.h

#ifndef SUM_H_
#define SUM_H_

/*
extern int sum(int a, int b);

*/
extern int (*sum)(int a, int b);//★関数ポインタに変更

#endif //SUM_H_

テスト結果

■GoogleTestを開始しました
[==========] Running 4 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 2 tests from SumTest
[ RUN      ] SumTest.add1
[ 初期化   ]
[ SetUp    ]
■SumTest 加算テスト
[ TearDown ]
[ 終了処理 ]
[       OK ] SumTest.add1 (0 ms)
[ RUN      ] SumTest.minus1
[ 初期化   ]
[ SetUp    ]
■SumTest 減算テスト
[ TearDown ]
[ 終了処理 ]
[       OK ] SumTest.minus1 (0 ms)
[----------] 2 tests from SumTest (0 ms total)

[----------] 2 tests from CheckTest
[ RUN      ] CheckTest.add1
[ 初期化   ]
[ SetUp    ]
■CheckTest 加算テスト
[ TearDown ]
[ 終了処理 ]
[       OK ] CheckTest.add1 (0 ms)
[ RUN      ] CheckTest.minus1
[ 初期化   ]
[ SetUp    ]
■CheckTest 減算テスト
[ TearDown ]
[ 終了処理 ]
[       OK ] CheckTest.minus1 (0 ms)
[----------] 2 tests from CheckTest (0 ms total)

[----------] Global test environment tear-down
[==========] 4 tests from 2 test cases ran. (0 ms total)
[  PASSED  ] 4 tests.

モック対象の関数sum(int a, int b) の引数に関係なく返す値を制御したい場合

以下の様にします。

_を使う。

using ::testing::_;

テストケース

TEST_F(CheckTest, add1)
{
    printf("■CheckTest 加算テスト\n");
    EXPECT_CALL(mock, sum(_,_)).WillRepeatedly(Return(3));
    EXPECT_EQ(1, check(2, 100, 3));
    EXPECT_EQ(0, check(2, 100, 4));
}

TEST_F(CheckTest, minus1)
{
    printf("■CheckTest 減算テスト\n");
    EXPECT_CALL(mock, sum(_,_)).WillRepeatedly(Return(1));
    EXPECT_EQ(1, check(2, -100, 1) );
    EXPECT_EQ(0, check(2, -100, 2) );
}
  • テストでは、sum(2, 100)とsum(2, -100)となる様な引数をcheck関数にセットしていますが、モックの振る舞いでは、sum関数は引数に関係なく必ず3又は1を返す様な設定です。

EXPECT_CALL(mock, sum(,)).WillRepeatedly(Return(3));

  • sum()の引数に関係なく必ず3を返す

EXPECT_CALL(mock, sum(,)).WillRepeatedly(Return(1));

  • sum()の引数に関係なく必ず1を返す

テスト結果

※モックが返す値は前回と変わらない為、テストはOKとなります。

(こででテストが通るのはイマイチと言われそうですが。。。)

■GoogleTestを開始しました
[==========] Running 4 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 2 tests from SumTest
[ RUN      ] SumTest.add1
[ 初期化   ]
[ SetUp    ]
■SumTest 加算テスト
[ TearDown ]
[ 終了処理 ]
[       OK ] SumTest.add1 (0 ms)
[ RUN      ] SumTest.minus1
[ 初期化   ]
[ SetUp    ]
■SumTest 減算テスト
[ TearDown ]
[ 終了処理 ]
[       OK ] SumTest.minus1 (0 ms)
[----------] 2 tests from SumTest (0 ms total)

[----------] 2 tests from CheckTest
[ RUN      ] CheckTest.add1
[ 初期化   ]
[ SetUp    ]
■CheckTest 加算テスト
[ TearDown ]
[ 終了処理 ]
[       OK ] CheckTest.add1 (0 ms)
[ RUN      ] CheckTest.minus1
[ 初期化   ]
[ SetUp    ]
■CheckTest 減算テスト
[ TearDown ]
[ 終了処理 ]
[       OK ] CheckTest.minus1 (0 ms)
[----------] 2 tests from CheckTest (0 ms total)

[----------] Global test environment tear-down
[==========] 4 tests from 2 test cases ran. (0 ms total)
[  PASSED  ] 4 tests.

2回目にモックが返す値を変更する

2回目にモックが返す値を変更して、2回目のテストを失敗させてみます。

WillOnceを使います。

TEST_F(CheckTest, add1)
{
    printf("■CheckTest 加算テスト\n");
    EXPECT_CALL(mock, sum(_,_))
     .WillOnce(Return(3))
     .WillOnce(Return(4))            //★ 2回目の返す値を変更
     .WillRepeatedly(Return(3)); //★ 3回目以降はこちら
    EXPECT_EQ(1, check(2, 100, 3));
    EXPECT_EQ(0, check(2, 100, 4));  //★ このテストで失敗するはず
}

TEST_F(CheckTest, minus1)
{
    printf("■CheckTest 減算テスト\n");
    EXPECT_CALL(mock, sum(_,_))
     .WillOnce(Return(1))
     .WillOnce(Return(2))           //★ 2回目の返す値を変更
     .WillRepeatedly(Return(1)); //★ 3回目以降はこちら
    EXPECT_EQ(1, check(2, -100, 1) );
    EXPECT_EQ(0, check(2, -100, 2) ); //★ このテストで失敗するはず
}

テスト結果

※2回目のテストで失敗しているのがわかります。

■GoogleTestを開始しました
[==========] Running 4 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 2 tests from SumTest
[ RUN      ] SumTest.add1
[ 初期化   ]
[ SetUp    ]
■SumTest 加算テスト
[ TearDown ]
[ 終了処理 ]
[       OK ] SumTest.add1 (0 ms)
[ RUN      ] SumTest.minus1
[ 初期化   ]
[ SetUp    ]
■SumTest 減算テスト
[ TearDown ]
[ 終了処理 ]
[       OK ] SumTest.minus1 (0 ms)
[----------] 2 tests from SumTest (0 ms total)

[----------] 2 tests from CheckTest
[ RUN      ] CheckTest.add1
[ 初期化   ]
[ SetUp    ]
■CheckTest 加算テスト
../src/CheckTest.cpp:120: Failure
Expected equality of these values:
  0
  check(2, 100, 4)
    Which is: 1
[ TearDown ]
[ 終了処理 ]
[  FAILED  ] CheckTest.add1 (0 ms)
[ RUN      ] CheckTest.minus1
[ 初期化   ]
[ SetUp    ]
■CheckTest 減算テスト
../src/CheckTest.cpp:131: Failure
Expected equality of these values:
  0
  check(2, -100, 2)
    Which is: 1
[ TearDown ]
[ 終了処理 ]
[  FAILED  ] CheckTest.minus1 (0 ms)
[----------] 2 tests from CheckTest (0 ms total)

[----------] Global test environment tear-down
[==========] 4 tests from 2 test cases ran. (0 ms total)
[  PASSED  ] 2 tests.
[  FAILED  ] 2 tests, listed below:
[  FAILED  ] CheckTest.add1
[  FAILED  ] CheckTest.minus1

 2 FAILED TESTS

#ifdefで対応する例

最後に、モック対象関数を関数ポインタに置き換える部分を、#ifdefで対応する例をあげつつ、 コードをひととおり記載します。

TestMain.cpp

#include <stdio.h>
#include "gtest/gtest.h"
#include "gmock/gmock.h"

int main(int argc, char** argv) {
    printf("■GoogleTestを開始しました\n");
  // 以下の行は,テスト開始前に Google Mock (と Google Test)
  // を初期化するために必ず実行する必要があります.
  ::testing::InitGoogleMock(&argc, argv);
  return RUN_ALL_TESTS();
}

common.h

#ifndef COMMON_H_
#define COMMON_H_

#define UNITTEST

#endif /* COMMON_H_ */

check.h

#ifndef CHECK_H
#define CHECK_H

extern int check(int a, int b, int c);

#endif //CHECK_H

check.c

#include "common.h"
#include "check.h"
#include "sum.h"

int check(int a, int b, int c)
{
  int result = 0;

  if ( sum(a,b) >= c ) 
  {
      result = 1;
  }
  else
  {
      result = 0;
  }

  return (result);
}

sum.h

#ifndef SUM_H_
#define SUM_H_

#ifdef UNITTEST
extern int (*sum)(int a, int b);//★関数ポインタに変更
#else
extern int sum(int a, int b);
#endif

#endif //SUM_H_

sum.c

#include "common.h"
#include "sum.h"

#ifdef UNITTEST
int sum_imple(int a, int b) //★実装関数を名前変更
#else
int sum(int a, int b)
#endif
{
  return (a+b);
}

#ifdef UNITTEST
int (*sum)(int a, int b) = sum_imple; // ★sumを関数ポインタで定義して、実装関数のポインタで初期化
#endif

CheckTest.cpp

#include <stdio.h>
#include "gtest/gtest.h"
#include "gmock/gmock.h"

extern "C" {
#include "common.h"
#include "check.h"
#include "sum.h"
}

namespace {

using ::testing::Return;
using ::testing::_;

// テスト対象となる関数 Sum のためのフィクスチャ
class SumTest : public ::testing::Test {
protected:
// 以降の関数で中身のないものは自由に削除できます.

    // コンストラクタ(初期化用)
    SumTest()
    {// テスト毎に実行される set-up をここに書きます.
        printf("[ 初期化   ]\n");
    }

    // デストラクタ(終了処理用)
    virtual ~SumTest()
    {// テスト毎に実行される,例外を投げない clean-up をここに書きます.
        printf("[ 終了処理 ]\n");

    }

    // コンストラクタとデストラクタでは不十分な場合.
    // 以下のメソッドを定義することができます:

    virtual void SetUp()
    {// このコードは,コンストラクタの直後(各テストの直前)に呼び出されます.
        printf("[ SetUp    ]\n");
    }

    virtual void TearDown()
    {// このコードは,各テストの直後(デストラクタの直前)に呼び出されます.
        printf("[ TearDown ]\n");

    }
// ここで宣言されるオブジェクトは,テストケース内の全てのテストで利用できます.
};

TEST_F(SumTest, add1)
{
    printf("■SumTest 加算テスト\n");
    EXPECT_EQ(3, sum(1, 2));
}

TEST_F(SumTest, minus1)
{
    printf("■SumTest 減算テスト\n");
    EXPECT_EQ(1, sum(2, -1) );
}

/* Mocksum モッククラス */
class MockSum
{
public:
  MOCK_METHOD2(sum, int(int a, int b) );
}mock;

/* モック関数 */
int Mock_sum(int a, int b)
{
    return mock.sum(a, b);
}

// テスト対象となる関数 check のためのフィクスチャ
class CheckTest : public ::testing::Test {
protected:
// 以降の関数で中身のないものは自由に削除できます.
    int (*saved_sum)(int a, int b);

    // コンストラクタ(初期化用)
    CheckTest()
    {// テスト毎に実行される set-up をここに書きます.
        printf("[ 初期化   ]\n");
    }

    // デストラクタ(終了処理用)
    virtual ~CheckTest()
    {// テスト毎に実行される,例外を投げない clean-up をここに書きます.
        printf("[ 終了処理 ]\n");

    }

    // コンストラクタとデストラクタでは不十分な場合.
    // 以下のメソッドを定義することができます:

    virtual void SetUp()
    {// このコードは,コンストラクタの直後(各テストの直前)に呼び出されます.
        printf("[ SetUp    ]\n");
        saved_sum = sum;
        sum = Mock_sum;
    }

    virtual void TearDown()
    {// このコードは,各テストの直後(デストラクタの直前)に呼び出されます.
        printf("[ TearDown ]\n");
        sum = saved_sum;
    }
// ここで宣言されるオブジェクトは,テストケース内の全てのテストで利用できます.
};

TEST_F(CheckTest, add1)
{
    printf("■CheckTest 加算テスト\n");
    EXPECT_CALL(mock, sum(1,2)).WillRepeatedly(Return(3));
    EXPECT_EQ(1, check(1, 2, 3));
    EXPECT_EQ(0, check(1, 2, 4));
}

TEST_F(CheckTest, minus1)
{
    printf("■CheckTest 減算テスト\n");
    EXPECT_CALL(mock, sum(2,-1)).WillRepeatedly(Return(1));
    EXPECT_EQ(1, check(2, -1, 1) );
    EXPECT_EQ(0, check(2, -1, 2) );
}
}//namespace