組込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