2012年1月21日土曜日

Objective-C がこの四年間でどれぐらい進化したのか一目でわかるテストケース

Twitterに流したら思ったよりも好評でしたので、ブログにも上げておきます。

こちらがiOS 2地点でのNSURLConnectionクラスを使った非同期通信のテストケース。

こちらがiOS 5でのNSURLConnectionクラスを使った非同期通信のテストケース。

Blocksはやっぱり偉大です。一つしかテストケースがないうちはまだマシなのですが、これが10個とかになると楽さが全く違ってきます。ぜひためしにURLだけ変えて同じテストケースを10個作ってみてください。iOS 5のBlocksを使ったコードはほとんどコピペだけで終わりますが、iOS 2でのdelegateを使ったコードは他にも変更しなければならない点が多数出てくるはずです。

また実際にこのコードを走らせてみると、理由はよくわからないのですがiOS 5で追加されたAPIを使ったコード(Blocks)のほうがそうでないコードよりも毎回2倍程度(0.1秒程度)高速に動作しているみたいです。ちょっと謎ですが、新しいAPIにはパフォーマンス面でのメリットもありそうです。GCDのおかげかな?

2012年1月20日金曜日

Jenkins を iOS アプリ開発に導入してみた (SenTestKit編)


最近、iOSアプリの開発でも継続的インテグレーション(CI)を取り入れていくプロジェクトが増加傾向にあるようで、各種ツールやライブラリ、ノウハウが出回ってきているように感じられます。そこで私も早速iOSアプリ開発でのCI導入を試してみることにしました。今回の導入試験では、以下のような環境を想定して行いました。
  • iOSアプリの開発を、Xcode 4.X系のプロジェクトとして行う。
  • VCSにはgitを採用し、githubの公開リポジトリをリポジトリサーバーとして使用する。
  • CIサーバにはMacを採用し、プロジェクトをビルドするためにXcode 4.Xをインストールしておく。


■必要なツールを準備する

CIといったら、まずは何はなくともJenkinsです。
http://jenkins-ci.org/
ここでは導入について詳しくは挙げませんが、私は以下の本を参考にしました。
https://gihyo.jp/dp/ebook/2011/978-4-7741-4952-3

続いてJenkinsのプラグインを導入します。
これでJenkinsの準備はだいたい完了です。
あとはVCSのgitですが、こちらは準備が出来ているという想定で進めます。


■余談:xcodebuildの使い方

JenkinsのXcodeプラグインを使えば、自分で面倒なantのbuild.xmlを書いたり、ビルドスクリプトを用意しなくても、JenkinsのGUI上でビルド設定を行うことが出来ますが、念のため基本を理解しておくべく、xcodebuildというツールを使ってみます。xcodebuildはXcodeプロジェクトをCUIからビルドするためのツールで、Xcodeに付属しています。JenkinsのXcodeプラグインも内部的にはこいつを使用しています。

基本的な使い方の例は以下の通り。
xcodebuild -project MyApp.xcodeproj -configuration Release -target MyApp clean build
xcodebuild -project MyApp.xcodeproj -configuration Debug -target MyAppTests -sdk iphonesimulator5.0 clean build
xcodebuild -project MyApp.xcodeproj -configuration Debug -target MyAppTests -sdk iphonesimulator clean build
上の例ではデフォルト設定のiOS SDKを用いてビルドターゲットMyAppがReleaseビルドされます。次の例ではiPhone Simulator 5.0 SDKを用いてビルドターゲットMyAppTestsがDebugビルドされます。一番下の例では、手元にある最新のiPhone Simulator SDKが使用されるようです。また -target の代わりに -scheme を使うことも出来ます。ビルドに使うことができるSDKの一覧を得るためには、
xcodebuild -showsdks
を使えばよいです。

ビルドアクションにはclean, build以外にもarchiveとかあるのですが試していないので詳細は不明です。しかし残念ながら、少なくともtestアクションがないことは確認しました。そのため、Xcode上で Cmd+U を押して実行できる単体テストが、xcodebuild経由では実行できません。これについては後ほどまた触れます。


■Xcodeプロジェクト側の準備

Jenkinsによる自動化を行うためには、まずはXcodeプロジェクト側を修正して、xcodebuildツールにより単体テストが実行できるような状態にして置かなければなりません。そのため先にXcodeプロジェクト側の設定を修正します。Xcode 4でテストケース付きの新規プロジェクトを作るとテストケース実行用のターゲットが自動的に用意されると思いますが、先述の通りこのターゲットはxcodebuild経由では実行できないため、以下の画像の様にUnit Testing -> Test Hostを削除して対応します。


このように設定を変更することで、UIWindowやUIApplicationを使わなければならないテストが実行できなくなりますが、それ以外のテストはXcodeからもxcodebuildからも実行できるようになります。試しにxcodebuildを使ってテストターゲットを実行し、テストが走っていることを確認してください。


■Jenkins側の準備(ジョブの設計)

続いていよいよJenkinsの設定に移ります。プラグインは導入してあるので、あとはジョブを作るだけです。Xcode連携のプラグインが入っていれば楽勝です。今回はシミュレータビルドで単体テストを行うので、ビルド後の処理でテスト結果の集約を行い、test-reports/*.xmlを指定しましょう。


デバイスビルドでリリース用のビルドを作る場合には、ビルド後の処理でビルド成果物の保存を行い、build/Debug-iphoneos/*.ipa(必要ならdSYMも)などを指定しておけば良いかと思います。デバイスビルドをするにはビルドをするマシンのKeychainにcodesign用の証明書と鍵が格納されている必要があるので注意してください。Technical VersionやMarketing Versionの値を指定すれば、自動的にInfo.plistに指定されているCFBundleVersionやCFBundleVersionShortStringを置換してくれるので非常に便利です。


ジョブを作ったら早速実行してみましょう。おそらくビルドだけならあまり苦労せずに通ると思います。私は10回目のビルドで完全に問題なくビルドが回りだすようになりました。

2012年1月15日日曜日

gdb で void* 型の変数をデバッグする

C言語で実装されたライブラリやアプリケーションでは、汎用的な型として随所で void* が使用されますが、これをgdbからデバッグすると、そのままでは型情報が無いためタダのポインタとして扱われてしまいます。これではデバッグ時の都合がよろしくないです。
(gdb) print 0xfee65c0
$1 = 267281856
(gdb) print (void *)0xfee65c0
$2 = (void *) 0xfee65c0
こんなとき、この void* が指し示している先の型がわかりきっている場合は、その型でキャストしてやって:
(gdb) print (struct imap_session_state_data *)0xfee65c0
$4 = (struct imap_session_state_data *) 0xfee65c0
(gdb) print $4
$5 = (struct imap_session_state_data *) 0xfee65c0
参照先にアクセスすればきちんと中身が見えます:
(gdb) print * $4
$6 = {
  imap_session = 0xfee6560,
  imap_mailbox = 0xfee6230 "INBOX",
  imap_flags_store = 0xfee6490,
  imap_ssl_callback = 0,
  imap_ssl_cb_data = 0x0
}
(gdb) print $6->imap_session
$7 = (mailimap *) 0xfee6560
(gdb) print * $7
$8 = {
  imap_response = 0xfee6090 "FETCH completed",
  imap_stream = 0xfeecc90,
  imap_progr_rate = 0,
  imap_progr_fun = 0,
  imap_stream_buffer = 0xfee6a00,
  imap_response_buffer = 0xfee6a20,
  imap_state = 3,
  imap_tag = 4,
  imap_connection_info = 0xfee64d0,
  imap_selection_info = 0xfee6030,
  imap_response_info = 0xfee60e0,
  imap_sasl = {
    sasl_conn = 0x0,
    sasl_server_fqdn = 0x0,
    sasl_login = 0x0,
    sasl_auth_name = 0x0,
    sasl_password = 0x0,
    sasl_realm = 0x0,
    sasl_secret = 0x0
  },
  imap_idle_timestamp = 0,
  imap_idle_maxdelay = 1740,
  imap_body_progress_fun = 0,
  imap_items_progress_fun = 0,
  imap_progress_context = 0x0
}
これでデバッグがはかどりました。