iOSテストフレームワーク(XCTest)を試してみた
はじめに
iOSテストフレームワークでXCTestを試してみました。環境はMac OS X 10.9.2, Xcode 5.1で行いました。XCodeに統合されており、XCodeから実行できます
XCTest
Xcodeユニットテスト ガイドを参考にしました
ロジックテストとアプリケーションテスト
ロジックテストは、各コンポーネントが正常に動作するかを確認するユニットテストです。シュミレータ上でしかできません。
アプリケーションテストは、Xcodeユニットテスト ガイドによると
このテストは、アプリケーションのコンテキストでコードのユニット をチェックします。アプリケーションテストを使用して、ユーザインターフェイスコントロール の接続(接続口とアクション)が適切であることと、コントロールとコントローラオブジェクト が、アプリケーションに適用したオブジェクトモデルに基づいて正しく動作することを保証でき ます。このテストを使用して、アプリケーションが動作しているデバイスの位置を取得するな ど、ハードウェアのテストも実行できます。
今回は、アプリケーションテストで試しました
ユニットテストターゲットをプロジェクトに追加
- アプリケーションテストするプロジェクトを開きます
- File > New > Targetをクリックします
- iOSのother > Cocoa touch Unit testing Bundleを選択し、nextをクリックします
- ProductNameに「XXXTests」(末尾をTestsにする), Company Identfierを適当に(例:jp.co.xyz)入れ, TypeをXCTestを選択し、Finishをクリックします
- 左側のフォルダ表示にProductNameに設定した「XXXTests」フォルダが作成され、XXXTests.mがデフォルトのユニットテストのファイルになります
XXXTests.m
#import <XCTest/XCTest.h> @interface XXXTests : XCTestCase @end @implementation XXXTests - (void)setUp { [super setUp]; // Put setup code here. This method is called before the invocation of each test method in the class. } - (void)tearDown { // Put teardown code here. This method is called after the invocation of each test method in the class. [super tearDown]; } - (void)testExample { XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); } @end
setUpメソッドが各テストのはじめに呼ばれます。tearDownメソッドが各テストの終わりに呼ばれます。testで始まるメソッドがテスト対象のメソッドです。デフォルトではtestExampleメソッドが定義されており、必ず失敗するXCTFail()関数が呼ばれています
アプリケーションテストの設定
- 左側のフォルダ表示で、プロジェクトを選択します。Targetから先ほど追加した「XXXTests」を選択します。次に「Build Setting」を選択します
- 「Build Setting」の「All」をクリックします
- 「Bundle Loader」を以下のように設定します
iOS: $(BUILT_PRODUCTS_DIR)/<app_name>.app/<app_name>
この値はDebugとReleaseに設定されます 4. 「Test Host」を以下のように設定します
$(BUNDLE_LOADER)
- 停止ボタンの右のプロジェクト名をクリックし、scheme editをクリックします
- 左のTestを選択します。テスト用のターゲットを選択します。画面下の[+]をクリックし、先ほど追加した「XXXTests」のテストターゲットを追加します
テストの確認
- メニューから「Product」> 「Test」を実行します
- メニューから「View」> 「Navigators」> 「Show Log Navigator」をクリックします。左から実行したテストを選択すると、右側に結果が表示されます。現在、testExampleメソッドが必ず失敗します
テストの記述
1.テスト対象となるクラスを追加します。アプリ本体のターゲットに、Utilクラスを追加します
Util.h
#import <Foundation/Foundation.h> @interface Util : NSObject + (BOOL)isMutipleOf3:(NSInteger)num; @end
Util.m
#import "Util.h" @implementation Util + (BOOL)isMutipleOf3:(NSInteger)num { if (num % 3 == 0) { return YES; } else { return NO; } } @end
Util.isMutipleOf3メソッドは、整数が3の倍数であるとき真、そうではないとき偽を返します
2.テストを記述します。先頭に#import "Util.h"を追加し、testExampleを以下のように書き換えます
XXXTests.m
#import <XCTest/XCTest.h> #import "Util.h" @interface XXXTests : XCTestCase @end @implementation XXXTests - (void)setUp { [super setUp]; // Put setup code here. This method is called before the invocation of each test method in the class. } - (void)tearDown { // Put teardown code here. This method is called after the invocation of each test method in the class. [super tearDown]; } - (void)testExample { XCTAssertTrue([Util isMutipleOf3:9], "3の倍数ではありません"); } @end
3.メニューから「Product」 > 「Test」でテストします。テスト失敗にならなければ成功です
4.テストマクロ
いくつかテストマクロを紹介します。詳しくはXcodeユニットテスト ガイドの「ユニットテスト結果用マクロのリファレン」を参照してください
4.1 nilテスト
- XCTAssertNil
- XCTAssertNotNil
4.2 trueテスト
- XCTAssertTrue
4.3 falseテスト
- XCTAssertFalse
4.4 等価テスト
- XCTAssertEqualObjects
- XCTAssertNotEqualObjects
- XCTAssertEqual
- XCTAssertNotEqual
4.5 例外テスト
- XCTAssertThrows
非同期
確認していませんが、XCTestで非同期ブロックのテストを書くで非同期処理のテストも書けるようです
おわりに
Travis CIでXCTestを実行できるようです
Travis CI で XCode プロジェクトを自動テストする方法
XCTestはXCodeに統合されているので、導入が簡単で、XCodeから実行できるところがいいところだと思います。
GHUnitについてはGHUnitを用いた単体テストが参考になると思います