iPhone アプリで利用している Xcode 上に単体テスト環境を構築する手順のメモです。

GUI の構築が多い iPhone アプリ開発ですが、一部ロジックもあります。ロジックのテストはあったほうがいいし、なきゃ不安。

ということで単体テスト環境の構築方法をいろいろしらべてやってみた手順のまとめです。

開発環境

僕の Xcode の環境です。この環境でテスト環境を作っていきます。

OS のバージョン

Mac OS X 10.6.4 Snow Leopard

Xcode のバージョン

3.2.4

iOS のバージョン

4.1

単体テスト環境の作り方 - 手順

Xcode には 単体テスト(OCUnit)用の SenTestingKit というフレームワークが用意されています。

ここでは、iPhone 用のプロジェクトで OCUnit を使った単体テスト環境を構築する手順を紹介します。

手順1 - テスト対象のプロジェクトを作成する

まずは、iPhone アプリ開発用のプロジェクトを作成します。すでにプロジェクトがある場合にはそれを使います。

スクリーンショット(2010-10-19 16.40.39).png

プロジェクト名は OCUnitDemo としました。

スクリーンショット(2010-10-19 16.43.25).png

ここでビルドしてみて、エラーがでないことを確かめます。

手順2 - テスト用ターゲットを作成する

つぎに、テスト用のターゲットを作成します。「プロジェクト」→「新規ターゲット」をたどり Unit Test Bundle を選びます。

スクリーンショット(2010-10-19 16.45.39).png

ターゲット名はなんでもよいのですが、ここでは UnitTest としました。

手順3 - テストクラスを置くためのフォルダを作成する

つぎに、テストクラスを置くためのフォルダとグループを作成します。この手順は省略しても構いませんがテストクラスとアプリケーションのクラスが同一のフォルダにあると分かりづらくなるので分けておくほうが良いと思います。

Finder でプロジェクトのフォルダを開き TestCases というフォルダを作成します。

スクリーンショット(2010-10-19 17.03.41).png

Xcode に戻って左のツリーの最初にあるプロジェクトアイコン(OCUnitDemo)を右クリックして「追加」→「既存のファイル」をクリックして今作った TestCases を選択します。

このとき「デスティネーショングループのフォルダに項目をコピーする(必要な場合)」のチェックは外しておきます。

また、「ターゲットに追加」で UnitTest にのみチェックが入っている状態にします。

スクリーンショット(2010-10-19 17.07.14).png

手順4 - テストクラスを作成する

つぎに、テストクラスを作成します。今追加した TestCases フォルダを右クリックし「追加」→「新規ファイル」から Objective-C test case class を選択します。

ファイル名はなんでもよいです。ここでは FirstTest.m としました。保存場所が TestCases、ターゲットが UnitTest になっていることを確認して作成します。

スクリーンショット(2010-10-19 17.09.03).png

ここまでできたら、一度ビルドしてみます。「ビルド」→「ビルド」をクリックしてみます。

おそらくエラーが3件(もしくは2件)、警告が1件でるはずです。次はこれを直していきます。

スクリーンショット(2010-10-19 17.14.21).png

エラーのうち、1件はテストのエラー、2件(もしくは1件)は Xcode3.2.4 で iOS4.1 以上を使っている場合にでるエラーになります。警告はガーベージコレクションが無効になっていることを示すもののはずです。

手順5 - テストクラスを修正する

iPhone アプリ開発用のプロジェクトでテストクラスを作成すると、テストケースは次のようになっていると思います。

FirstTest.m

#if USE_APPLICATION_UNIT_TEST     // all code under test is in the iPhone Application
 
- (void) testAppDelegate {
  
  id yourApplicationDelegate = [[UIApplication sharedApplication] delegate];
  STAssertNotNil(yourApplicationDelegate, @"UIApplication failed to find the AppDelegate");
  
}
 
#else
//...

1行目の #if USE_APPLICATION_UNIT_TEST というところがエラーの一つめのテストが失敗した原因になります。iPhone アプリ開発のプロジェクトでプロジェクトを作成するとこのマクロが追加されるようです。

今回はロジックの単体テストをおこなうつもりなので、この部分は削除してしまいます。削除したあとのコードは次のようになりました。

FirstTest.h

#import <SenTestingKit/SenTestingKit.h>
//#import "application_headers" as required
 
@interface FirstTest : SenTestCase {
 
}
 
- (void) testMath;              // simple standalone test
 
@end

FirstTest.m

#import "FirstTest.h"
 
@implementation FirstTest
 
- (void) testMath {
  
  STAssertTrue((1+1)==2, @"Compiler isn't feeling well today :-(" );
  
}
 
@end

UIKit に関する #import 文も削除しました。

手順6 その1 - An internal error occurred when handling command output: -[XCBuildLogCommandInvocationSection setTestsPassedString:]: unrecognized selector sent to instance エラー対策をする

つぎに、なにやら長ったらしいエラーの対策をします。

An internal error occurred when handling command output: -[XCBuildLogCommandInvocationSection setTestsPassedString:]: unrecognized selector sent to instance 0x207543c40

これは Xcode3.2.4 で iOS4.1 の SDK をいれるとでるエラーらしいです。対応として、次の github にあるソースコードをプロジェクトに追加します。

gist:58296

gist:586296 - GitHub

ファイルをダウンロードまたは、中身をコピーして、TestCases の下にソースコードとして追加します。追加後のツリーはこんな感じになります。

スクリーンショット(2010-10-19 17.34.12).png

おそらく、Xcode のバージョンアップでそのうち直ると思いますが、それまではこの対応が必要になりそうです。

手順6 その2 - An internal error occurred when handling command output: -[XCBuildLogCommandInvocationSection setTestsPassedString:]: unrecognized selector sent to instance エラー対策をする

手順6 その1 を試してみても長ったらしいエラーがなくならない場合は、次の手順を試してみてください。その1で治らないということはおそらく iOS のバージョンが 4.1 ではないのだと思います。iPad アプリのテストの場合などは、こちらを試すと良いと思います。

  1. 左側に出ている「グループとファイル」のツリーから、[ターゲット]-[UnitTest] を開きます
  2. [スクリプトを実行] をダブルクリックして、情報ペインを表示します
  3. "${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests" の行の最後に 1> /dev/null を追記します

スクリーンショット(2010-11-06 12.15.47).png

参考

SenTestCase in Xcode 3.2 and XCBuildLogCommandInvocationSection Errors - stackoverflow

手順7 - ビルドしてみる

ここまでできたらビルドしてみます。エラーがなくなればテストが上手く通ったことになります。

スクリーンショット(2010-10-19 17.35.58).png

FirstTest.m の testMath メソッドの中身を変えて、テストが失敗するようにしてビルドを行うと、エラーが発生することを確認します。エラーがきちんと発生していればテスト環境が整ったことになります。

スクリーンショット(2010-10-19 17.36.27).png

あとは、テスト対象のクラスをごにょごにょしたりして単体テストを満喫すれば良いと思います。

手順8 - テスト対象のクラスをコンパイルする

上までの手順が整ったら、テストを書いて行けばよいのですが、実際にテスト対象クラスを #import したところでちょっとつまずいたのでメモです。

UnitTest ターゲットを作ってテストケースクラスはこっちに追加することは上に書きましたが、実際のテスト対象クラスは OCUnitDemo ターゲットに追加されているだけで、UnitTest ターゲットに追加されていません。

UnitTest ターゲット側でテスト対象のクラスを見えるようにするには、テスト対象のクラスを UnitTest ターゲットの[ソースをコンパイル] のフェーズにドラッグ&ドロップして追加する必要があります。

スクリーンショット(2010-11-06 15.58.45).png

(おまけ?)手順9 - UnitTest ターゲットの設定変更

僕の環境では手順7まででエラーなくテストが実行出来るようになったのですが、一部プラットフォーム(アーキテクチャのベース SDK)を iOS4.1 から Mac OS X に変えたり、Objective-C ガーベージコレクションの非対応を必須に変えたりしないといけないみたいな情報をちらちら見かけたので、手順7までで上手くいかない場合には試してみてください。

参考

参考にしたサイトです。