#freeze [[FrontPage]] 2010/09/21からのアクセス回数 &counter; #contents ** iPhone SDKのUnitTestは中途半端 [#gfe7292b] iPhone SDKにもUnitTest用のクラスとプロジェクトテンプレートが提供されています。 *** UnitTest バンドルの新規作成 [#l64ea95c] iPhone用のアプリケーションプロジェクト(ここではCopyTextを使います)に、UnitTestバンドルを追加します。 - 「グループとファイル」からプロジェクト名を選択し、右クリックで「追加」→「新規ターゲット」を選択します - 「Cocoa Touch」から「Unit Test Bundle」を選択し、「次へ」ボタンを選択 - ターゲット名を入力します(ここではUnitTestsとします) *** 単体テストクラスを作成 [#f04b24b1] 次に単体テスト用のクラスを作成します。 - プロジェクト名を右クリックで「追加」→「新規グループ」を選択し、グループ名を入力(ここでは「Test Classes」と)します。 - 「Test Classes」を右クリックし、「追加」→「新規クラス」を選択します - 「Objective-C test case class」を選択し、「次へ」ボタンを選択 - クラス名を入力します(ここではMyTestCase)とし、ターゲットをUnitTestsとします。 今回は、APPLICATION_UNIT_TESTを使わないので、MyTestCase.hのdefine文を変更します。 #pre{{ #define USE_APPLICATION_UNIT_TEST 0 }} *** 単体テストの実行 [#f4c3278b] 単体テストの実行は、 - ターゲットを「UnitTests」を選択 - 「実行」メニューから「ビルド」を選択します 単体テストに失敗すると「CopyTest-ビルド結果」のウィンドウにエラーが表示されるのですが、 正常終了の場合は、Build Succeededとしか出力されません。 「何となく」物足りないのと、エラーが発生した場合デバッグすることができないのが、 私が「中途半端」と感じるところです。 試しにtestMathの1+1を1+2に変更し、ビルドして見てください。 &ref(failed.png); のようなエラーが「スクリプト実行」の結果として表示されます。 残念ながらこのままではtestMathをデバッグできません。 ** かっこ悪いデバッガの使い方 [#u6f2627b] とても残念なのですが、色々調べた結果単体テストのデバッグには、次ような作業がプロジェクト毎に必要になります。 - 実行可能ファイルにotestを追加 - otestの情報として、2個の引数と8個の環境変数の設定が必要 この方法は、Scitt Densmore氏の以下のURLを参照しました。 - http://scottdensmore.typepad.com/blog/2010/07/debugging-unit-tests-for-the-iphoneipad.html *** otestの設定 [#s3de1876] octestの追加手順は以下の通りです。 - 「実行可能ファイル」を選択し、右クリックで「追加」→「新規カスタム実行可能ファイル」を選択 - 「実行可能ファイル名」をotest、実行可能ファイルのパスとして、以下の値をセット #pre{{ /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.0.sdk/Developer/usr/bin/otest }} otestの設定 - otestを選択し、右クリックで「情報を見る」を選択し、引数タブを選択します &ref(arg_setting.png); のように引数と環境変数を設定します。 引数は、2個目の引数は、単体テストのターゲット名+.octestとします。 |-SenTest All| | UnitTests.octest| 環境変数は、 |Name|Value|h |DYLD_ROOT_PATH|$(SDKROOT)| |DYLD_FRAMEWORK_PATH|${BUILD_PRODUCTS_DIR}:${SDK_ROOT}:${DYLD_FRAMEWORK_PATH}| |IPHONE_SIMULATOR_ROOT|$(SDKROOT)| |CFFIXED_USER_HOME|$(HOME)/Library/Application Support/iPhone Simulator/User/| |OBJC_DISABLE_GC|YES| |DYLD_LIBRARY_PATH|${BUILD_PRODUCTS_DIR}:${SDK_ROOT}:${DYLD_LIBRARY_PATH}| |DYLD_NEW_LOCAL_SHARED_REGIONS|YES| |DYLD_NO_FIX_PREBINDING|YES| *** デバッガの起動 [#s25fc151] デバッガを起動する前に、もう一度、ターゲットUnitTestsを実行しておきます。 次に、 - testMathの最初の行にブレークポイントを設定 - otestを選択し、右クリックで「ブレークポイントを使ってotestを開始」を選択します。 - 実行からデバッガを表示をクリックすると以下のデバッガの画面になります。 &ref(debugger.png); これで、無事デバッガが使えるようになりました。 ** hamcrestの準備 [#bc443250] hamcrestは、単体テストで判定をする時の便利な機能を集めたパッケージです。 hamcrestの大きな特徴は、メソッド名称が英語に近い表現になっていることです。 #pre{{ assertThat(aaaa, is(not(equalTo(bbbb)))); }} は、カッコとカンマを全部取り除くと assert that aaaa is not equal to bbbb.となり、 英語として意味のわかるように作られています。(( hamcrestについては、java版ですが都元ダイスケさんのページ http://d.hatena.ne.jp/daisuke-m/20090710/1247181113 を参考にさせて頂きました。)) *** インストール手順 [#t96d3f85] 残念ながら、hamcrestにはiPhone SDK用のパッケージはありません。 ここでは、SVNを使ったソースからの作成方法を紹介します。 - ホームディレクトリにlocal/OCHamcrestを作成 - SVNを使ってソースをダウンロードします #pre{{ svn checkout http://hamcrest.googlecode.com/svn/trunk/ hamcrest-read-only }} - ~/local/OCHamcrest/hamcrest-read-only/hamcrest-objectivec/SourceのOCHamcrest.xcodeprojをダブルクリック - ターゲットのOCHamcres-iOSをテスト環境と同じDebugとSimulatorにセットし、ビルドします *** 使い方 [#h46a0b92] hamcrestの使い方((詳しくは http://code.google.com/p/hamcrest/wiki/TutorialObjectiveC を参照))は、簡単です。 ソースの最初に以下の2行を追加するだけです。 #pre{{ #define HC_SHORTHAND #import <OCHamcrest/OCHamcrest.h> }} 最初のHC_SHORTHANDは、メソッドの先頭にHC_を付けないで使用するためのおまじないです。 ライブラリで提供する機能は以下の通りです。 - 基本 -- aynthing : 常にマッチする -- describedAs : カスタムfailureを記述するためのデコレータ(可読性のためのメソッド) -- is : 読みやすくするためのデコレータ - 論理 -- allOf : すべてがマッチしたときに、マッチ(真となる) -- anyOf : いづれかたがマッチしたときに、マッチ -- isNot : マッチしなかったときにマッチ - オブジェクト -- equalTo : オブジェクトの一致をisEqualメソッドを使ってチェック -- hasDescription : テストに関する記述を追加 -- instanceOf, isCompatibleType : 型の一致をチェック -- notNilValue, nilValue : nilのチェック -- sameInstance : 同一インスタンスのチェック - 集合 -- hasEntry, haskey, hasValue : NSDictionaryのcontains entry, key, value のチェック -- hasItem, hasItems : 集合がアイテムを含むか否かのチェック - 数値 -- closeTo : 浮動小数点の値が近いかどうかのチェック -- greaterThan, greaterThanOrEqualTo, lessThan, LessThanOrEqualTo : 大小のチェック - テキスト -- equalToIgnoringCase : 大文字小文字の区別無く文字の一致をチェック -- equalToIgnoringWhiteSpace : 空白の違いを無視して文字の一致をチェック -- containsString, endsWith, startsWith : 文字の一致をチェック *** hamcrestの使用例 [#u4e27b02] hamcrestを使うには、 - 外部フレームワークをセットし、インクルードファイルパスをセットする - 使用するライブラリがiPhone OS用のスタティックライブラリを使用するためにhamcrestプロジェクトをドラッグする - 外部リンクフラッグに-ObjC, -all_load, -lstdc++ を追加する 必要があります。 外部フレームワークの追加は、プロジェクトを選択し、右クリックで「追加」→「既存のフレームワーク」を選択し、「その他の追加」からOCHamcrestのSource/build以下のDebug/OCHamcrest.frameworkを選択します。 次に、OCHamcrestのXCodeプロジェクトをテストしたいプロジェクトにドラッグしますと、 &ref(drag_proj.png); のダイアログが表示されるので、ターゲットを単体テスト(UnitTests)であることを確認します。 次に、libochamcrest.aの左端のチェックボックスをセットします。 &ref(hamcrest_ex.png); 最後にターゲットのUnitTestsを選択し、「情報を見る」を選択し、ビルドタグから外部リンクを選択し、「他のリンカフラグ」をダブルクリックし、他のフラグの後に-ObjC, -all_load, -lstdc++ を追加する。 テストプログラムは、以下を参考に入力してください。 #pre{{ #import "GTMSenTestCase.h" #define HC_SHORTHAND #import <OCHamcrest/OCHamcrest.h> @interface ExampleWithAssertThat : SenTestCase @end @implementation ExampleWithAssertThat - (void) testUsingAssertThat { assertThat(@"xx", is(@"xx")); assertThat(@"yy", isNot(@"xx")); assertThat(@"i like cheese", containsString(@"cheese")); } @end }} ** OCMockの準備 [#q8496f26] [[iPhone/最初の一歩]]でもお話したように、iPhoneの処理はViewControllerを中心に作られているため、 iPhoneシミュレータでWindowを表示しなければ、動作をチェックすることができません。 単体テストでは、OCMockのモック機能を使ってWindowを実際に表示することなく、テストが可能に なります。 OCMockには、 - expectとverifyを使った検証機能 - stubを使ったモック機能 があります。 *** インストール手順 [#hb4d2ca8] OCMockは、以下のURLから「Download」タグをクリックして、 http://www.mulle-kybernetik.com/software/OCMock/ iPhone用の場合には、Mac OS X 10.6以降が必要ですから、ocmock-1.70.dmgをダウンロッドしてください。 ダウンロードしたOCMockは、システムではなくユーザのローカルディレクトリに入れることにします。ここでは、 - $HOME/local/ に入れます。 OCMock 1.70/Source/ocmock-1.70にあるOCMock.xcodeprojをダブルクリックします。 ターゲットをOCMockPhoneSimに切り替えて、Debug版のlibOCMock.aを作成します。 &ref(ocmock_proj.png); *** OCMockの使用例 [#ff24df68] OCMockを使うには、ヘッダファイルとライブラリをセットする必要があります。 - ヘッダは、$HOME/local/OCMock 1.70/Source/ocmock-1.70/Release/OCMock.frameworkを使用 - ライブラリは、OCHamcrestと同様にOCMock.xcodeprojをドラッグして使用 ここで注意することは、OCMock.frameworkのライブラリを使用しないように、プロジェクトのファイル名一覧のOCMock.frameworkの右端のチェックを外すことです。 &ref(ocmock_setting.png); 準備ができたら、OCMockのテスト用のメソッドを追加します。 #pre{{ // 以下のimport文を追加 #import <OCHamcrest/OCHamcrest.h> #import <OCMock/OCMock.h> // テスト用メソッドは以下の通り - (void)testReturnsStubbedReturnValue { id mock = [OCMockObject mockForClass:[NSString class]]; [[[mock stub] andReturn:@"megamock"] lowercaseString]; id returnValue = [mock lowercaseString]; assertThat(returnValue, is(@"megamock")); } }} これで、テスト駆動型開発の準備が整いました。 ** コメント [#u2ca1c96] この記事は、 #vote(おもしろかった[3],そうでもない[0],わかりずらい[3]) #vote(おもしろかった[4],そうでもない[0],わかりずらい[3]) 皆様のご意見、ご希望をお待ちしております。 #comment_kcaptcha