概要
Mocking Frameworkってすごいですよね。
C#でMoqを使ったとき、あまりの便利さに感動しました。
その一方で、リフレクションがある言語限定かなと思ってたのですが、C++にもあるんですね。
せっかくなので以下を試しに使ってみました。
- FakeIt
- HippoMocks
- Google Mock
動くコードは以下に置いてます。
https://github.com/minoru-nagasawa/CppMockFrameworkTrial/
比較
C言語みたいにグローバル関数が多い場合はHippoMocks一択
C++のクラスだけが対象なら、FakeItとHippoMocksで好みの方
個人的にはFakeItの書き方が好きなので、FakeItにグローバルな関数も対応してほしいところ。
Google Mockは無いな。
という感じです。
ただ、お試しに使ってみただけなので、ヘビーユーザの方のご意見聞きたいですね。
FakeIt | HippoMocks | Google Mock | コメント | |
---|---|---|---|---|
導入しやすさ | 容易 | 容易 | 面倒 | Google Mock以外は1つのヘッダファイルをincludeするだけなので簡単 |
モッククラスの定義 | 不要 | 不要 | 要 | FakeItとHippoMocksは素晴らしい。 Google Mockは、モッククラスを作る必要があるのでいまいち |
グローバル関数のモック | 不可 | 可 | 不可 | HippoMocks有利 |
クラスのpublicメソッドのモック | 可 | 可 | 可 | 基本機能 |
クラスのprivateメソッドのモック | 不可 | 不可 | 不可 | privateは変更できない |
クラスのstaticメソッドのモック | 不可 | 可 | 不可 | HippoMocks有利 |
具象クラスの一部だけモック | 可 | 不可 | 可 | FakeItとGoogle Mock有利 |
価格 | 無料 | 無料 | 無料 | 安心 |
最終更新日 | 2018/11/10 | 2018/12/7 | 2015/8/26 | FakeItとHippoMocksは開発が継続してるように見える |
googleでの検索数 | 3220(hippomocks) | 4160(fakeit mock c++) | 38100("google mock") | 知名度は圧倒的にGoogle Mock |
テスト対象
以下をテスト対象にしてます。
class ISample
{
public:
virtual int SampleMethod1(int lhs) = 0;
virtual int SampleMethod2(int lhs, int rhs) = 0;
};
class SampleConcrete : public ISample
{
public:
SampleConcrete();
~SampleConcrete();
int SampleMethod1(int lhs);
int SampleMethod2(int lhs, int rhs);
static int StaticMethod(int lhs);
};
SampleConcrete::SampleConcrete()
{
}
SampleConcrete::~SampleConcrete()
{
}
int SampleConcrete::SampleMethod1(int lhs)
{
return lhs;
}
int SampleConcrete::SampleMethod2(int lhs, int rhs)
{
return lhs + rhs;
}
int SampleConcrete::StaticMethod(int lhs)
{
return lhs;
}
int SampleMethodGlobal(int lhs)
{
return lhs;
}
FakeIt
開発サイト
クイックスタートはここ
https://github.com/eranpeer/FakeIt/wiki/Quickstart
導入方法
1. ヘッダファイルをコピー
自分のテストフレームワークにあったファイルを下記から持ってきてください。
- https://github.com/eranpeer/FakeIt/blob/master/single_header/gtest/fakeit.hpp
- https://github.com/eranpeer/FakeIt/blob/master/single_header/mstest/fakeit.hpp
- https://github.com/eranpeer/FakeIt/blob/master/single_header/boost/fakeit.hpp
- https://github.com/eranpeer/FakeIt/blob/master/single_header/catch/fakeit.hpp (Tested with Catch 2.0.1)
- https://github.com/eranpeer/FakeIt/blob/master/single_header/tpunit/fakeit.hpp
- https://github.com/eranpeer/FakeIt/blob/master/single_header/mettle/fakeit.hpp
- https://github.com/eranpeer/FakeIt/blob/master/single_header/qtest/fakeit.hpp
-
https://github.com/eranpeer/FakeIt/blob/master/single_header/nunit/fakeit.hpp
(See caveats in config/nunit/fakeit_instance.hpp) - https://github.com/eranpeer/FakeIt/blob/master/single_header/standalone/fakeit.hpp
2. インクルード+α
以下のようにヘッダファイルをインクルードして、using namespace fakeitとしてください。
1つのヘッダファイルをインクルードするだけなので、簡単。
#include "fakeit.hpp"
using namespace fakeit;
使い方
実際の使い方はこんな感じです。
// 実装したいインターフェースを指定する
Mock<ISample> mock;
// 以下のようにすると、SampleMethod1を実行すると、必ず1が返ってくる
When(Method(mock, SampleMethod1)).AlwaysReturn(1);
// ISample型のオブジェクトを取得
ISample& sample = mock.get();
// ここまでの準備により、sample.SampleMethod1(10)が1を返すようになっている。
Assert::AreEqual(1, sample.SampleMethod1(10));
// 実装したいインターフェースを指定する
Mock<ISample> mock;
// 以下のようにUsingを使うと、メソッドの引数を指定できる。
// SampleMethod1(1)を実行すると、必ず1が返ってくる
// SampleMethod1(2)を実行しても、1は返ってこない
When(Method(mock, SampleMethod1).Using(1)).AlwaysReturn(1);
// ISample型のオブジェクトを取得
ISample& sample = mock.get();
// ここまでの準備により、sample.SampleMethod1(1)が1を返すようになっている。
Assert::AreEqual(1, sample.SampleMethod1(1));
// 引数を指定してないsample.SampleMethod1(2)を実行するとテスト失敗
//sample.SampleMethod1(2);
// 実装したクラスの場合は、オブジェクトを生成してMockに渡す
SampleConcrete obj;
Mock<SampleConcrete> mock(obj);
// 実装しているメソッドを一部だけを上書きできる
When(Method(mock, SampleMethod1)).AlwaysReturn(5);
// SampleConcrete型のオブジェクトを取得
SampleConcrete& sample = mock.get();
// 上書きしたメソッド
Assert::AreEqual(5, sample.SampleMethod1(1));
// 実装されている元のメソッド
Assert::AreEqual(3, sample.SampleMethod2(1, 2));
HippoMocks
開発サイト
http://www.hippomocks.com/Main_Page
https://github.com/dascandy/hippomocks
導入方法
1. ヘッダファイルをコピー
以下のヘッダファイルを持ってきてください。
https://github.com/dascandy/hippomocks/blob/master/HippoMocks/hippomocks.h
2. インクルード
以下のようにヘッダファイルをインクルードしてしてください。
1つのヘッダファイルをインクルードするだけなので、簡単。
#include "hippomocks.h"
使い方
実際の使い方はこんな感じです。
// とりあえずMockRepositoryを生成
MockRepository mocks;
// ISample型のオブジェクトへのポインタを取得
// mocksのデストラクタで解放されるので、sampleを直接解放する必要はない
ISample* sample = mocks.Mock<ISample>();
// 以下のようにすると、SampleMethod1を実行すると、必ず1が返ってくる
mocks.ExpectCall(sample, ISample::SampleMethod1).Return(1);
// ここまでの準備により、sample.SampleMethod1(10)が1を返すようになっている。
Assert::AreEqual(1, sample->SampleMethod1(10));
// とりあえずMockRepositoryを生成
MockRepository mocks;
// ISample型のオブジェクトへのポインタを取得
// mocksのデストラクタで解放されるので、sampleを直接解放する必要はない
ISample* sample = mocks.Mock<ISample>();
// 以下のようにWithを使うと、メソッドの引数を指定できる。
// SampleMethod1(1)を実行すると、必ず1が返ってくる
// SampleMethod1(2)を実行しても、1は返ってこない
mocks.ExpectCall(sample, ISample::SampleMethod1).With(1).Return(1);
// ここまでの準備により、sample.SampleMethod1(1)が1を返すようになっている。
Assert::AreEqual(1, sample->SampleMethod1(1));
// 引数を指定してないsample.SampleMethod1(2)を実行するとテスト失敗
//sample->SampleMethod1(2);
// とりあえずMockRepositoryを生成
MockRepository mocks;
// SampleConcrete型のオブジェクトへのポインタを取得
SampleConcrete* sample = mocks.Mock<SampleConcrete>();
// 実装しているメソッドを上書きできる
mocks.ExpectCall(sample, ISample::SampleMethod1).Return(5);
// 上書きしたメソッド
Assert::AreEqual(5, sample->SampleMethod1(1));
// 実装されている元のメソッドは呼べない。
// NotImplementedExceptionが吐かれる。
// https://stackoverflow.com/questions/41547660/hippomocks-throws-notimplementedexception-when-not-specifying-expectation
//sample->SampleMethod2(1, 2);
// グローバル関数を上書きする前は、実装した通りのふるまい
Assert::AreEqual(1, SampleMethodGlobal(1));
// とりあえずMockRepositoryを生成
MockRepository mocks;
// グローバル関数も上書きできる
mocks.ExpectCallFunc(SampleMethodGlobal).Return(10);
// 上書きしたメソッド
Assert::AreEqual(10, SampleMethodGlobal(1));
// クラスのstaticメソッドを上書きする前は、実装した通りのふるまい
Assert::AreEqual(1, SampleConcrete::StaticMethod(1));
// とりあえずMockRepositoryを生成
MockRepository mocks;
// クラスのstaticメソッドも上書きできる
mocks.ExpectCallFunc(SampleConcrete::StaticMethod).Return(10);
// 上書きしたメソッド
Assert::AreEqual(10, SampleConcrete::StaticMethod(1));
Google Mock
開発サイト
チートシートがここ
https://github.com/google/googlemock/blob/master/googlemock/docs/CheatSheet.md
導入方法
1. Nugetからインストール
本家の導入方法には、自分でビルドする必要があって面倒です。
Visual Studioで開発している場合はNugetで取得できましたので、こっちを使います。
以下のようにGoogleMockで検索して、「googletestmock.v141」をインストールしてください。
GoogleオフィシャルのNugetパッケージはうまく動かなかったので、こっちの方がいいです。
2. インクルード
以下のようにヘッダファイルをインクルードしてしてください。
#include "gmock/gmock.h"
3. モッククラスを作成
Google Mockでは、モッククラスを自分で作成する必要があります。
以下のように作成します。
MOCK_METHOD#が定義されてますので、引数の数に合わせて使用します。
// インターフェースのモッククラス
class MockSample : public ISample
{
public:
MOCK_METHOD1(SampleMethod1, int(int lhs));
MOCK_METHOD2(SampleMethod2, int(int lhs, int rhs));
};
// 具象クラスのモッククラス
class MockSampleConcrete : public SampleConcrete
{
public:
// 実装しているメソッドを一部だけを指定できる
MOCK_METHOD1(SampleMethod1, int(int lhs));
};
使い方
実際の使い方はこんな感じです。
// 事前に準備したMock用のオブジェクトを生成
MockSample mock;
// 以下のようにすると、SampleMethod1を実行すると、必ず1が返ってくる
EXPECT_CALL(mock, SampleMethod1(testing::_)).WillRepeatedly(testing::Return(1));
// ここまでの準備により、sample.SampleMethod1(10)が1を返すようになっている。
Assert::AreEqual(1, mock.SampleMethod1(10));
// 事前に準備したMock用のオブジェクトを生成
MockSample mock;
// 以下のように、メソッドの引数を指定できる。
// SampleMethod1(1)を実行すると、必ず1が返ってくる
// SampleMethod1(2)を実行しても、1は返ってこない
EXPECT_CALL(mock, SampleMethod1(1)).WillRepeatedly(testing::Return(1));
// ここまでの準備により、sample.SampleMethod1(1)が1を返すようになっている。
Assert::AreEqual(1, mock.SampleMethod1(1));
// mock.SampleMethod1(2)を実行すると、指定しないので0が返ってくる
Assert::AreEqual(0, mock.SampleMethod1(2));
// 事前に準備したMock用のオブジェクトを生成
MockSampleConcrete mock;
// 実装しているメソッドを一部だけを上書きできる
EXPECT_CALL(mock, SampleMethod1(1)).WillRepeatedly(testing::Return(1));
// 上書きしたメソッド
Assert::AreEqual(1, mock.SampleMethod1(1));
// 実装されている元のメソッド
Assert::AreEqual(3, mock.SampleMethod2(1, 2));
最後に
今回はC++のMocking Frameworkを比較してみました。
お試しレベルで使ってみた感じでは非常に便利でした。
ただ、実際の大きなプロジェクトで使用すると、見えてない課題が出て来るかもしれませんね。
それにしても、FakeItとHippoMocksはどういう仕組みで実現してるんだろう。
コード見てもtemplateが駆使してあって全然わからない。。。