特定の処理内で例外が発生したかテストするマッチャーも用意されています。次のように、ブロック内で例外が発生したかどうかをテストできます。
[[theBlock(^{ // エラーを発生させる [NSException raise:@"SampleException" reason:@“ERROR-001”]; }) should] raiseWithName:@"SampleException" reason:@"ERROR-001"];
例外のテストで使用できるマッチャーは次の通りです。
マッチャー | 説明 |
---|---|
[[theBlock(^{ ... }) should] raise] | ブロック内で例外が発生したか |
[[theBlock(^{ ... }) should] raiseWithName:] | ブロック内で指定した名前の例外が発生したか |
[[theBlock(^{ ... }) should] raiseWithReason:(NSString *)aReason] | ブロック内で指定した理由の例外が発生したか |
[[theBlock(^{ ... }) should] raiseWithName:(NSString *)aName reason:(NSString *)aReason] | ブロック内で指定した名前・理由の例外が発生したか |
ここまで、さまざまなマッチャーを見てきましたが、iOSアプリケーションの要件によってはカスタムのマッチャーを作らなければいけない場合があります。そういった場合を想定し、Kiwiではカスタムのマッチャーを作成できます。
そこで、今回はNSDateを比較するシンプルなマッチャーを作ってみたいと思います。
まずKWMatcherクラスを継承したクラス「KWSDateMatcher」を新規に作成してください。KWMatcherクラスはマッチャーの最低限の動作を定義した、NSObjectクラスのカテゴリクラスです。クラスは次のように実装してください。
#import "KWMatcher.h" @interface KWSDateMatcher : KWMatcher @property (nonatomic, readwrite) NSInteger otherSubject; - (void)equalMonth:(NSInteger)anObject; @end
#import "KWSDateMatcher.h" @implementation KWSDateMatcher // マッチャーのメソッド名を登録する + (NSArray *)matcherStrings { return [NSArray arrayWithObjects:@"equalMonth:", nil]; } // 評価の処理を実装する - (BOOL)evaluate { NSCalendar* calendar = [NSCalendar currentCalendar]; NSDateComponents* components = [calendar components:NSMonthCalendarUnit fromDate:self.subject]; NSInteger expect = (int)components.month; return expect == self.otherSubject; } // shoudマクロの場合のテスト失敗時のメッセージ - (NSString *)failureMessageForShould { return @"期待値と異なる"; } // shoudNotマクロの場合のテスト失敗時のメッセージ - (NSString *)failureMessageForShouldNot { return @"期待値と同一"; } // マッチャーのメソッド - (void)equalMonth:(NSInteger)anObject { self.otherSubject = anObject; } @end
順番に確認していきましょう。まずmatcherStringsメソッドでこのマッチャークラスで使えるメソッドを配列で指定します。次のevaluateメソッドは実際の比較処理を行うメソッドで、テストケースが実行され比較するときに呼び出されます。ここではNSDateの月を比較している実装になっています。次のfailureMessageForShouldメソッドとfailureMessageForShouldNotメソッドは、評価に失敗したときにエラーとして表示する文字列を返すようにします。最後のequalMonth:メソッドがカスタムのマッチャーメソッドになります。
このマッチャークラスをテストケースで使用するには、テストケースでregisterMatchersメソッドを使います。このメソッドは引数に渡されたプレフィックスのクラスをマッチャーとしてKiwiに登録するメソッドです。次のようになります。
#import "Kiwi.h" #import "KWSDateMatcher.h" SPEC_BEGIN(NSDateSpec) registerMatchers(@"KWS"); describe(@"NSDate", ^{ it(@"3月であること", ^{ NSDate *date = [NSDate new]; [[date should] equalMonth:3]; }); }); SPEC_END
このように、既存のマッチャーで物足りない場合はカスタムのマッチャークラスを簡単・自由に追加できます。
テストケースを書いていると、ある条件におけるオブジェクトを再現する際、オブジェクトやプロパティがどうしても再現できなかったり、再現するために必要な処理が多くて面倒なことがあります。このような再現が困難なオブジェクトを疑似的に再現する仕組みがモックとスタブです。
「モック」はオブジェクトを疑似的に再現したい場合に使い、「スタブ」はメソッドの戻り値を疑似的に再現したい場合に使います。
モックは対象のクラスのmockメソッドか、KWMockクラスのmockForClassメソッドを使って生成します。これだけでモックオブジェクトは「対象のクラスから生成したオブジェクト」として取り扱えます。
下記の例では、Carクラスのmockオブジェクトを生成しています。モックオブジェクトのクラスがCarクラスであるかという確認と、currentGearメソッドの戻り値の確認をしています。
// モックの生成 id carMock = [Car mock]; // クラスの比較の確認 [[carMock should] beMemberOfClass:[Car class]]; // 呼び出される予定のメソッドの戻り値の指定 [[carMock should] receive:@selector(currentGear) andReturn:theValue(3)]; // メソッドの呼び出し確認 [[theValue(carMock.currentGear) should] equal:theValue(3)];
上記の例のように生成したモックでは、receiveメソッドで指定されていないメソッドが1つでも呼び出されると例外が出力されるため、呼び出されるメソッドの全てについて、receiveメソッドを指定しなければいけません。
このような問題を解決するため、Kiwiではクラスのメソッドを全て自動で追加してくれる「NullMock」と呼ばれるモックオブジェクトを生成できます。NullMockは対象のクラスのnullMockメソッドか、KWMockクラスのnullMockForClassメソッドを使って生成します。モックの戻り値を考慮する必要がない場合や、例外を発生させたくない場合に便利です。
// NullMockの生成 id carNullMock = [Car nullMock]; // メソッドの戻り値の確認 [[theValue(carNullMock.currentGear) should] equal:theValue(0)]; // メソッドの呼び出しの確認 [carNullMock applyBrakes];
Kiwiでは、さらにもう1つProtocolMockを生成する機能もあります。この機能を使うと、Delegateのモックなどを生成できます。ProtocolMockはKWMockのmockForProtocolメソッドを使って生成します。
// ProtocolMockの生成 id flyerMock = [KWMock mockForProtocol:@protocol(FlyingMachine)]; // FlyingMachineプロトコルに準拠しているか確認する [[flyerMock should] conformToProtocol:@protocol(FlyingMachine)];
オブジェクトのメソッドをスタブのメソッドに差し替えるには、スタブを適用したいオブジェクトのstubメソッド、またはstub:andReturnメソッドを使います。引数には差し替えたいメソッドのセレクターを渡します。
id cruiser = [Cruiser cruiser]; // energyLevelInWarpCoreメソッドをスタブに差し替える [[cruiser stubAndReturn:theValue(42.0f)] energyLevelInWarpCore:7]; // energyLevelInWarpCoreメソッドの戻り値の確認 float energyLevel = [cruiser energyLevelInWarpCore:7]; [[theValue(energyLevel) should] equal:theValue(42.0f)];
なお、オブジェクトに適用したスタブは、itメソッドのブロックの最後に解除されます。
本稿では、機能の振る舞いをテストするためのテスティングフレームワークであるKiwiの導入方法と使い方を解説しました。テストケースをコンテキストによって階層構造で表現できるので、分かりやすく無駄の無いテストケースが作成できると思います。
テストは一度実行しただけでは意味はありません。日々繰り返し、継続することでプロジェクトに多くの価値をもたらします。
不慣れなうちは、まずは比較的テストしやすい部分(ユーティリティクラスなど)からテストケースを書き始めるといいと思います。そこから徐々に慣れていき、テストする範囲を増やしていければいいでしょう。実装を進めながらテストケースを書いて確認する習慣が楽しくなってくるはずです。
Copyright © ITmedia, Inc. All Rights Reserved.