RailtieとGeneratorのSpecテスト

GitHubで公開している[rack-dev-mark](https://github.com/dtaniwaki/rack-dev-mark)で、少し前にRailtieとGeneratorを追加したのですが、specテストが書けておらず微妙だな〜と思ってました。

# Specテストが書けていなかった理由

## Railsは複数回initializeできない

Rails 3.2ではRails::Applicationを継承したサブクラスを２つ以上作るとエラーになる。

```rb
app = Class.new(::Rails::Application)
app = Class.new(::Rails::Application)
> RuntimeError: You cannot have more than one Rails::Application
```

複数回`initialize!`を呼ぶのもNG。

```rb
app = Class.new(::Rails::Application)
app.initialize!
app.initialize!
> RuntimeError: Application has been already initialized.
```

Rails 4ではこの問題は起きないが、まだ3.2をテスト対象から外すには早すぎる。

# 攻略方法

そして、長ーく考えた結果、やっと攻略できました。

## rspec内でfork

forkしたプロセスでテストを実行すればその中でRailsをinitializeできます。
この時考慮しなければいけないのは以下２点

- Coverageのマージ
- テスト結果のsync

## Coverageのマージ

`Coveralls`はそもそも各rubyとgemの組み合わせのCoverageをマージしているので、forkしたとしてもマージしてくれる。
ローカルでsimplecovを使った場合はプロセス同士がcoverageの結果を上書きし合うので結果が正しくなくなる。

specテストに以下を書くことで解決。

```rb:spec/spec_helper.rb
resultset_path = SimpleCov::ResultMerger.resultset_path
FileUtils.rm resultset_path if File.exists? resultset_path

SimpleCov.use_merging true
SimpleCov.at_exit do
  SimpleCov.command_name "fork-#{$$}"
  SimpleCov.result.format!
end
```

まず、`SimpleCov.use_merging true`ですが、これは以前実行したcoverageの結果に対してマージを行う設定。
`cucumber`のテストと`rspec`のテストをマージする際などに使われる。

しかし、rspecのプロセスをforkした場合、そのままだとマージするためのキーも同じなので上書きされてしまいます。

そこで、`SimpleCov`の`at_exit`フックでマージする前にキーを変更してやるのがコツ。

```rb
SimpleCov.at_exit do
  SimpleCov.command_name "fork-#{$$}"
  SimpleCov.result.format!
end
```

そして、そのままだと過去のテスト分もマージされてしまうので、開始時に初期化。

```rb
resultset_path = SimpleCov::ResultMerger.resultset_path
FileUtils.rm resultset_path if File.exists? resultset_path
```

これで無事、forkしたrspecのCoverageをマージすることができました！

![Screen Shot 2014-06-24 at 10.32.45 AM.png](https://qiita-image-store.s3.amazonaws.com/0/22653/9835effc-0edf-6999-37ce-57dc7b9294d6.png)

## テスト結果のsync

rspecのコードをかなり読み込みんで、

```rb:spec/support/fork_helper.rb
shared_context 'forked spec' do
  around do |example|
    read, write = IO.pipe
    pid = fork do
      $stdout.sync = true
      $stderr.sync = true
      res = example.run
      Marshal.dump(res, write)
      write.close
    end 
    Process.waitpid2 pid 
    res = Marshal.load(read)
    example.example.send :set_exception, res if res && !res.empty?
    read.close
  end 
end
```

```rb:spec/rack/dev-mark/railtie_spec.rb
require 'spec_helper'

describe Rack::DevMark::Railtie do
  include_context 'forked spec'

  before do
    @app = Class.new(::Rails::Application)
    @app.config.active_support.deprecation = :stderr
    @app.config.eager_load = false
  end 
  context "rack_dev_mark enable" do
    before do
      @app.config.rack_dev_mark.enable = true
      @app.initialize!
    end 
    it 'inserts the middleware' do
      expect(@app.middleware.middlewares).to include(Rack::DevMark::Middleware)
    end 
  end
end
```

こんな感じのコードで突破しました。

流れとしてはforkしたプロセスとパイプでやり取りし、テスト結果をパイプで送って親プロセスのテスト結果にセットします。

そしてforkしたプロセスの結果をfork元の結果として扱うことに成功しました。

![Screen Shot 2014-06-24 at 10.30.08 AM.png](https://qiita-image-store.s3.amazonaws.com/0/22653/39c2ea7f-959b-cb52-571d-f20c47b1b4b4.png)

そしてオールグリーン！

![coveralls.jpeg](https://qiita-image-store.s3.amazonaws.com/0/22653/417e5ae6-6b2e-2eee-d889-dea1d4399853.jpeg)

Rails 4以上をサポートする場合は複数appを作成できるのでrailtieやgeneratorのテストも簡単にできそう。