rspecの死んでいるコードを検出するrspectreを試してみた


rspectreってなに?

https://github.com/dgollahon/rspectre

rspectreはspecファイル内の以下のような意味をなしていない死んだコードを違反として検出・自動修正してくれるツール(Ruby Gem)です。

  • 未使用の letUnusedLet
  • 未使用の subjectUnusedSubject
  • 未使用の shared_examplesshared_contextsUnusedSharedSetup

rubocop-rspec のような静的解析ツールでは検出しがたいものを実際にrspecを実行しつつ解析して検出します。

Ruby 2.5以降、rspec 3.0以降をサポートしているようです。[1]

rspectreの使い方

インストール

READMEのままです。

以下を実行する

gem install rspectre

または Gemfile に以下を追加して bundle install します。

gem "rspectre"

実行方法

特に設定ファイルなどはないようです。

以下のように実行します。[2]

$ rspectre

rspecに追加のオプションを渡したい場合は以下のように --rspec オプションを使います。

$ rspectre --rspec '--fail-fast --format documentation spec/'

特定のファイルだけ指定して実行したい場合も --rspec オプションを使います。

$ rspectre --rspec '--fail-fast spec/path/to/your_spec.rb'

自動修正する場合は以下のように --auto-correct オプションを使います。

$ rspec --auto-correct

お試し実行

以下のようなspecファイルがあったとします。

require "spec_helper"

RSpec.describe "rspectreお試し" do
  shared_examples "未使用のshared_examplesです" do
    it "使われない検証です" do
      expect(1 + 1).to eq 2
    end
  end

  subject { "未使用のsubjectです" }
  let(:unused_let) { "未使用のletです" }

  it "何か検証します" do
    expect(1 + 1).to eq 2
  end
end

rspectreを実行すると違反が以下のように検出されます。

$ rspectre

/home/yuji_developer/rspectre-test/spec/hoge_spec.rb:4:3: UnusedSharedSetup: Unused `shared_examples`, `shared_examples_for`, or `shared_context` definition.
      shared_examples "未使用のshared_examplesです" do
      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

/home/yuji_developer/rspectre-test/spec/hoge_spec.rb:10:3: UnusedSubject: Unused `subject` definition.
      subject { "未使用のsubjectです" }
      ^^^^^^^

/home/yuji_developer/rspectre-test/spec/hoge_spec.rb:11:3: UnusedLet: Unused `let` definition.
      let(:unused_let) { "未使用のletです" }
      ^^^^^^^^^^^^^^^^

自動修正を実行してみます。

$ rspectre --auto-correct

差分は以下のようになります。

diff --git spec/hoge_spec.rb spec/hoge_spec.rb
index 0cecc3e..97fd20f 100644
--- spec/hoge_spec.rb
+++ spec/hoge_spec.rb
@@ -1,14 +1,10 @@
 require "spec_helper"

 RSpec.describe "rspectreお試し" do
-  shared_examples "未使用のshared_examplesです" do
-    it "使われない検証です" do
-      expect(1 + 1).to eq 2
-    end
-  end
+  

-  subject { "未使用のsubjectです" }
-  let(:unused_let) { "未使用のletです" }
+  
+  

   it "何か検証します" do
     expect(1 + 1).to eq 2

気になったところ

自動修正でインデントのスペースや空行が残る

自動修正で UnusedLet などを修正するとインデントのスペースや空行が残ったままになります。
これはrubocopで余分なスペースや空行を修正できるのでそこまで困ることはなさそうですがちょっと気になりますよね。

--rspec オプションを指定した場合はrspecに指定したいすべてのオプションを指定する必要がある

例えば以下のようにフォーマットオプションだけを指定した場合、デフォルトで指定される --fail-fast オプションが指定されていない扱いになります。
これだけならまだ良いのですが、対象のファイルも指定されていない扱いになるようでこれだと何も解析されません。

$ rspectre --rspec '--format documentation'

意図したように動作させるためには、以下のように指定する必要があります。

$ rspectre --rspec '--fail-fast --format documentation spec/'

実行中にrspecの出力が表示されない

rspectre経由で実行するとrspecの出力が実行中にコンソールに出力されません。
テストが失敗した場合は最後にrspecの出力が表示される仕組みになっているようです。
どこまで進んだのかが見えないのがちょっと精神衛生上良くないかなと思ったりします。

並列実行がサポートされていない

rspecはparallel_testsなどで並列実行が可能ですが、rspectreではサポートされていないようです。
規模が大きいプロジェクトでは気軽に全specファイルに対して実行するのは難しいかもしれません。

依存関係がそこそこある

一概に悪いとは言えませんが、rspectreは依存関係がそれなりにあります。
依存関係があまり増えるとメンテナンスの手間が上がるので Gemfile に追加するか迷っています。

まとめ

rspectreはまだ改善の余地がありそうですが、現状でもかなり便利に使えると思います。
実際に動いているプロジェクトでrspectreを使ってみたところ、未使用の let が見つかったり、未使用の let と思いきやexmapleがないことで未使用と検出された[3]ものが見つかったりと、かなり有意義な結果となりました。

どなたかのご参考になれば幸いです。

脚注
  1. 2020/11/11現在の最新版のv0.0.3について書いています。 ↩︎

  2. bundle exec は省略するので必要に応じて脳内補完してください。
    デフォルトでrspecには --fail-fast オプションが渡されて実行されます。 ↩︎

  3. そうです、テスト出来ていませんでした。exampleを追加したらちゃんと動いたのでセーフです。 ↩︎