大分放置してましたけど思い出したかのように続き。いや実際忘れてたのだけども。

とりあえず以前書いたものたち。

spceコマンドのラッパを書く

前回は自分のリソース内の*_spec.rbを読み込んで自分のmainバンドルのクラスをテストする、というRubyCocoaアプリを作ったのだけど、specを走らせるためだけに毎回そんなものを作るのは面倒くさすぎる。実際やってることはSpec::Runner::CommandLineにspecファイルを渡してるだけなので、ローダブルバンドルを引き数に取ってspecを走らせる、というspecコマンドのラッパを用意する。

#!/usr/bin/env ruby

require 'rubygems'
require 'spec'
require 'osx/cocoa'

spec_opts   = ARGV.reject {|opt| opt =~ /\.bundle\z/}
bundle_path = ARGV.find {|opt| opt =~ /\.bundle\z/}

if bundle_path && bundle = OSX::NSBundle.bundleWithPath(File.expand_path(bundle_path))
  bundle.load
  bundle
  Dir.foreach(bundle.resourcePath) {|file| spec_opts.push(File.join(bundle.resourcePath, file)) if file =~ /.+_spec.rb\z/}
end

::Spec::Runner::CommandLine.run(spec_opts, STDERR, STDOUT, true, true)

見ての通り、バンドルをロードして*_spec.rbを抜き出し、それらのパスをコマンドライン引数に付け加えてSpecのランナーに渡すだけ。バンドルは最初の一個以外は全部無視する設定になってるけど、俺が想定してる使用方法で扱うバンドルは一個だけなのでとりあえずこれで十分。必要であれば全部受けつけてもいいけど。

拡張子が.bundleのファイル以外の引数は全てspecのランナーに渡るので、適当な名前付けてパスの通ったとこに配置しておくとspecコマンドの代わりに使える。-fとか-cとか-sとかも問題なく使えたりする。

使用方法

で、こいつを実際にどう使うかというと、例えばXcodeを使っているならこんな感じ。

  1. 新規ターゲット追加でCocoa Loadable Bundleのターゲットを追加
  2. そのターゲットに次のスクリプトを実行するビルドフェーズを追加:ruby <上のスクリプトのパス> [<specオプション>] $BUILD_DIR/$CONFIGURATION/$EXECUTABLE_NAME.bundle
  3. そのターゲットにObjective-Cのクラスとspecファイルを入れる(その際specファイルは必ず末尾に「_spec.rb」を付ける必要がある)

これで、このターゲットをビルドすると自動的にspecを実行してくれるようになるという噂。いや、ちゃんとなりました。なりましたよ。スペックが全て通ると何事もなくビルド完了するけど、スペックが通らないとビルドが失敗しビルド結果画面にspecコマンドの結果が出る。例えばビルドターゲットの依存関係でアプリターゲットがspecターゲットに依存するようにしておくと、specが全て通らない限りアプリがビルドできなくなるのでいい感じ。

注意点いくつか

まず当然のことながらRubyCocoaがインストールされてないと動かない。Leopardだと最初から/System/Library/Frameworks/RubyCocoa.frameworkがあるんだけど、それはもともと入ってるruby(/usr/bin/ruby)からしか使えない。普段から/usr/bin/rubyを使ってる人は別に問題ないけど、それ以外*1を使ってる場合、そのRuby用に再度RubyCocoaを入れるか、/usr/bin/rubyの方でRSpecを入れるかしておく必要がある。

あと、RSpecの最新版だとspecの実行結果をsuccessとfailedの他にpendingにすることができるけど、pendingはsuccessと同じ扱いになってビルド成功してしまう。ので、ご注意を。まぁ、実行結果はビルドログに吐きだされてるのでそれを見ることはできるけども。

その他

とりあえず結果表示をどうにかしたい。Growlに通知投げたりしたら面白いかなぁ。

*1:/usr/local/bin/rubyとか/opt/local/bin/rubyとか