勉強用に公式マニュアルを翻訳中。機械翻訳様ベース。
expect callを「呼び出し期待」、actual callを「呼び出し実施」と訳してみる。
Simple Scenario / シンプルなシナリオ
最も簡単なシナリオは、特定の関数呼び出しが行われたかどうかを確認することです。 以下のコードはこの例です:
# include "CppUTest/TestHarness.h"
# include "CppUTestExt/MockSupport.h"
TEST_GROUP(MockDocumentation)
{
void teardown()
{
// Mock資源をクリア
mock().clear();
}
};
// 関数productionCode() を置き換えるMock関数
void productionCode()
{
// 呼び出し実施を記録する
mock().actualCall("productionCode");
}
TEST(MockDocumentation, SimpleScenario)
{
// productionCode()の呼び出し期待を記録する
mock().expectOneCall("productionCode");
// Mock関数を呼び出す
productionCode();
// 結果検証
mock().checkExpectations();
}
mockingのための唯一の追加インクルードは CppUTestExt/MockSupport.h であり、これはCppUTest Mockingの主なインクルードです。 TEST_GROUPの宣言はいつもと同じで、違いはありません。
TEST(MockDocumentation, SimpleScenario) には、期待値の記録が含まれています:
mock().expectOneCall("productionCode");
mock() の呼び出しは、グローバルなMockSupportを返します(詳細は後で説明します)。 この例では、productionCodeという関数の1回の呼び出し期待を記録する expectOneCall("productionCode") を呼び出します。
productionCodeの呼び出しとは、本物のコードとは関係なく、呼び出しを示します。 テストは、期待が満たされているかどうかをチェックすることで終了します。下記でチェックします:
mock().checkExpectations();
MockSupportPluginを使用しない場合、この呼び出しが必要です。MockSupportPluginを使用する場合は、各テストごとに自動的に行われます。また、MockSupportPluginを使用している場合、teardownでmock().clear()を呼び出す必要はありません。それ以外の場合はMockSupportをクリアする必要があります。 クリアされなければ、メモリリークディテクタはMock関数の呼び出しをリークとして報告します。
実行されるMock関数呼び出しは以下のようにします:
void productionCode()
{
mock().actualCall("productionCode");
}
ここでは mock() を呼び出すことで MockSupportを使用し、
ここでは、MockSupportを使用してmock()を呼び出し、発生した呼び出しをproductionCode関数に記録します。
このシナリオは、サードパーティのライブラリなどのリンカスタブを使用する場合には非常に一般的です。
productionCodeの呼び出しが行われない場合、テストは次のエラーメッセージで失敗します。
ApplicationLib/MockDocumentationTest.cpp:41: error: Failure in TEST(MockDocumentation, SimpleScenario)
Mock Failure: Expected call did not happen.
EXPECTED calls that did NOT happen:
productionCode -> no parameters
ACTUAL calls that did happen:
<none>
時々、同じ関数へのいくつかの同一の呼び出し 、例えば、productionCodeへの5回の呼び出しを期待する場合があります。 そのような状況のための便利な簡略表記があります:
mock().expectNCalls(5, "productionCode");
Using Objects / オブジェクトの使用
オブジェクトを使用したシンプルなシナリオでは、Mock関数とオブジェクトには違いはありません。以下のコードは、実行時モック化を使ったMockを示しています。
// 本物クラスClassFromProductionCodeを継承したMockクラスClassFromProductionCodeMockを定義する
class ClassFromProductionCodeMock : public ClassFromProductionCode
{
public:
// 対象関数importantFunction()を置き換える
virtual void importantFunction()
{
// importantFunction()の呼び出し実施を記録する
mock().actualCall("importantFunction");
}
};
TEST(MockDocumentation, SimpleScenarioObject)
{
// importantFunction()の呼び出し期待を記録する
mock().expectOneCall("importantFunction");
// create mock instead of real thing
//
ClassFromProductionCode* object = new ClassFromProductionCodeMock;
object->importantFunction();
mock().checkExpectations();
delete object;
}
このコードはわかりやすいです。 本当のオブジェクトは、手作りのモックオブジェクトに置き換えられます。 モックへのコールは、コール実施をMockSupport経由で記録します。
オブジェクトを使用している場合は、次のようにして、正しいオブジェクトで呼び出しが行われたかどうかを確認することもできます。
mock().expectOneCall("importantFunction").onObject(object);
呼び出し実施は次のように書きます。
mock().actualCall("importantFunction").onObject(this);
間違ったオブジェクトへの呼び出しが発生すると、次のエラーメッセージが表示されます。
MockFailure: Function called on a unexpected object: importantFunction
Actual object for call has address: <0x1001003e8>
EXPECTED calls that DID NOT happen related to function: importantFunction
(object address: 0x1001003e0)::importantFunction -> no parameters
ACTUAL calls that DID happen related to function: importantFunction
<none>
Parameters / パラメータ
もちろん、関数が呼び出されているかどうかを確認するだけでパラメータを確認できないのでは、特に役に立ちません。 関数のパラメータを次のように記録します:
mock().expectOneCall("function").onObject(object).withParameter("p1", 2).withParameter("p2", "hah");
呼び出し実施は次のようになります。
mock().actualCall("function").onObject(this).withParameter("p1", p1).withParameter("p2", p2);
パラメータが渡されないと、次のエラーが発生します。
Mock Failure: Expected parameter for function "function" did not happen.
EXPECTED calls that DID NOT happen related to function: function
(object address: 0x1)::function -> int p1: <2>, char* p2: <hah>
ACTUAL calls that DID happen related to function: function
<none>
MISSING parameters that didn't happen:
int p1, char* p2
Objects as Parameters / パラメータとしてのオブジェクト
withParametersは、int
、double
、const char *
またはvoid *
のみを使用できます。 しかし、パラメータは基本型ではなく、他の型のオブジェクトであることがよくあります。 オブジェクトをパラメータとして扱うには? 以下はその例です:
mock().expectOneCall("function").withParameterOfType("myType", "parameterName", object);
withParameterOfTypeを使用する場合、Mockingフレームワークは型を比較する方法を知る必要があるため、この型のパラメータを使用する前にComparatorをインストールする必要があります。これは、次のようにinstallComparatorを使用して行われます。
MyTypeComparator comparator;
mock().installComparator("myType", comparator);
MyTypeComparatorはカスタムのコンパレータで、MockNamedValueComparatorインターフェイスを実装しています。 例えば:
class MyTypeComparator : public MockNamedValueComparator
{
public:
virtual bool isEqual(const void* object1, const void* object2)
{
return object1 == object2;
}
virtual SimpleString valueToString(const void* object)
{
return StringFrom(object);
}
};
isEqualは、2つのパラメータを比較するために呼び出されます。 valueToStringは、エラーメッセージが出力されたときに呼び出され、実際に与えられた値と期待する値を出力する必要があります。 通常のC関数を使いたい場合は、コンストラクタ内の関数へのポインタを受け付けるMockFunctionComparatorを使うことができます。
コンパレータを削除するには、removeAllComparatorsAndCopiersを呼び出すだけです:
mock().removeAllComparatorsAndCopiers();
警告1:
コンパレータ変数のスコープに注意してください!
コンパイラーはコピーされず、代わりにinstallComparator関数に渡される正確なインスタンスが使用されます。 したがって、フレームワークがそれを使用しようとすると、スコープ内にあることを確認してください! たとえば、TEST内にinstallComparatorを実装しても、teardownでcheckExpectationsを実行すると、コンパレータが破棄されたためクラッシュする可能性があります。
警告2:
MockPluginを使用するときにスコープに細心の注意を払う
MockPlugin(推奨)を使用する場合は、MockPluginを介してコンパイラをインストールするか、グローバル・スペースに配置することをお勧めします。 checkExpectationsはティアダウン後に呼び出され、ティアダウンでコンパレータが破棄された場合、クラッシュが発生します。
Output Parameters / 出力パラメータ
いくつかのパラメータは、呼び出された関数に渡されるデータを表すものではありませんが、参照先として渡されるため、関数はポイント先のデータを変更して値を返すことができます。
CppMockでは、これらの出力パラメータの値を期待される呼び出しで指定することができます。
int outputValue = 4;
mock().expectOneCall("Foo").withOutputParameterReturning("bar", &outputValue, sizeof(outputValue));
...そして呼び出し実施の中で書かれた:
void Foo(int *bar)
{
mock().actualCall("Foo").withOutputParameter("bar", bar);
}
呼び出し実施の後、関数Fooに渡されたbarパラメーターは、期待される呼び出し(この場合は4)で指定された値を持ちます。
警告1:
CppMock は、出力パラメータを使用しているときに無効なメモリアクセスを防止したり、防ぐことができません 。 これは、withOutputParameterReturning呼び出しで指定されたバイト数を正確にmemcpyします。 セグメンテーションフォールトは、呼び出し実施で提供される出力パラメータが指すデータより大きい場合に発生する可能性があります。
withOutputParameterReturningの関数オーバーロードは、char、int、unsigned、long、unsigned long、およびdouble型に対して提供され、sizeパラメータを省略することができます。
char charOutputValue = 'a';
mock().expectOneCall("Foo").withOutputParameterReturning("bar", &charOutputValue);
int intOutputValue = 4;
mock().expectOneCall("Foo").withOutputParameterReturning("bar", &intOutputValue);
unsigned unsignedOutputValue = 4;
mock().expectOneCall("Foo").withOutputParameterReturning("bar", &unsignedOutputValue);
long longOutputValue = 4;
mock().expectOneCall("Foo").withOutputParameterReturning("bar", &longOutputValue);
unsigned long unsignedLongOutputValue = 4;
mock().expectOneCall("Foo").withOutputParameterReturning("bar", &unsignedLongOutputValue);
double doubleOutputValue = 4;
mock().expectOneCall("Foo").withOutputParameterReturning("bar", &doubleOutputValue);
警告2:
char、intなどの配列がwithOutputParameterに渡されるときは、汎用のwithOutputParameterReturningを使用して、配列の実際のサイズを指定するか、1つの要素だけをコピーする必要があります。
Output Parameters Using Objects / オブジェクトを使用した出力パラメータ
出力パラメータを処理する最良の方法は、カスタムタイプのコピー機(v3.8)を使用することです。 一般的な原則は、上記カスタムコンパレータと似ています。
MyType outputValue = 4;
mock().expectOneCall("Foo").withOutputParameterOfTypeReturning("MyType", "bar", &outputValue);
対応する呼び出し実施の書き方は次のとおりです。
void Foo(int *bar)
{
mock().actualCall("Foo").withOutputParameterOfType("MyType", "bar", bar);
}
withOutputParameterOfTypeReturningを使用する場合、Mockフレームワークは型をコピーする方法を知る必要があるため、この型のパラメータを使用する前にCopierをインストールする必要があります。 これは、次のようにinstallCopierを使用して行います。
MyTypeCopier copier;
mock().installCopier("myType", copier);
MyTypeCopierはカスタムコピー機で、MockNamedValueCopierインターフェイスを実装しています。 例えば:
class MyTypeCopier : public MockNamedValueCopier
{
public:
virtual void copy(void* out, const void* in)
{
*(MyType*)out = *(const MyType*)in;
}
};
コピー機を削除するには、removeAllComparatorsAndCopiersを呼び出す必要があります。
mock().removeAllComparatorsAndCopiers();
上記の警告1と警告2は複写機にも適用されます。
Return Values / 戻り値
モック関数が生産コードで使用できる値を返すようにする必要があることがあります。 テストコードは次のようになります。
mock().expectOneCall("function").andReturnValue(10);
モック関数は次のようになります:
int function () {
return mock().actualCall("function").returnIntValue();
}
またはreturnIntValueをactualCall(その下に!)から分離することができます:
int function () {
mock().actualCall("function");
return mock().intReturnValue();
}
戻り値オプションは、テストオブジェクトとモックオブジェクトの間でデータを転送するために使用され、テスト自体が失敗することはありません。
Passing other data / 他のデータを渡す
時には、テストでモックオブジェクトにさらに多くのデータを渡して、たとえば、計算でいくつかのパラメータだけを変更したいとします。 これは次のようにすることができます:
ClassFromProductionCode object;
mock().setData("importantValue", 10);
mock().setDataObject("importantObject", "ClassFromProductionCode", &object);
そして、モックオブジェクトのように使用することができます:
ClassFromProductionCode * pobject;
int value = mock().getData("importantValue").getIntValue();
pobject = (ClassFromProductionCode*) mock().getData("importantObject").getObjectPointer();
戻り値と同様に、設定データはテストに失敗することはありませんが、モックオブジェクトの構築をサポートします。
Other MockSupport / その他のMockSupport
MockSupportは他の便利な機能をいくつか提供しています。これについてはこのセクションで説明します。
多くの場合、テストでいくつかの呼び出しをチェックし、他のすべての呼び出しを無視したいだけです。 これらの呼び出しごとにexpectOneCallを追加すると、テストが大きくなりすぎる可能性があります(ただし、テストが実際には大きすぎるという匂いかもしれません)。 これを防ぐ1つの方法はignoreOtherCallsです:
mock().expectOneCall("foo");
mock().ignoreOtherCalls();
これは、fooの1つの呼び出しが起こることをチェックします(そして1つだけの呼び出し!)が、他のすべての呼び出しは無視されます( "bar"など)。
場合によっては、単にコールを無視するのではなく、しばらくの間(何かをするために)モックフレームワーク全体を無効にすることがあります。 これは初期化時に時々起こります。ここではフレームワークチェックコールを使わずに何かをしたいかもしれません。 これを行うには、次のような有効/無効を設定します。
mock().disable();
doSomethingThatWouldOtherwiseBlowUpTheMockingFramework();
mock().enable();
戻り値を持つ関数を嘲笑して呼び出しを無視すると、キャッチがあります。 テストを無視しようとすると、おそらくランタイムエラーが発生します。
人生のほぼすべてのことを例として説明することは簡単です。このモック関数があるとしましょう:
int function () {
return mock().actualCall("function").returnIntValue();
}
それを無視するかフレームワークを無効にしようとすると、フレームワークが爆発します。 どうして ? 戻り値は定義されていないので、Mock関数の戻り値を定義する方法はありません。
このような場合には、mock関数が無視されたときに返されるデフォルトの戻り値を定義するための一連の戻り値関数があります。
上記の例は次のように書くことができます:
int function () {
return mock().actualCall("function").returnIntValueOrDefault(5);
}
関数を無視するかフレームワークを無効にすると、5が返されます。それ以外の場合は、期待される戻り値が返されます(テストケースに設定された期待通りに返されます)。
呼び出し実施オブジェクトを使用する代わりに、MockSupportを直接使用することもできます。
int function () {
mock().actualCall("function");
return mock().returnIntValueOrDefault(5);
}
すべての期待値、設定、コンパレータをクリアしたい場合は、clearを呼び出します:
mock().clear();
ClearはcheckExpectを実行しませんが、すべてを消去してやり直します。 通常、checkExpectationsの後にclear()が呼び出されます。
場合によっては、Mockの呼び出し実施が発生しますが、どこから呼び出されたかを把握することはできません。 あなたがコールスタックをもっていれば、あなたはそれを追跡することができます。 残念なことに、mockingフレームワークはスタックトレースを出力しませんが、クラッシュすることはできます。 MockSupportでcrashOnFailureを呼び出すと、クラッシュし、デバッガを使用してスタックトレースを取得できます。 次のように:
mock().crashOnFailure();
gdbを使用する場合は、次のようにスタックトレースを取得します。
gdb examples/CppUTestExamples_tests
r
bt
(r は run で、クラッシュするまで実行されます。bt は back trace で、スタックを生成します)
MockSupport Scope / MockSupportのスコープ
MockSupportは、MockSupportスコープを使用して階層的に使用できます。 これは本当に複雑に聞こえますが、実際は非常に簡単です。 モック関数を使ってモックをサポートする場合、ネームスペースまたはスコープを渡して、このスコープ内に期待値を記録する(または他のことを行う)ことができます。 例えば:
mock("xmlparser").expectOneCall("open");
呼び出し実施は次のようになります。
mock("xmlparser").actualCall("open");
別の名前空間での呼び出しは機能しません。たとえば、xmlparser openの呼び出しと一致しません。
mock("").actualCall("open");
コールを名前空間に保持すると、1つのタイプのコールを無視して別のコールに集中することが容易になります。
mock("xmlparser").expectOneCall("open");
mock("filesystem").ignoreOtherCalls();
MockSupportPlugin / MockSupportプラグイン
MockSupportPluginを使用すると、モックで作業するのが容易になります。 MockSupportPluginの完全なドキュメントはPlugin Manualページにあります。
C Interface / Cインタフェース
.cppファイルではなく.cファイルからモーキングフレームワークにアクセスすると便利なことがあります。 たとえば、何らかの理由でスタブが.cppファイルではなく.cファイルに実装されているとします。 すべてを.cppに変更するのではなく、MockフレームワークをC経由で呼び出すことができれば簡単です.Cインターフェイスは、このためのものです。 インターフェイスはC ++をベースにしているので、以下にいくつかコードがありますが、これらが何をしているのか分かりやすいはずです(前に書いたものをすべて読んでいれば)。
# include "CppUTestExt/MockSupport_c.h"
mock_c()->expectOneCall("foo")->withIntParameters("integer", 10)->andReturnDoubleValue(1.11);
return mock_c()->actualCall("foo")->withIntParameters("integer", 10)->returnValue().value.doubleValue;
mock_c()->installComparator("type", equalMethod, toStringMethod);
mock_scope_c("scope")->expectOneCall("bar")->withParameterOfType("type", "name", object);
mock_scope_c("scope")->actualCall("bar")->withParameterOfType("type", "name", object);
mock_c()->removeAllComparators();
mock_c()->setIntData("important", 10);
mock_c()->checkExpectations();
mock_c()->clear();
Cインターフェイスは、C ++インターフェイスと同様のビルダー構造を使用します。 C言語ではあまり一般的ではありませんが、同じように動作します。
現在、C ++と同じ方法で実際の戻り値を指定することもできます(v3.8)。
return mock_c()->actualCall("foo")->withIntParameters("integer", 10)->doubleReturnValue();
return mock_c()->doubleReturnValue();
呼び出しが発生したときにmockingが無効になっている場合の、デフォルトの戻り値を指定します(v3.8)。
return mock_c()->actualCall("foo")->withIntParameters("integer", 10)->returnDoubleValueOrDefault(2.25);
return mock_c()->returnDoubleValueOrDefault(2.25);
Miscellaneous / 雑多
特定のモック関数呼び出しが起こらないようなテストを明示したい場合は、次のように書くことができます(v3.8):
mock().expectNoCall("productionCode");
そうすることは機能的には何の期待もないと同等です。