読者です 読者をやめる 読者になる 読者になる

本当は怖い情報科学

情報系大学院生の趣味&実益ブログ。

[ruby][Rails] 複数のRailsプロジェクトのカバレッジをまとめて取得するRakefile(修正あり)

※2008/3/12 修正:コード中で、「--sort coverage」 オプションを指定する場所が間違ってました。


普通のRailsプロジェクトであれば、

$ rake spec:rcov

で取得することができる。だけど、複数のRailsアプリのカバレッジをまとめて取得することはできない。

もちろんカバレッジを別々に取得することはできる。だけど、共通のライブラリを使っていたり、Railsレシピのレシピ55のようにsvn:externalsでモデルを共有している場合とかは、全体にまたがるトータルなカバレッジが取りたくなる。

さらにCruiseControl.rbを使っている場合、rake testで全部テストが走ってくれると準備の作業が必要なくなるので便利。

というわけで、そんな俺のためのRakefileを書いた。

使い方は、rake spec, rake spec:rcov

# You can use this sample code under BSD Lisence.

#----------------------------------------------

PROJECT_ROOT = File.dirname(__FILE__)

# プロジェクトのルートからの相対パス
RAILS_ROOTS = ["src/rails1", "src/rails2"]
OUTPUT_DIR = "#{PROJECT_ROOT}/tmp"

RSPEC_LIB = "#{PROJECT_ROOT}/#{RAILS_ROOTS[0]}/vendor/plugins/rspec/lib"

#----------------------------------------------

$:.unshift RSPEC_LIB
require 'spec/rake/spectask'
require 'spec/translator'

task :default => 'spec'
task :test    => 'spec:rcov'

__dir__ = File.dirname(__FILE__)

if ENV['RAILS_ROOTS']
  roots = ENV['RAILS_ROOTS'].split(',')
else
  roots = RAILS_ROOTS
end


if roots.size == 1
  desc "Run RSpec test #{roots[0]}"
  Spec::Rake::SpecTask.new(:spec) do |t|
    t.spec_opts = ['--colour']
    t.spec_files = FileList["#{roots[0]}/spec/**/*_spec.rb"]
  end

  namespace :spec do
    desc "Run spec with rcov in #{roots[0]}"
    Spec::Rake::SpecTask.new(:rcov) do |t|
      t.spec_opts = ['--colour']
      t.spec_files = FileList["#{roots[0]}/spec/**/*_spec.rb"]
      t.rcov = true
      t.rcov_opts = ["-t",
                     "--aggregate #{OUTPUT_DIR}/coverage.dat",
                     "--rails"]
    end
  end

else
  desc "Run spec task in each rails prject."
  Spec::Rake::SpecTask.new(:spec) do |t|
    roots.each do |r|
      sh "rake spec -f #{__FILE__} RAILS_ROOTS=#{r}"
    end
  end

  namespace :spec do
    desc ""
    Spec::Rake::SpecTask.new(:rcov) do |t|
      rm_rf "#{OUTPUT_DIR}/coverage.dat"
      roots.each do |r|
        sh "rake spec:rcov -f #{__FILE__} RAILS_ROOTS=#{r}"
      end
      sh "rcov --aggregate #{OUTPUT_DIR}/coverage.dat --sort coverage"
    end
  end
end

とりあえず、プロジェクト全体のトップがあって、src/rails1 ...のような形でソースが配置してあることを想定して書いてあるので、適宜書き換えて使っていただければよいと思う。

ちょっと試行錯誤の末のトリッキーな部分があるので、簡単な解説を書いておく。

  • 基本的な仕組みは、rcovの--aggregateオプションを使ってカバレッジ率のデータをファイルに書き出し、それをまとめて集計・出力している。
  • それだけなら個々のRailsプロジェクトでrcov.optを書き換えれば済む話だが、そうすると結果のHTMLにおいてファイルのパスの表示等が悲しいことになったりrcovのバグにハマったりするので、、リポジトリのトップからのパスが表示できるように、Rakefileをゼロから作っている。
  • さらに、一つのrakeプロセスの中で全部行うと、1つのプロセスに複数のRailsインスタンスが全部ロードされてしまうので、これはちょっと都合が悪い。だから、別々のrakeプロセスを立ち上げるために、自分自身を再帰的にrake実行している。

ちょっとバグがあって、rake spec だとなぜか2回タスクが実行される。テストを2回実行するだけだから、時間が2倍かかる以外の実害は無いんで、とりあえず放置してる。
たぶん、RSpec on Rails 内のファイルでなぜか実行されているという感じなんだけど、面倒なので追ってない(時間が2倍かかるという以外は実害ないので)。ぜひ改良してパッチください。

【広告】