たごもりすメモ

コードとかその他の話とか。

jrubyでさくさくrspecを実行する(Nailgun or spork)

JRubyでコード書いてるとrspecを起動するたびに永遠にも等しい待ち時間が苦痛で苦痛で苦痛で。

素のrspecで5.6秒、rspecを実行するようにRakefileを書いた rake test で9.4秒、bundle exec rake test に至っては19.7秒という永遠にも等しい時間を必要とする。こうなると生きるのがつらくなってくる。

で、調べてたらsporkなるものがあるらしいんだけど試してみてもなんかいまいちうまくいかない。(うまく動いた、末尾の追記参照)

んでさらに調べてたら、そもそもjvmの起動コストについてはNailgunという仕組みをつかってパスできるっぽい。世の中のRailsアプリだとRailsまわりのライブラリのロードがさらに辛いらしいが、まあそこは今の自分には関係ないのでパス。jvm起動コストだけなんとかなればいいや。

ということで、簡単。以下のようにする。まずjvmをホストするNailgunサーバを起動。

$ jruby --ng-server
NGServer started on all interfaces, port 2113.

続けて別の端末でテストを動かす。

$ jruby --ng -S rspec 
......................

Finished in 0.364 seconds
22 examples, 0 failures

はやい!!!!!!

具体的にはこのくらい速くなった。

$ time rspec
......................

Finished in 0.335 seconds
22 examples, 0 failures

real	0m5.225s
user	0m12.507s
sys	0m0.460s

$ time jruby --ng -S rspec 
......................

Finished in 0.21 seconds
22 examples, 0 failures

real	0m1.741s
user	0m0.032s
sys	0m0.046s

Cool!!!!!!!!!!! はやい!!!!! もうこれでいいよこれでぐれいと!!!!!!

気になる点

rake test すると普通のrspecになってしまうので遅い。が、まあこれは Nailgun Server が立ち上がってるかいちいちチェックするのもめんどくさいので、いいや。分かってれば普段自分では打たないし。あとはまあ、bundlerを経由してないのも気になるとかはある。

(追記) sporkもうまく動いた

こちらのページを大変参考にさせていただきました。ありがとうございます。
Twiwt:Blog / jugyo : spork でサクサク RSpec on Rails3

spork を起動するところまではいっしょ。これはもちろん jruby 経由で起動される。

rubyのコードのloadについてはrailsじゃないので必要なファイルを自分で探してロードするようにする。

# in spec/spec_helper.rb
Spork.each_run do
  # This code will be run each time you run your specs.
  Dir.glob('./lib/myapp/*.rb').each do |file|
    load file
  end
end


rspecコマンド自体がjvmの起動コストを払ってきわめて重いので、上述ページのirb経由で起動するというやつをもちろん実行する。イマドキなのでこれを pry にしよう。まず gemspec に依存関係を追加。

Gem::Specification.new do |spec|
  # 略
  spec.add_development_dependency "bundler", "~> 1.3"
  spec.add_development_dependency "rake"
  spec.add_development_dependency "rspec", "~> 2.0"
  spec.add_development_dependency "spork"
  spec.add_development_dependency "pry"
end

それから pry を起動するためのスクリプト(仮に script/spec_server_pry とする)を作成する。こんな。

#!/usr/bin/env ruby

require 'drb'
require 'pry'

begin
  begin
    DRb.start_service("druby://localhost:0")
  rescue SocketError, Errno::EADDRNOTAVAIL
    DRb.start_service("druby://:0")
  end
  $spec_server = DRbObject.new_with_uri("druby://127.0.0.1:8989")
rescue DRb::DRbConnError
  err.puts "No DRb server is running. Running in local process instead ..."
end

def rspec(file=nil)
  if file
    $spec_server.run(["--color", "--format", "s", file], STDERR, STDOUT)
  else
    $spec_server.run(["--color", 'spec'], STDERR, STDOUT)
  end
end

puts <<DESC

Example:
  > rspec 'spec/xxx_spec.rb'
  or (for all tests)
  > rspec

DESC

Pry.start

IRBをPryに変えたのと、rspec起動時に対象を指定しない場合は全テストを対象に走らせるようにした。その際はすっきりた見た目で。
こんな感じにしておくと常にpryを起動*1しておき、気が向いたらいつでも rspec と叩くだけでテストが実行される。YATTA!!!!!!!!

*1:ちなみにjrubyだとpryの起動も気が狂いそうになるくらい遅い