Cっぽいコードでgoogle testとgoogle mockを使ってみる

Cっぽいコードでgtestとgmockを使ってみる

クラスとか使っていないコードでgtest, ついでにgmockを使うことができないか試してみたのでメモ。

参考にした情報

以下の情報を参考にさせていただきました。ありがとうございます。

元のコード

我ながらCだかC++だか分からないコードですが・・・
capsule.h

#include <iostream>
#include <stdint.h>

#ifndef CAPSULE_H_
#define CAPSULE_H_

// Prototype for public function
void publicMethod(const uint8_t input, uint8_t &output);

#endif // CAPSULE_H_

capsule.cpp

#include "capsule.h"

// Prototype for private function
static void privateMethod01(const uint8_t input, uint8_t &output);

// Implement for public function
void publicMethod(const uint8_t input, uint8_t &output) {
    std::cout << "publicMethod called." << std::endl;

    // call private function
    privateMethod01(input, output);

    return;
}

// Implement for private function
static void privateMethod01(const uint8_t input, uint8_t &output) {
    std::cout << "privateMethod called." << std::endl;

    // TODO Implement here!!
    return;
}

privateMethod01の実装は大変そうなので、とりあえずprivateMethod01はニセモノでいいからPublicMethodのテストを書いておきたい、という状況を想定しています(あるのか?)。

gmockを使わずgtestをする場合

いきなり状況を無視しますが、ニセモノを使わずprivateMethod01から正直に実装を進めるのであれば、テストコード側からcapsule.cpp**をincludeすることで、privateMethod01まで含めて試験を書くことができます。
capsule_gtest.cpp

#include <gtest/gtest.h>

// To avoid multiply declaration
namespace UNITTEST {
#include "capsule.cpp"

class SampleTest : public ::testing::Test {
protected:
    virtual void SetUp() {
    }
    virtual void TearDown() {
    }
};

TEST_F(SampleTest, public_method_test) {
    std::cout << "public method test start!" << std::endl;
    uint8_t input = 3;
    uint8_t output = 0;

    publicMethod(input, output);
    EXPECT_EQ(5, output);
}

TEST_F(SampleTest, private_method_test) {
    std::cout << "private method test start!" << std::endl;
    uint8_t input = 3;
    uint8_t output = 0;

    privateMethod01(input, output);
    EXPECT_EQ(3, output);
}
} // namespace UNITTEST

リンク時の名前衝突を防ぐため、capsule.cppのincludeは適当なnamespace内で行うようにしています(参考:モダンC言語プログラミング §5.3.3)。

gmockを使えるように修正

gmock本来の使い方では、

  • 試験対象のクラスを継承したモッククラスを作り
  • メンバ関数をモックでoverrideする

ようですが、クラスとか無い今回は

  • 関数呼び出し先のポインタをモックに置き換えてやる

必要があるようです(参考:sioaji2012のブログ)。

モック化したい関数呼び出しを関数ポインタに変更

テストのときにだけprivateMethod01の呼び出しを関数ポインタにしたいので、(ちょっと気持ち悪いですが)マクロを使ってみます。
capsule.cpp

#include "capsule.h"

#ifdef GTEST
// add "_impl" suffix to private function
#define FUNC(func_name) func_name ## _impl
#else
// not to change
#define FUNC(func_name) func_name
#endif // GTEST

// Prototype for private function
static void FUNC(privateMethod01)(const uint8_t input, uint8_t &output);

#ifdef GTEST
// To use mock, replace function call to pointer
void (*privateMethod01)(const uint8_t input, uint8_t &output) = FUNC(privateMethod01);
#endif

// Implement for public function
void publicMethod(const uint8_t input, uint8_t &output) {
    std::cout << "publicMethod called." << std::endl;

    // call private function
    privateMethod01(input, output);

    return;
}

// Implement for private function
static void FUNC(privateMethod01)(const uint8_t input, uint8_t &output) {
    std::cout << "privateMethod called." << std::endl;

    // TODO Implement here!!
    return;
}

テストのときにだけ

  • privateMethod01のプロトタイプ宣言と実装をprivateMethod01_implという名前に変更
  • privateMethod01という関数ポインタを宣言してprivateMethod01_implを格納

となるようにしていて、テストでない場合には元と同じになります。

モックを使った試験を実装

gmockを使った試験になるよう、capsule_gtest.cppも修正します。

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

// To avoid multiply declaration
namespace UNITTEST {
#include "capsule.cpp"

using ::testing::AtLeast;
using ::testing::SetArgReferee;
using ::testing::_;

// Mock class
class MockPrivate {
 public:
    MOCK_METHOD2(privateMethod01, void(const uint8_t input, uint8_t &output));
};

// Create instance
MockPrivate mock;

// Mock function
void mockPrivateMethod01(const uint8_t input, uint8_t &output) {
    return mock.privateMethod01(input, output);
}

class SampleTest : public ::testing::Test {
protected:
    // Mock target (private function)
    void (*saved_privateMethod01)(const uint8_t input, uint8_t &output);
    virtual void SetUp() {
        // Save original function pointer
        saved_privateMethod01 = privateMethod01;
        // Overwrite function pointer by Mock
        privateMethod01 = mockPrivateMethod01;
    }
    virtual void TearDown() {
        // Restore original function pointer
        privateMethod01 = saved_privateMethod01;
    }
};

TEST_F(SampleTest, public_method_test) {
    std::cout << "public method test start!" << std::endl;
    uint8_t input = 3;
    uint8_t output = 0;

    // Set behavior for Mock
    EXPECT_CALL(mock, privateMethod01(_, _))
        .Times(2)
        .WillOnce(SetArgReferee<1>(input))
        .WillOnce(SetArgReferee<1>(5));

    // evaluation
    // No.1
    publicMethod(input, output);
    EXPECT_EQ(3, output);

    // No.2
    publicMethod(input, output);
    EXPECT_EQ(5, output);
}

int main(int argc, char** argv) {
  ::testing::InitGoogleMock(&argc, argv);
  return RUN_ALL_TESTS();
}
} // namespace UNITTEST
  • モッククラスとモック関数を定義
  • テストフィクスチャ内でprivateMethod01の関数ポインタをモック関数に置き換え(テストごとに戻す)

これで、モックを使った試験ができるようになりました。

まとめ

Cっぽいコードでgmockを使ったgtestができるようにしてみました(誰得?)。
ただ、元コードにかなり手を入れてしまっているので、場合によってはこの方法を取ることができないこともあると思います。
というか、もっとスマートにできる方法があるに違いない・・・思いつきませんが。

Written with StackEdit.

コメント

このブログの人気の投稿

Cっぽいコードでgtestとgmockを使ってみる その2

WSLにgoogle testを入れてみる