Rails の複数バージョンで gem をテストするのに appraisal を導入した話

最近、Rails Upgrade のプロジェクトをやったのですが、そのとき困ったことがありました。

複数のリポジトリに分けてソースコードを使っており、それぞれのリポジトリが共通して利用している社内gemがあり、複数の Rails バージョンで動作を保証する必要が出てきました。
​
いままでは複数のリポジトリが同じRailsバージョンだったので、大きな問題には、ならなかったのですが
社内gem が複数の Rails で動くことを保証するため、
複数の Rails バージョンでテスト (rspec) を実行するようにしました。
​
「なんで "複数の Rails で動くことを保証" する必要があるのですか？」と思われるかもしれませんが、
このようなリポジトリが、Railsのバージョンが<b>新しい順</b>にあるとします。

- リポジトリA (ver. x)
- リポジトリB (ver. y)
- リポジトリC (ver. z)
​
(文字だけだとイメージしづらいので、図を置いておきます)
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30536/813d1128-4686-748e-d67b-b10e3b605b7a.png" width="450px">


例えばですが、x になってから新しいメソッド `def only_x` が追加された時を考えましょう。
この新しいメソッドを使っていたとして、`ver. y` `ver. z` では動かないとなります。
​
そのために `my-gem.gemspec`  で rails のバージョンを指定すればいいのですが、先行して各リポジトリが依存している 社内gemの `my-gem` をリリースすることが出来ないということになってしまいます。

- 各リポジトリが社内gemの `my-gem` に依存している
- 日々、社内gemの `my-gem` は開発が行われている (差分がどんどん出る)

そんな時に、この gem を使いました。

- [thoughtbot/appraisal: A Ruby library for testing your library against different versions of dependencies.](https://github.com/thoughtbot/appraisal)
​
今回は、社内gem (my-gem という名前とします) で、下記のRailsバージョンでテスト (rspec) を実行できるようにします。

- Rails 5.1.0
- Rails 5.2.3
- Rails 6.0.0

### 確認環境

```
$ ruby --version
ruby 2.5.5p157 (2019-03-15 revision 67260) [x86_64-darwin17]
```

### 調査
Github にある通りにセットアップしていきます。

#### Appraisals ファイルの準備

Appraisals

```ruby
appraise "rails-5.1.0" do
  gem "rails", "5.1.0"
end
​
appraise "rails-5.2.3" do
  gem "rails", "5.2.3"
end
​
appraise "rails-6.0.0" do
  gem "rails", "6.0.0"
end
```

#### インストール

my-gem.gemspec

```ruby
group :development do
  # 省略...
  gem 'appraisal'
end
```

```
$ bundle install
```

```
$ bundle exec appraisal install
```

このファイルが出来上がります。
各Railsバージョンごとの依存を管理しています。

```
gemfiles/rails_5.1.0.gemfile
gemfiles/rails_5.2.3.gemfile
gemfiles/rails_6.0.0.gemfile
```

#### テストファイル用意

spec/models/user_spec.rb

```ruby
require 'rails_helper'
​
RSpec.describe User, type: :model do
  it '計算1 should' do
    (1 + 2).should eq 3
  end
​
  it '計算2 expect' do
    expect(3 + 4).to eq 7
  end
end
​
```

#### テスト実行

それではテストを実行してみましょう。

```
$ RAILS_ENV=test bundle exec appraisal rspec spec/models/user_spec.rb
```
​出力結果 はこちらになります。
(deprecation warning など不要だと思われる情報は削ったり、パスを書き換えたりしてます)
​
Rails5.1.0 でのテスト

```
>> BUNDLE_GEMFILE=/my-gem/gemfiles/rails_5.1.0.gemfile bundle exec rspec spec/models/user_spec.rb
Run options: include {:focus=>true}
​
All examples were filtered out; ignoring {:focus=>true}
..
​
1 deprecation warning total
​
Finished in 0.21237 seconds (files took 5.08 seconds to load)
2 examples, 0 failures
```

Rails5.2.3 でのテスト

```
>> BUNDLE_GEMFILE=/my-gem/gemfiles/rails_5.2.3.gemfile bundle exec rspec spec/models/user_spec.rb
Run options: include {:focus=>true}
​
All examples were filtered out; ignoring {:focus=>true}
..
​
1 deprecation warning total
​
Finished in 0.22129 seconds (files took 5.45 seconds to load)
2 examples, 0 failures
```

Rails6.0.0 でのテスト

```
>> BUNDLE_GEMFILE=/my-gem/gemfiles/rails_6.0.0.gemfile bundle exec rspec spec/models/user_spec.rb
Run options: include {:focus=>true}
​
All examples were filtered out; ignoring {:focus=>true}
..
​
1 deprecation warning total
​
Finished in 0.07676 seconds (files took 6.03 seconds to load)
2 examples, 0 failures
```

これで、Appraisals ファイルに記述してある3種類の Rails バージョンで rspec が実行されます。
ちなみに、こんな感じで、`Rails 5.2.3` だけをテストすることも可能です。

```
$ RAILS_ENV=test bundle exec appraisal rails-5.2.3 rspec spec/models/user_spec.rb
```
​
はい。これで、特定のRailsバージョンが動かない処理が書かれたときに検知できますね！
​
みなさん、お気づきかと思いますが、開発する時に<b>自動テストがきちんと書かれているから検知できる</b>のです。
​
私は、自動テストの効果についてピンと来てませんでしたが、Rails Upgrade のプロジェクトを通じて、自動テストの重要性に気付かされました。