最近やってるRailsプロジェクトのテスト方法
Railsエンジニアになってから1年半くらいが経ち、社内のRailsのプロジェクトを全部で5つくらい触って、今やってるAbilie*1でようやく人並みにテストを書いてる気がしてきたので、現時点でやってるテストの方法をまとめておく。
テストのルール的なの
rspecでは必ずモデルのテストは書くようにしてる。ヘルパーも大体書いてるけど、コントローラやルーティングのテストはあまり書いてない。
というのも、コントローラーのコードを極力短くしてモデルを太らせているのでコントローラのテストはあんまり意味が無い気がしていて、その代わりにCapybaraでテストを書いておけば十分なんじゃないかなと思ってきたから。Capybaraは書いてるので、そういう意味では書いてるとも言える。
社内の管理者だけが使える管理画面も作ってるけど、そっちはテストあんまり書いてない。ここは動かなくなっても一般ユーザーには影響が無いので、動かなくなったのを気づいてから直せばいいかなーという感じ。
テストの実行
GuardとSporkを組み合わせて使い、結果をGrowlで出力してる
guard/guard · GitHub
guard/guard-spork · GitHub
Guardはファイルに変更があったらなにかしてくれるもの。
Sporkは事前にRailsのコードを事前に読み込んでおいてrspecの起動時間を短縮してくれる。
GrowlはMacだったらみんな使ってるアレですね。最近有料になりましたが、会社に #金くれ って言ったら買ってくれました。
Guardに関して紹介してるブログとか読むと、guardを起動時に全体のテストを流し、モデルなどのテストを通ったらまた全体のテストを流すように設定してあるけど、全体テストが流れると時間がかかるので設定を無効化してる
Guardfileを以下のように書けばいい。
guard 'rspec', :version => 2, :cli => "--drb", :all_after_pass => false, :all_on_start => false do
全体テストが実行したくなったら、guardが動いてる所でEnterを押せば動く。
fixture_replacement
factory_girlで特に困ってもないので相変わらずfactory_girlを使ってる。
thoughtbot/factory_girl · GitHub
modelを作成したときにfactory_girlのファイルも生成するようにconfig/application.rbに以下のように書いてる
config.generators do |g| g.test_framework :rspec, :fixture => true, :fixture_replacement => :factory_girl g.fixture_replacement :factory_girl, :dir => 'spec/factories' end
あとSporkを実行してると、factory_girlのファイルに変更があっても更新してくれないので、spec_helper.rbに以下のように書いてる。
Spork.each_run do FactoryGirl.reload Deadend::Application.reload_routes! end
カバレッジ
半年くらい前はcover_meを使ってたけど、最近はsimple-covを使ってる。
colszowka/simplecov · GitHub
出力されたカバレッジ結果のHTMLが綺麗なのと、rcov形式で出力できてJenkinsでも使えるのが良い。
Sporkで実行される場合は何か変になるので、Sporkを使ってる時は実行しないようにしてる。
あとローカルでrake specしたときはデフォルトの出力を、CIで実行した場合にはrcov形式で出力できるようにspec_helper.rbに以下のように書いてる。
unless Spork.using_spork? require 'simplecov' if ENV["JENKINS"] == 'on' require 'simplecov-rcov' SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter end SimpleCov.start 'rails' do add_filter "/spec/" end end
Redisのテスト
resqueを使ってるというのもあり、プロジェクトでRedisを使っているのでこのテストも書きたい。
RSpec and Resque · defunkt/resque Wiki · GitHub
resqueのWikiを参考に、テスト実行時にredisのconfファイル作ってredisを起動し、テストが完了後にプロセスを落としてダンプファイルも消してる。
Jenkinsで動かす場合にうまく動かなかったのでその部分だけTempFileを使うようにしてる。
REDIS_PID = "#{Rails.root}/tmp/pids/redis-test.pid" REDIS_CACHE_PATH = "#{Rails.root}/tmp/cache/" config.before(:suite) do redis_options = { "daemonize" => 'yes', "pidfile" => REDIS_PID, "port" => 9736, "timeout" => 300, "save 900" => 1, "save 300" => 1, "save 60" => 10000, "dbfilename" => "dump.rdb", "dir" => REDIS_CACHE_PATH, "loglevel" => "debug", "logfile" => "stdout", "databases" => 16 }.map { |k, v| "#{k} #{v}" } if ENV["JENKINS"] == 'on' require 'tempfile' temp = Tempfile::new("redis_temp.conf", REDIS_CACHE_PATH) redis_options.each do |value| temp.puts(value) end temp.close Subexec.run("redis-server #{temp.path}") else redis_options = redis_options.join("\n") `echo '#{redis_options}' | redis-server -` end end config.after(:suite) do %x{ cat #{REDIS_PID} | xargs kill -QUIT rm -f #{REDIS_CACHE_PATH}dump.rdb } end
WebAPIのテスト
ここで言うWebAPIのテストとは、外部のWebAPI(twitterとか)を使う場合のテストのこと。
これはwebmockというgemを使えば良い。
bblimke/webmock · GitHub
ただ、webmockを使ってしまうとCapybaraがlocalhostにアクセスしたときにもすべてmockが返ってきてしまうので、localhostを除外するようにする
spec_helper.rbに以下のように書く
WebMock.disable_net_connect!(:allow_localhost => true)
Javascriptのテスト
Capybara-Webkitを使ってる。
thoughtbot/capybara-webkit · GitHub
今のところそんな特殊なJavascriptを書いてるわけではなく、ほとんどjQueryに頼ったものなのでこれで十分間に合ってる。
ただ、隣の人からJSのカバレッジも見たいという要望も出てるので、poltergeistを使ったほうが良いのかもしれない。
jonleighton/poltergeist · GitHub
あとCapybara-Webkitはrspecとは別のスレッドで動くらしく、factory_girlで作ったデータをCapybara側で読めない。
CapybaraのgithubでActiveRecordのshared_connectionを変える方法が紹介されてるけど、なんかうまく行かなかったのでDatabaseCleanerを使う方法をとってる。
JSのテスト以外は従来のtransactionを使ってデータを消し、JSの時はtruncateを全テーブルにする方法にしてる。
Capybara.javascript_driver = :webkit config.before :each do if Capybara.current_driver == :rack_test DatabaseCleaner.strategy = :transaction DatabaseCleaner.start else DatabaseCleaner.strategy = :truncation end end config.after :each do DatabaseCleaner.clean end
画像のテスト
画像関連はCarrierWaveを使ってるんだけど、factory_girlで作られてしまうのでテストが終わった後に消してる。あとテストの場合は通常とは違うディレクトリに書き出してる。
継続的インテグレーション
Jenkinsを使ってgitにpushされたらテストを実行してる。それとは別に朝8時ごろにも流してる。
simplecov-rcovとci_reporterを使って、カバレッジとテスト数の推移をグラフで出してる。
またテストに通ったら、自動的に社内の検証環境にデプロイされるように、ビルドパイプラインの設定をしてる。
Abilieに関してはまだリリースされてないので、Jenkinsも試験的に社内のサーバに入れてるだけで、本番へのデプロイとかはやるかどうかわからない。
まだJenkinsは使い始めたばかりであまりいじってないけど、rails_best_practiceとかも組み込めるといいかなと思ったりしてる。
おわりに
入社した直後に引き継いだプロジェクトはテストも無く、チームにテストを書く文化もなく、業務でちゃんとテストを書いたことがなかったぼくですが、入社半年後くらいからテストを書き始め、少しずつテストを書く文化ができてきてよかったかなと。
とは言っても僕の担当してるプロジェクト以外は相変わらずテストが書かれてないものが多いので、そのあたりが今年の課題な気はしてる。
*1:詳しくはhttp://d.hatena.ne.jp/tohae/20111222/1324533072