Skip to content
This repository has been archived by the owner on Jun 20, 2023. It is now read-only.

11.3 Kiwiを用いた振る舞いテスト

Brian Gesiak edited this page Mar 5, 2014 · 4 revisions

iOS開発でBDD(振る舞い駆動開発)を行うフレームワークとしてKiwiがあります。ここでは、Kiwiの紹介と導入、利用法を説明します。

Kiwiのソースコードは以下より取得することができます。

https://github.com/allending/Kiwi

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としました

ターゲットの追加

CocoaPods を用いてインストール

以下のように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の設定があたっていること Configuration設定

次にKiwiTestのtargetに移動してBuildSettingを修正します。

  • BundleLoaderの値を
$(BUILT_PRODUCTS_DIR)/MyProject.app/ApplicationTargetName

に変更します。今回のサンプルプロジェクトの場合は

$(BUILT_PRODUCTS_DIR)/janken.app/janken

TestHostも変更します

$(BUNDLE_LOADER)

Scheme の設定

次にSchemeの設定を行います。

まず、ターゲットの選択よりEditSchemeを選択します。

出てきたスキームの編集画面で、Testを選び、その中のTestを選びます。その後、下部にある+ボタンをクリックします

scheme_1

次に中に含まれるKiwiTestを選択し、OKを押します

scheme_2

ここまでで、導入は完了です。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

mock / stub

モックやスタブは、単体テストをする上で、必要となるオブジェクトを一部擬似的にエミュレートする機能である。 例えば、通信結果をパースするクラスで、実際にサーバーにアクセスするとサーバーでも常に同じレスポンスを返すことを保証しないといけない。そのようなときに用いるのがモック、スタブである。

Mock

  • モックオブジェクトの生成には、[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];
    });
};

Stub

モックはインスタンス全体を偽装するのに対し、スタブはあるメソッドを入れ替えます。あるメソッドを入れ替える場合[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. iOSについて

  2. Xcode最初のステッフ

  3. 導入

  4. Objective C の基礎

  5. メモリ管理

  6. 1.3 UIViewController1 UIViewController のカスタマイズ(xib, autoresizing)

  7. 1.3 UIViewController1 UIViewController のカスタマイズ(storyboard)

  8. UIViewController2 - ModalViewController

  9. UIViewController2 - ModalViewController(storyboard)

  10. UIViewController3 - ライフサイクル

  11. HomeWork 1 Objective C の基本文法

  12. HomeWork 2 UIViewControllerとModalViewController

  13. HomeWork 3 UIViewController + Animation

  14. UIKit 1 - container, rotate-

  15. UINavigationController

  16. UITabController

  17. Custom Container View Controller

  18. Supporting Multiple Interface Orientations

  19. HomeWork 1 - タブバーからモーダルビューを表示する

  20. HomeWork 2 - NavigationController

  21. HomeWork 2.3 デバイスことに回転対応

  22. UIKit 2- UIView -

  23. UIView

  24. UIView のカスタマイズ

  25. UIView Animation

  26. HomeWork 1 - UIScrollView

  27. UIKit 3 - table view -

  28. UITableView について

  29. UITableViewとNavigationController

  30. custom UITableViewCell の作成

  31. UITableViewのその他のオプション、カスタマイズ

  32. HomeWork 1 - Dynamic height with a custom uitableviewcell

  33. UIKit 4 - image and text -

  34. UIImagePickerController

  35. Assets Library

  36. UITextFiled, UITextView

  37. KeyboardNotification

  38. Homework 1 - フォトの複数枚選択

  39. ネットワーク処理

  40. NSURLConnection

  41. JSONのシリアライズとデシリアライズ

  42. UIWebView

  43. ローカルキャッシュと通知

  44. NSUserDefaults, Settings Bundle

  45. NSFileManager

  46. Key Value Observing

  47. NSNotification、NSNotificationCenter を用いた通知

  48. UILocalNotification

  49. Blocks, GCD

  50. Blocks

  51. GCD

  52. 【演習】GCD,-Blocksを用いたHTTPリクエストマネージャの作成

  53. 設計とデザインパターン

  54. クラス設計 1

  55. クラス設計 2

  56. [クラス設計演習] (https://github.com/mixi-inc/iOSTraining/wiki/9.3-%E3%82%AF%E3%83%A9%E3%82%B9%E8%A8%AD%E8%A8%88%E6%BC%94%E7%BF%92)

  57. 開発ツール

  58. Instruments, デバッガ

  59. CocoaPods

  60. テスト

  61. iOS開発におけるテスト

  62. GHUnit

  63. Kiwi

  64. KIF

  65. In-App Purchase

  66. In-App Purchase

  67. 付録

  68. Tips of Xcode

  69. Auto Layout 入門

  70. Auto Layout ドリル

Edit sidebar

Clone this wiki locally