-
Notifications
You must be signed in to change notification settings - Fork 336
11.3 Kiwiを用いた振る舞いテスト
iOS開発でBDD(振る舞い駆動開発)を行うフレームワークとしてKiwiがあります。ここでは、Kiwiの紹介と導入、利用法を説明します。
Kiwiのソースコードは以下より取得することができます。
https://github.com/allending/Kiwi
Xcodeにはユニットテストを実行する機能が含まれており、⌘+Uで実行することが可能です。デフォルトのテストフレームワークはSenTestingKit(OCUnit)を採用しています。 KiwiはこのXcodeのユニットテストの機能を用いてテストを実行することができ、かつ内部でSenTestingKitを利用しているのでそのアサーションなどの機能も使うことができます。
ここでは、既存のプロジェクトにKiwiの導入する方法を紹介します。
(Xcodeのユニットテストについてはこちらをご覧ください https://developer.apple.com/jp/devcenter/ios/library/documentation/UnitTesting.pdf )
導入については、
https://github.com/allending/Kiwi/wiki/Getting-Started-with-Kiwi-2.0
こちらのページを参考にしています。
すでにプロジェクトに単体テストのターゲットが存在する場合は必要ありません
メニューバーよりFile → New → Target を選び Cocoa Touch Unit Testing Bundle を選択します。 名称は適当なものをつけてください。ここではKiwiTestとしました
以下のようにPodfileを書き
platform :ios
target :KiwiTest, :exclusive => true do
pod 'Kiwi' # XCTestのbundleをテストする場合は'Kiwi/XCTest'
end
インストールします
pod install
インストールが完了したら .xcworkspace よりプロジェクトを再起動します
まず、以下の点を確認します。
- CocoaPodsによりインストールされた設定ファイル( Pods-KiwiTest.xcconfing ) がプロジェクトに含まれていること
- プロジェクトナビゲーターのプロジェクトよりConfigurationsを確認し、ターゲット KiwiTest にPods-KiwiTestの設定があたっていること
次にKiwiTestのtargetに移動してBuildSettingを修正します。
- BundleLoaderの値を
$(BUILT_PRODUCTS_DIR)/MyProject.app/ApplicationTargetName
に変更します。今回のサンプルプロジェクトの場合は
$(BUILT_PRODUCTS_DIR)/janken.app/janken
TestHostも変更します
$(BUNDLE_LOADER)
次にSchemeの設定を行います。
まず、ターゲットの選択よりEditSchemeを選択します。
出てきたスキームの編集画面で、Testを選び、その中のTestを選びます。その後、下部にある+
ボタンをクリックします
次に中に含まれるKiwiTestを選択し、OKを押します
ここまでで、導入は完了です。Xcode上で⌘+Uとするとテストが実行されます。 実行されて、失敗すれば完了です。 (失敗は、サンプルのテスト KiwiTest.m の中でSTFailが含まれているためです)
以上でプロジェクトにKiwiを導入することができました。以下では、Kiwiを用いたテストの書き方について解説します。
KiwiのテストはRSpecの書き方と非常に良く似ており、振る舞い(behavior)をベースとした書き方です。
- テンプレートより Objcetive-C test case class を選択し、新しいテストクラスを作成します
- ヘッダファイルは不要なので削除します。.mファイルからもimport文を削除し、
@implementation
~@end
も削除します - Kiwiのヘッダファイルをインポートします
#import <Kiwi.h>
- テストしたいクラスのヘッダファイルもインポートします
#import "MixiViewController.h"
(注意:view controller の生成などは可能ですがxibをロードするなど、画面描画を行うテストは出来ないようです) https://github.com/allending/Kiwi/wiki/Kiwi-FAQ
-
SPEC_BEGIN
で始めてSPEC_END
で終わる -
describe
以下に振る舞いを書いていく -
it( @"description", ^{ /* テスト内容 */ })
のようにit()の内部に振る舞いを記述する -
[[objA should] equal:objB]
のような形で要件を満たしているかをチェックする - メソッドの呼び出しを確認したい場合は、
[[obj should] receive:(SEL)aSelector]
とします。呼び出し回数や引数のチェックなども可能です。
例えば、ジャンケンの勝敗を考えるテストの場合だと以下のように書くことができます。
SPEC_BEGIN(MixiJankenDeciderSpec)
describe(@"Winner", ^{ // 勝敗についての振る舞い(仕様)を記述する
__block MixiJankenPeople *alice;
__block MixiJankenPeople *bob;
__block MixiJankenPeople *winner;
beforeAll(^{ // 各 it 以下で始まる振る舞いが実行される前に呼び出される
alice = [[MixiJankenPeople alloc] init];
bob = [[MixiJankenPeople alloc] init];
winner = nil;
});
it(@"is even", ^{ // 引き分けになる場合の振る舞い
alice.hand = JankenHandTypePaper;
bob.hand = JankenHandTypePaper;
winner = [MixiJankenDecider jankenWithPeoples:@[alice, bob]];
[winner shouldBeNil]; // nilになることをチェック
});
it(@"is alice", ^{ // アリスが勝つ場合の振る舞い
alice.hand = JankenHandTypeScissors;
bob.hand = JankenHandTypePaper;
winner = [MixiJankenDecider jankenWithPeoples:@[alice, bob]];
[[winner should] equal:alice]; // アリスが勝つことをチェック
});
it(@"is bob", ^{ // ボブが勝つ場合の振る舞い
alice.hand = JankenHandTypeRock;
bob.hand = JankenHandTypePaper;
winner = [MixiJankenDecider jankenWithPeoples:@[alice, bob]];
[[winner should] equal:bob]; // ボブが勝つことをチェック
});
});
SPEC_END
モックやスタブは、単体テストをする上で、必要となるオブジェクトを一部擬似的にエミュレートする機能である。 例えば、通信結果をパースするクラスで、実際にサーバーにアクセスするとサーバーでも常に同じレスポンスを返すことを保証しないといけない。そのようなときに用いるのがモック、スタブである。
- モックオブジェクトの生成には、
[SomeClass mock]
や[KWMock mockForClass:(Class)Class]
を用います - デリゲートなどのモックを作りたい場合は
[KWMock mockForProtocol:(Protocol *)aProtocol]
を用います。 - 例えば、あるインスタンスのプロパティをモックして置き換える場合は次のようにします
NSString *mockName = [NSString mock];
obj.name = mockName;
- モックオブジェクトについても、呼び出しの回数を確認することができます。
使用例を以下に示します。KiwiではUIKitから派生する、UILabelなどが生成されないことがあります。以下ではUILabelをモックして、そのsetText:
が呼ばれることの確認を行っています。
describe(@"spec for winner label updates", ^{
__block MixiViewController *viewController;
__block UILabel *mockWinnerLabel;
beforeAll(^{
viewController = [MixiViewController new];
[viewController viewDidLoad];
mockWinnerLabel = [UILabel mock]; // mock オブジェクトの生成
viewController.winnerLabel = mockWinnerLabel; // viewControllerのプロパティを上書き
});
it(@"is not updated", ^{
[[mockWinnerLabel shouldNot] receive:@selector(setText:)]; // mockオブジェクトについてメソッドが呼ばれないことを確認
viewController.alice.hand = JankenHandTypeUnknown;
viewController.bob.hand = JankenHandTypePaper;
[viewController updateWinnerLabel];
});
};
モックはインスタンス全体を偽装するのに対し、スタブはあるメソッドを入れ替えます。あるメソッドを入れ替える場合[obj stub:(SEL)aSelector]
とします。何かしらの戻り値を期待する場合は[obj stub:(SEL)aSelector andReturn:(id)aValue]
を用います。
以下では、クラスメソッドをスタブした例を示します。(上の例に引き続いています)
it(@"is even", ^{
[MixiJankenDecider stub:@selector(jankenWithPeoples:) andReturn:nil]; // MixiJankenDeciderのクラスメソッドjankenWithPeoplesをスタブし、戻り値がnilとなるようにしています
[[mockWinnerLabel should] receive:@selector(setText:) withArguments:@"あいこ"];
viewController.alice.hand = JankenHandTypePaper;
viewController.bob.hand = JankenHandTypePaper;
[viewController updateWinnerLabel];
});
はじめに
-
導入
-
1.3 UIViewController1 UIViewController のカスタマイズ(xib, autoresizing)
-
UIKit 1 - container, rotate-
-
UIKit 2- UIView -
-
UIKit 3 - table view -
-
UIKit 4 - image and text -
-
ネットワーク処理
-
ローカルキャッシュと通知
-
Blocks, GCD
-
設計とデザインパターン
-
開発ツール
-
テスト
-
In-App Purchase
-
付録