1. ホーム
  2. ruby

Rubocop 25行ブロックサイズとRSpecのテスト

2023-07-22 13:24:27

質問

典型的な RSpec ユニットテストでは、コードを構造化し、DSL "magic" を使用して spec を BDD ステートメントのように読ませるために、入れ子の Ruby ブロックを広範に使用します。

describe Foo do
  context "with a bar" do
    before :each do
      subject { Foo.new().add_bar }
    end

    it "looks like a baz" do
      expect # etc

理想的な仕様では、各例は比較的短くて正確なものになります。しかし、RSpecの構造がこのように機能しているため、外側のブロックが100行以上になることはよくあることで、多くのspec例が必要ではなく、それぞれが数行の具体的な設定を持っていて describe ブロックの大きさは、説明する対象のコードと同じかそれ以上になります。

最近の Rubocop のアップグレードで、ブロックの長さは 25 行以内という新しいルールが導入されました。に記載されていないので、その根拠はよくわかりません。 Ruby スタイルガイド . 良いことだと思い、デフォルトのルールセットに追加するのはわかるのですが。しかし、アップグレード後、Rubocop のテストが何度も失敗し、次のようなメッセージが表示されるようになりました。 tests/component_spec.rb:151:3: C: Block has too many lines. [68/25]

Rubocop のようなコードメトリックツールを使って、私は のように のポリシーを持っています (主に、タブとスペースやその他の些細なことを議論するのは時間の無駄であり、IME の場合 は決して ここでは、明らかに不可能です。2 つのコア データ品質ツールが、コード レイアウト アプローチについて意見が一致しません。

これに対して、私たちは単に Rubocop ブロック サイズ ルールを高いしきい値に設定しました。しかし、そうすると、何が足りないのだろう?RSpec はコード レイアウトのために今では信用されていないアプローチを使用しているのでしょうか、そして、どのような 合理的な のブロック サイズを小さくするには、どのような選択肢があるのでしょうか?大きなブロックを避けるためにコードを再構築する方法はわかりますが、それらは例外なく、Rubocop のルールを満たすことだけを目的とした醜いハックです (たとえば、すべてのブロックをヘルパー関数に分割する、など)。

def looks_like_a_baz
  it "looks like a baz" do
         expect # etc
  end
end

def bar_context
  context "with a bar" do
    before :each do
      subject { Foo.new().add_bar }
    end
    looks_like_a_baz
  end
end


describe Foo do
  bar_context
  # etc

. . . つまり、それは可能なのですが、このように大量の spec 例をヘルパー関数にすることは、RSpec の設計で推奨されている読みやすいアプローチとは正反対のようです。

それを無視する方法を見つける以外に何かできることはありますか?


このトピックについて、最も近い既存の質問をここで見つけることができました。 RSpec & Rubocop / Ruby スタイルガイド で、これはテストテンプレートを編集することで解決できるように見えました。

どうやって解決するの?

最近の Rubocop のアップグレードで、ブロックの長さを 25 行以下にするという新しいルールが導入されました。Rubyのスタイルガイドには記載されていないので、その根拠はよくわかりません。

かつては、すべての警官は The Ruby Style Guide に基づいており、RuboCop はコミュニティによって定められた慣習を遵守するための方法でした。

その後方向性が変わり、RuboCop の範囲は、開発者がコードベース全般の一貫性を確保するのを助けるために広がりました。これは、2 つのことをもたらしました。

  1. コップ (Ruby スタイル ガイドに基づくものであっても) はほとんど設定可能になりました。
  2. Ruby スタイルガイドに記載されていないものでも、プロジェクトで一貫性を持たせるために有用なコップがあります。

この警官は2番目のカテゴリに属します。

RSpecはコードレイアウトのために今では信用されていないアプローチを使用していますか?また、RSpecのテストでブロックサイズを減らすために、どのような合理的なオプションがあるでしょうか?

短い答えは「いいえ」です。DSL はまだイケてます :-)

この警官は命令型プログラミングの意味での大きなブロックを対象としています。一般的なガイドとして、それはしばしば宣言的であるDSLには適用されません。例えば、長い routes.rb というファイルがあるのは、まったく問題ありません。これはスタイル違反というよりも、大規模なアプリケーションの自然な結果です。(そして、多くのテストを持つことは純粋に素晴らしいことです)。

さて、RuboCop はかなり賢いのですが、何が DSL で何がそうでないかを知らないので、自動的に無視することはできません。Rails routes や RSpec spec など、人気のあるフレームワークの DSL 入力メソッドを除外することは可能だという意見もあるでしょう。そうしない理由は主に

  1. 偽陰性。どのクラスも同じ名前のブロックを取って、メソッドを実装することができます。
  2. RuboCopはRubyの解析ツールであり、外部ライブラリについては本当に知らないはずです。(ただし /spec ディレクトリを除外するのは、適切な拡張システムができるまでの礼儀であり、これを処理するには rubocop-rspec gem で処理できます)。

つまり、それは可能なのですが、この方法でspecのサンプルを大量にヘルパー関数にすることは、RSpecの設計が推奨する読みやすいアプローチとは正反対のようです。

肝心なのは RuboCop は、私たちがよりよいコードを書くのを助けるためにあります。もし私たちのアプリケーションの設計が健全であるにもかかわらず、RuboCop を喜ばせるためだけに可読性を下げているのであれば、フィルタリングするか、設定するか、あるいは無効にすべきです :-)

これに対して、私たちは単に Rubocop のブロック サイズ規則を高いしきい値に設定しました。しかし、これでは、何を見逃しているのでしょうか?

これはかなり鈍感なツールで、あなたがほのめかしているように、おそらくそのために偽陰性が発生するでしょう。この警官の偽陽性には 2 つのタイプがあります。

  1. 純粋に宣言的な DSL を含むファイル (例: Rails ルート、RSpec 仕様)。
  2. 宣言的なDSLが、ほとんど命令的なコードに混じっているファイル、例えば aasm ステートマシン宣言のようなものです。

最初のケースでは、ファイルまたはディレクトリを除外することが最適な解決策であり、2番目のケースでは、インライン無効化を使用することです。

あなたの場合は .rubocop.yml を使ってください。

Metrics/BlockLength:
  Exclude:
    - 'Rakefile'
    - '**/*.rake'
    - 'test/**/*.rb'

(リストが上書きされるため、デフォルトの設定から基本的な除外を再度行う必要があることに注意してください)。