11.2 GHUnitを用いた単体テスト

yuichi takeda edited this page May 28, 2015 · 4 revisions

はじめに

  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

iOSにおけるロジックテストにはGHUnitを用いた単体テストについて紹介します。

iOSにおける単体テストの環境には

  • GHUnit
  • OCUnit

の二種類があります。それぞれを簡単に比較すると

OCUnit GHUnit
Xcodeによるサポート あり なし
実行UI Xcodeと統合 GUI上で実行可能
setup/tearDown なし あり
テストの範囲 ロジックのみ ロジックに加えて非同期やUIViewも可能
導入のしやすさ やや難
CIからの実行 可能 可能

のようになります。コンパクトなテストを行う場合はOCUnitを、高機能なテスト環境を用いたい場合はGHUnitという使い分けができるかと思います。

この章では、公式クライアントアプリの開発でも用いてきたGHUnitについて、導入方法と利用方法を説明します。

追記 Xcode5よりサポートされるテストフレームワークがOCUnitからXCTestへと変化しました。 XCTestはXcodeのサポートもあるため簡単に導入することができます。実際、新しいプロジェクトを作成したときに 自動的にXCTestのターゲットが追加されています。 このページの文末にXCTestを用いた単体テストの書き方について記述しています。

導入方法

導入には次のステップで行います。

  1. 新しいターゲットの追加
  2. CocoaPodsを用いてインストール
  3. Build Settingを修正
  4. main.mの修正

では順番に解説していきます。

すてにあるプロジェクトに対して追加する方法を解説します。

1. 新しいターゲットの追加

  • メニューバーより File → New → Target として新規ターゲットを追加します。
  • アプリケーションテンプレートはEmpty Templateを選択します
  • ターゲット名はここでは UnitTest としました。それ以外の名称にした場合は適宜読み替えてください

ターゲットの追加

ターゲットの追加が終わり次第、不要なファイルの削除とフレームワークの追加を行います。

  • QuartzCore.frameworkを追加します
    • プロジェクトの設定 → ターゲットをUnitTest → Link Binary With Libraries より 追加できます
    • CocoaPodsでインストールしたときにQuartzCore.frameworkが必要となるためです
  • UnitTest以下にあるAppDelegate.h, AppDelegate.m の削除

完了すると、以下のようになっていると思います。

プロジェクトの修正

(QuartzCore.frameworkはFramework以下に移動しても構いません)

2. CocoaPodsを用いてインストール

GHUnitはCocoaPodsを用いてインストールできます。Podfileは以下のように書きます

platform :ios, '6.1'

target :UnitTest, :exclusive => true do
       pod 'GHUnitIOS', '~> 0.5.5'
end

書けたら

pod install

でインストールします。完了したら .xcworkspace からプロジェクトを再起動します

3. Build Settingを修正

シミュレータ上で動かすために、Build Settingに以下の修正を加えます。

Other Linker Flags に -ObjC -all_loadを追加します。

なお、-ObjCは最初から入っていると思います。

BuildSettings修正

4. main.mの修正

ApplicationMainではなく、GHUnitのメインを立ち上げるために、UnitTest側のmain.mファイルを修正します。

int main(int argc, char *argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, @"GHUnitIOSAppDelegate");
    }
}

四つ目の引数を @"GHUnitIOSAppDelegate" に変えています。

ターゲットを UnitTest に変更して起動すると次のようにシミュレータが立ち上がれば成功です。

empty_gh_unit

テストの書き方と実行方法

導入ができたらGHUnitを使ってテストを書き、実行してみましょう

書き方

今回はロジックであるMixiJankenDeciderについてテストを書きます。

まず、テストを書きたいファイルをテストターゲットに追加します。

add_to_target

テストを書きたいファイルを選び、Xcode右側のUtilities、Target Membership の UnitTestにチェックを入れます。

次にテストファイルを作成します。

新規ファイル作成画面へ移動し、GHTestCaseのサブクラスとしてクラスを作成し、TargetはUnitTestのみにチェックを入れて作成します。 ヘッダファイルは不要となるので削除してください。 そして、.mファイルに以下のテンプレートを貼付けます。クラス名などは適宜読み替えてください。

//
//
// Template of Unit Test Case
//
//

#import <GHUnit.h>

@interface MixiJankenDeciderTest : GHTestCase

@end

@implementation MixiJankenDeciderTest

- (void)testHogeFuga
{
    
}


@end

GHUnitではtestHogeFugaと書いたメソッドについてテストが実行されます。今回は試しに、ジャンケンの勝敗を決めるメソッドについてテストを書いてみます。

- (void)testJankenWithPeoples
{
    MixiJankenPeople *alice = [[MixiJankenPeople alloc] init];
    MixiJankenPeople *bob = [[MixiJankenPeople alloc] init];
    MixiJankenPeople *winner;

    alice.hand = JankenHandTypePaper;
    bob.hand = JankenHandTypePaper;

    winner = [MixiJankenDecider jankenWithPeoples:@[alice, bob]];

    GHAssertNil(winner, @"あいこではnilが返ってくるべき");

    alice.hand = JankenHandTypeScissors;
    winner = [MixiJankenDecider jankenWithPeoples:@[alice, bob]];

    GHAssertTrue(winner == alice, @"アリスが勝つべき");

}

Assertなどについては、また後ほど説明しますが、 GHAssertNil では引数がnilであることを期待します。 GHAssertTrueでは引数がYESとなることを期待します。

実行

それでは実行してみましょう。実行は、ターゲットをUnitTestに変更してRunすることで実行できます。

run_gh_unit

テストが失敗した時は、該当するテストの行が赤く表示されます

アサーション、setup/tearDown

ここでは、GHUnitに関するいくつかのアサーションと、テスト開始前/後のメソッドについて紹介します。

アサーション
アサーション 解説
GHAssertEquals(a1, a2, description, ...) a1とa2が同じであることを期待します。判断にはポインタを用います。
GHAssertNotEquals(a1, a2, description, ...) a1とa2が同じでないことを期待します。判断にはポインタを用います。
GHAssertEqualObjects(a1, a2, description, ...) a1とa2が同じであることを期待します。オブジェクトの構造を調べて同じかどうかを判断します。
GHAssertNotEqualObjects(a1, a2, desc, ...) a1とa2が同じでないことを期待します。オブジェクトの構造を調べて同じかどうかを判断します。
GHAssertGreaterThan(a1, a2, description, ...)  a > b を期待します
GHAssertLessThan(a1, a2, description, ...)  a < b を期待します
GHAssertEqualStrings(a1, a2, description, ...) 文字列 a1とa2が同じであることを期待します
GHAssertNotEqualStrings(a1, a2, description, ...) 文字列 a1とa2が同じでないことを期待します
GHAssertEqualCStrings(a1, a2, description, ...) Cの文字列char*について同じかどうかを比較します
GHAssertNil(a1, description, ...) nilであることを期待します
GHAssertNotNil(a1, description, ...) nilでないことを期待します
GHAssertTrue(expr, description, ...) 引数がYESであることを期待します

GHAssertEqualsとGHAssertEqualObjectsについては以下のような違いがあります

NSArray *a = @[obj];
NSArray *b = @[obj];

GHAssertEquals(a, b, nil); // fail
GHAssertEqualObjects(a, b, nil); // OK

詳細についてはこちらをご覧ください

http://gabriel.github.io/gh-unit/docs/appledoc_include/guide_testing.html

setup / tearDown

あるテストクラスの開始前と後に、初期化や後片付けを行う時に呼び出されるメソッドがあります。

メソッド名 解説
- (void)setUp 各テスト実行前に呼ばれる
- (void)tearDown 各テスト実行後に呼ばれる
- (void)setUpClass テストクラス実行前に呼ばれる
- (void)tearDownClass テストクラス実行後に呼ばれる

たとえば、あるデータをクラス内で共有して使いたい、何かしら副作用を生むのでその初期化を行いたいなどの場合に用います

Appendix

コマンドラインからのビルド

Jenkins で定期的にテストを実行したい、と言った時にはXcodeからRunなどは難しいのでコマンドラインからテストを実行します。 次のステップで実行可能です

  • ビルドスクリプトの追加
  • main.mの修正
  • xcodebuildを用いてコマンドラインからテストを実行

ビルドスクリプトの追加

GHUnitのgithubよりビルドスクリプトを取得します。xcworkspaceやxcodeprojのあるディレクトリで

curl https://raw.github.com/gabriel/gh-unit/master/Scripts/RunTests.sh > RunTests.sh

としてスクリプトファイルを保存します。

次にプロジェクトの設定→ Build Phases より Add Build Phaseを選択して、スクリプトを追加します。 add script

スクリプトには

sh RunTests.sh

add script2

と入力します。

xcodebuildを用いてコマンドラインからテストを実行

次のコマンドをプロジェクトルートから実行します

GHUNIT_CLI=1 xcodebuild ONLY_ACTIVE_ARCH=NO -workspace <xcworkspaceファイルへのパス> -scheme UnitTest -configuration Debug -sdk iphonesimulator build

XCTest

XCTestはXcode5よりOCUnitの代わりに用いられるようになった単体テストフレームワークです。 Xcodeで新規プロジェクトを作成した時に自動的にテストターゲットに追加されているため導入の手間はほとんどありません。

リファレンスや使い方については iOS Developer Library — Pre-Release Testing with Xcode をごらんください

以下ではXcodeでプロジェクトを作った時にできるスケルトンを用いてテストの全体を解説します。

テストファイル

#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>

// テストクラスは XCTest のサブクラスにします
@interface MyAppTests : XCTestCase

@end

@implementation MyAppTests

// 各テスト実行前にこの setup が実行されます。
// テストを行う前に共通する準備が必要な場合はここに記述します。
- (void)setUp {
    [super setUp];
}

// 各テスト実行後にこの tearDown が実行されます。
// テストで何か副作用を産むコードがある場合(一時ファイルを作るなど)はここで後片付けをします。
- (void)tearDown {
    [super tearDown];
}

// 各テストのメソッドです。
// `test` で始まるメソッドがテストのメソッドとして自動的にテストランナーに登録されて実行されます。
- (void)testExample {

    // アサーションは XCTAssert... のような記述となります。
    XCTAssert(YES, @"Pass");
}

// パフォーマンステストも行うことができます。
- (void)testPerformanceExample {

    [self measureBlock:^{
        // このブロック内に計測したい処理を入れると
        // 実行時間を計測してくれます。
    }];
}

@end

XCTAssert(YES, @"Pass"); のように書かれているアサーションが条件を満たすと各テストは成功とみなされ、 すべてのアサーションが成功すると全体でテストは成功になります。

テストを実行する

テストの実行はCmd+Uやメニューの Product → Run から実行することができます。 実行するとシミュレータが立ち上がり、各テストが実行されます。

テストは各テストメソッドごと、テストクラスごとに実行することもできます。 Xcode左ペインのTest navigatorや行番号にあるひし形をクリックするとそのテスト範囲だけ実行することができます。

アサーション

単体テストで満たすべき条件を記述していくのがアサーションになります。 以下に主要なものを解説します。

すべてのアサーションについてはiOS SDKのXCTestAssertions.h に記述してあります。

アサーション 解説
XCTAssertNil(obj) objがnilであることを期待します
XCTAssertTrue(条件文) 条件文が真になることを期待します
XCTAssertFalse(条件文) 条件文が偽になることを期待します
XCTAssertEqual(a, b) a == b となることを期待します. オブジェクト同士の比較ではなく、プリミティブの比較を行います
XCTAssertEqualObjects(a, b) aとbが同じオブジェクトであることを期待します. 例えば XCTAssertEqualObjects(@"foo", @"foo") とすると真になります
XCTAssertThrows(実行文) 実行結果が例外を投げることを期待します

基本的な使い方はOCUnit/GHUnitとほぼ同様です。