上京エンジニアの葛藤

都会に染まる日々

simplecov を流し読み

simplecov の流し読みをしたのでまとめておきます。

simplecov

simplecov とはテストカバレッジを計測する gem で、計測結果をあらゆるフォーマットで出力してくれます。
Rails と親和性も高く簡単に導入することができます。

github.com

コードリーディング

simplecov を require すると lib/simplecov.rb の中で、lib/simplecov/defaults.rb が読み込まれ以下のコードでデフォルトの設定がされます。

# Default configuration
SimpleCov.configure do
  formatter SimpleCov::Formatter::HTMLFormatter
  load_profile "bundler_filter"
  load_profile "hidden_filter"
  # Exclude files outside of SimpleCov.root
  load_profile "root_filter"
end

# Gotta stash this a-s-a-p, see the CommandGuesser class and i.e. #110 for further info
SimpleCov::CommandGuesser.original_run_command = "#{$PROGRAM_NAME} #{ARGV.join(' ')}"

at_exit do
  next if SimpleCov.external_at_exit?

  SimpleCov.at_exit_behavior
end

formatter は HTML が選択され、除外ディレクトリなどの filter の設定も最低限されます。
filter の設定は lib/simplecov/profiles/ 以下に定義されています。

at_exit では計測終了時の処理が定義されています。

# Autoload config from ~/.simplecov if present
require_relative "load_global_config"

# Autoload config from .simplecov if present
# Recurse upwards until we find .simplecov or reach the root directory

config_path = Pathname.new(SimpleCov.root)
loop do
  filename = config_path.join(".simplecov")
  if filename.exist?
    begin
      load filename
    rescue LoadError, StandardError
      warn "Warning: Error occurred while trying to load #{filename}. " \
        "Error message: #{$!.message}"
    end
    break
  end
  config_path, = config_path.split
  break if config_path.root?
end

.simplecov ファイルを再帰的に探索し、読み込みます。

実際に simplecov を使う場合 lib/simplecov.rb の SimpleCov.start が初めに呼ばれます。

rails で使う場合は profile がすでに定義されているので SimpleCov.start "rails" と指定します。

def start(profile = nil, &block)
    require "coverage"
    initial_setup(profile, &block)
    require_relative "./simplecov/process" if SimpleCov.enabled_for_subprocesses?

    @result = nil
    self.pid = Process.pid

    start_coverage_measurement
end

initial_setup メソッドでは profile の 読み込み、block の処理が行われ self.running = true で計測状態が変更されます。

start_coverage_measurement メソッドでは Coverage.start が呼ばれ計測が開始します。

SimpleCov.result は以下です。
該当のプロセスのカバレッジ結果から rootパス以下以外の結果を除外、 load されていないファイルの結果を初期化し merge します。
同時に self.running = false で計測状態を変更しています。

def result
  return @result if result?

  # Collect our coverage result

  process_coverage_result if running

  # If we're using merging of results, store the current result
  # first (if there is one), then merge the results and return those
  if use_merging
  wait_for_other_processes
  SimpleCov::ResultMerger.store_result(@result) if result?
  @result = SimpleCov::ResultMerger.merged_result
  end

  @result
ensure
  self.running = false
end

lib/simplecov/result.rb の format! を呼ぶことで coverage の結果を出力します。

# Applies the configured SimpleCov.formatter on this result
def format!
  SimpleCov.formatter.new.format(self)
endt

先述の通り formatter の指定がなければ SimpleCov::Formatter::HTMLFormatter がデフォルトで設定されているので、SimpleCov::Formatter::HTMLFormatter.new.format(self) が呼び出されて、html ファイルが出力されます。

以上です。