RailsプロジェクトのRSpec3.0.0へのアップデート
皆さん、こんにちわ。MUGENUPの narikazu です。6月2日にRSpec 3.0.0がリリースされました(Myron Marston » RSpec 2.99.0 and 3.0.0 have been released!)。RSpec 3.0.0での変更点はMyron Marston » Notable Changes in RSpec 3あるいは、その日本語訳のRSpec 3の重要な変更 - 有頂天Rubyをご参照下さい。
ここでは、弊社Railsプロジェクト(Rails 3.2.18)でのRSpec3.0.0へのアップデート手順を記述いたします。この記事が皆さんのアップデート時の参考になれば幸いです。
アップデート手順
アップデート手順は以下の記事を参考に致しました。
- 既存のRailsプロジェクトをRSpec 3.0にアップグレードする際の注意点 ~RSpec 3は怖くないよ!~ - Qiita
- Ruby - RSpecの最新の動向・RSpec 3へのアップグレードガイド - Qiita
正直、ほとんど上記記事と同じ手順を踏んでいます(汗)。しかし、異なる点もあるので、以下手順の大まかな流れをご説明します。
- 現在のテストが全てsuccessすることを確認
- rspec-railsのバージョンを2.99にアップデート
- 2.99の状態でテストを実行
- テスト結果のdeprecationを全て修正(transpecと必要なGemのインストールおよび手作業)
- rpsec-railsのバージョンを3.0.1にアップデート
- 3.0.1の状態でテストを実行
- テスト結果のdeprecationを全て修正(transpecと手作業)
以下では、各手順について詳細に記述していきます。
現在のテストが全てsuccessすることを確認
弊社では、CircleCIへブランチをpushすることをトリガーにテストを実行しています。以前はローカル環境にて並列でテストを高速でブン回すparallel_testsを使用しておりました。CircleCIはテスト結果がGithub上で表示されるので、とても便利ですね〜。少し話は逸れましたが、私の場合はRSpecアップデート用のブランチを作成し、pushしている間にrspec-railsのバージョンを上げる作業をしていました。
rspec-railsのバージョンを2.99にアップデート
Gemfileでrspec-railsのバージョンを2.99.0と指定し、bundle update rspec
を実行します(bundle update rspec-rails
ではないので注意)。
# Gemfile group :test, :development do gem 'rspec-rails', '~> 2.99.0' # ... end
2.99の状態でテストを実行
RSpec 2.99でどのようなdeprecation出るか確認するために、全体テストを行います。私の場合はRSpec 2.99アップデートのcommitを積んでGithubにブランチをpushし、CircleCIがテストをブン回してくれました。
テスト結果のdeprecatedを全て修正(transpecと必要なGemのインストールおよび手作業)
2.99で出たdeprecationを放置して、3.0.0に上げるとそのテストは3.0.0に準拠した記述方法ではないのでテストは落ちてしまいます。よって、出てきたdeprecationは全てこのタイミングで修正しましょう。ちなみに、私は258個のdeprecationがありました。
2.99でのdeprecationの修正は以下の流れで行いました。
- RSpec3.0で外部化されたGemのインストール
- GemのTranspecを使って全自動でRSpec3.0系の記述方法に移行
- Transpecで対応できなかった箇所を手作業で修正
以下で各手順について詳細にご説明します。
RSpec3.0で外部化されたGemのインストール
`mock_model` is deprecated. Use the `rspec-activemodel-mocks` gem instead.
上記のdeprecationが出力された方はいらっしゃらないでしょうか?RSpec2.0系で使用できたmock_model
はRSpec3.0では使用できなくなり、rspec-activemodel-mocks
として外部Gemになりました。よって、このgemをGemfileに記述し、bundle install
でインストールしましょう。
# Gemfile group :test do gem 'rspec-activemodel-mocks' # ... end
また下記のdeprecationが出力された方もいらっしゃるのではないでしょうか?
`expect(collection).to have(1).attachment` is deprecated. Use the rspec-collection_matchers gem or replace your expectation with something like `expect(collection.size).to eq(1)`
RSpec 2系でcollectionの数を検証するためにexpect(object).to have(2).items
という構文がありました。RSpec 3ではこの構文はなくなり、expect(object.items.size).to eq 2
と書く必要があります。個人的には、RSpec 2系の書き方は自然言語の英語のように書けて気に入っているので、従来の書き方を続けたいと思いました。そのような人のために、rspec-collection_matchersというGemが存在します。これもGemfileに記述し、bundle install
を使ってインストールしましょう。
# Gemfile group :test do gem "rspec-collection_matchers" # ... end
弊社のRailsプロジェクトでは使用していませんでしたが、its
記法もRSpec 3では使用できなくなり外部Gem化(rspec/rspec-its)しているようです。必要な方はインストールするとよいでしょう!
GemのTranspecを使って全自動でRSpec3.0系の記述方法に移行
RSpec2系を3.0の記述方法にコマンド一発で自動で移行してくれる神Gem transpecがあります。こちらをインストールし、コマンドでサクッと3.0の記述方法に書き変えましょう。インストールはgem i transpec
で行います。使い方も簡単で私の場合はtranspec --no-explicit-spec-type --keep have_items
というコマンドを打って一気に書き換えました。
注意していただきたいのは--no-explicit-spec-type
と--keep have_items
オプションをつけていることです。
--no-explicit-spec-type
オプション
2系ではspecファイルが配置されているディレクトリから自動的にspecのタイプ(model, controller, feature等)を判別してくれました。しかし、RSpec 3ではこの機能がオフになっています。よって、Rspec 3ではspecタイプを明示する必要があります。引き続き配置しているディレクトリに応じてspecタイプを自動判別させるためには、spec_helper.rb
にinfer_spec_type_from_file_location!
オプションを付けます。Transpecは自動的にspecタイプのメタデータを追加します。しかし、2系と同じ挙動が気に入っているので、--no-explicit-spec-type
オプションを指定して実行することで、specタイプのメタデータ追加が無効になり、代わりにinfer_spec_type_from_file_location!
を自動的に追加します。
--keep have_items
オプション
expect(object).to have(2).items
構文を引き続き使用するべくrspec-collection_matchersのGemを入れた方は、Transpecでhave().item
記法を変更させないように--keep have_items
オプションを付けましょう。
Transpecで対応できなかった箇所を手作業で修正
いくつかTranspecで書換えを行った後もdeprecationが残っていたので、手で書き換えました。ここでは全てのdeprecationを修正しておきましょう。
rpsec-railsのバージョンを3.0.1にアップデート
2.99.0にアップデートした時と同様にGemfileでrspec-railsのバージョンを3.0.1と指定し、bundle update rspec
を実行します(bundle update rspec-rails
ではないので注意)。
# Gemfile group :test, :development do gem 'rspec-rails', '~> 3.0.1' # ... end
3.0.1にアップデート後、再度全テストを通します。
テスト結果のdeprecationを全て修正(transpecと手作業)
3.0.1アップデート後にテストを実行すると以下のエラーで落ちました。
`raise_too_low_error': You are using capybara 2.1.0. RSpec requires version >= 2.2.0. (RSpec::Support::LibraryVersionTooLowError)
Gemのcapybaraのバージョンが低いと言われたので、bundle update capybara
でアップデートしました。
また以下のようなdeprecationが表示されました。
The Fuubar formatter uses the deprecated formatter interface not supported directly by RSpec 3. To continue to use this formatter you must install the `rspec-legacy_formatters` gem, which provides support for legacy formatters or upgrade the formatter to a compatible version.
これはテストの進捗状況を可視化するgem fuubar(Ruby - Rspecのテスト進捗状況が可視化できるFuubarがいい感じ! - Qiita)のバージョンが低いため表示されます。Rspec 3に対応した2.0.0.rc1を指定しbundle update fuubar
でアップデートしました。
# Gemfile group :test, :development do gem 'fuubar', '~> 2.0.0.rc1' end
そして、再度transpecを用いてdeprecationを消していきます。
なぜかというと2系のstub_chain
記法がallow(obj).to receive_message_chain()
という記法になるのですがRspec 3.0からでないと使えないため、3.0にアップデート後もtranspecを使用します。前回と同様transpec --no-explicit-spec-type --keep have_items
コマンドを実行致しました。
これでほとんどのdeprecationが消えました。
また今回のRailsプロジェクトでは本来は通るfeatureテストが時々落ちることがあるので、一度落ちたテストをもう一度実行するモンキーパッチ(sorah / auto_retry.rb)を使わせていただいていました。しかし、Rspec3.0アップデートのapi変更でエラーが出るようになっていたので、少し書き換えました。以下のコードにより、featureスペックかit "hoge", auto_retry: true do ... end
としているテストは落ちるともう一度だけテストを実行してくれます。
# spec/support/auto_retry.rb module RSpec module Core class Example def run_with_retry(example_group_instance, reporter) @retrying_count = 0 succeeded = run_without_retry(example_group_instance, reporter) unless succeeded return finish_without_retry(reporter) unless retry_needed? $stderr.puts "[RETRY] #{self.location}: #{self.full_description}" @retrying_count += 1 # Initialize itself (originally this has done in ExampleGroup#run_example) @exception = nil # before_all_ivarsをbefore_context_ivarsに書き換え example_group_instance = example_group_instance.class.new.tap do |group| group.class.set_ivars(group, group.class.before_context_ivars) end succeeded = run_without_retry(example_group_instance, reporter) end succeeded end alias_method :run_without_retry, :run alias_method :run, :run_with_retry private def finish_with_retry(reporter) if (@retrying_count || 0) < 1 && @exception false else finish_without_retry(reporter) end end alias finish_without_retry finish alias finish finish_with_retry def retry_needed? # 元々は@optionsで[:auto_retry]としていたが、metadata[:auto_retry]に書き換え metadata[:auto_retry] == true || file_path =~ /spec\/features/ end end end end
その他にはguardのバージョンが低くて動かないようになっていたので、最新のものにアップデートしRspec3でも動くようにしました。
まとめ
以上、弊社のRailsアプリでのRspec3.0へのアップデート手順を記述してみました。作業自体は2〜3時間で終わりました。transpecという神Gemがあるので思ったよりも大変ではなかったですので、皆さんもアップデートしてはいかがでしょうか?
またここで網羅できなかったことおよび、基本的な手順は以下の記事から得ることができます。