pockestrap

Web Programmer's memo

RuboCop-RSpec 1.16.0 の CHANGELOG を読む

RuboCop-RSpec のバージョン 1.16.0 がリリースされました。

https://github.com/backus/rubocop-rspec/releases/tag/v1.16.0

RuboCop-RSpec とは、RSpec 特有の問題を RuboCop で検知するためのプラグインです。

CHANGELOG から、新規追加の Cop を見ていこうと思います。

FactoryGirl/DynamicAttributeDefinedStatically

FactoryGirl では、アトリビュートの値を動的に定義することが出来ます。

FactoryGirl.define do
  factory :user do
    name { ['a', 'b', 'c'].sample }
  end
end

user1 = FactoryGirl.create :user
user1.name # => 'c'
user2 = FactoryGirl.create :user
user2.name # => 'b'

このように、ブロックをつかうことで動的にアトリビュートを定義することが出来ます。

この Cop は、動的に定義するつもりのアトリビュートが静的に定義されていた場合警告を出します。

例:

FactoryGirl.define do
  factory :user do
    # ブロックが無いため、`sample`は定義時に実行されてしまう
    name ['a', 'b', 'c'].sample
  end
end


# 何度実行しても name は同じ
user1 = FactoryGirl.create :user
user1.name # => 'a'
user2 = FactoryGirl.create :user
user2.name # => 'a'

RSpec/AlignLeftLetBrace, RSpec/AlignRightLetBrace

この Cop は、letを定義する際に左右の括弧が揃っているかどうかを検証します。

# RSpec/AlignLeftLetBrace は警告を出す。
let(:foobar) { blahblah }
let(:baz) { bar }
let(:a) { b }

# こう直す
let(:foobar) { blahblah }
let(:baz)    { bar }
let(:a)      { b }
# RSpec/AlignRightLetBrace は警告を出す。
let(:foobar) { blahblah }
let(:baz)    { bar }
let(:a)      { b }

# こう直す
let(:foobar) { blahblah }
let(:baz)    { bar      }
let(:a)      { b        }

なお、このCopはデフォルトでは無効になっています。 そのため、有効にするには明示的に.rubocop.ymlで指定する必要があります。

RSpec/AlignLeftLetBrace:
  Enabled: true
RSpec/AlignRightLetBrace:
  Enabled: true

RSpec/LetBeforeExamples

この Cop は、letの定義がitよりも前にあるべきであることを検査します。

describe 'let が it の後にあるので警告が出る' do
  it 'something' do
  end

  let(:foo) { 'foo' }
end

describe 'let が it の前にあるので問題ない' do
  let(:foo) { 'foo' }

  it 'something' do
  end
end

RSpec/MultipleSubjects

この Cop はsubjectが複数ある場合に警告を出します。

RSpec では subject が複数定義されていた場合、最後に定義された subject が subject として参照できます。 また、名前付き subject である場合はその名前で参照することが出来ますが、subject である必要はないでしょう。

そのため、一つのdescribeのスコープ内で定義される subject はひとつであるべきです。

例:

describe Foo do
  subject(:user) { User.new } # user としてのみアクセスできる
  subject { dead_code }       # アクセスできない
  subject(:post) { Post.new } # subject, post としてアクセスできる
end

RSpec/ReturnFromStub

この Cop は、戻り値を返すstubを定義する時に使うスタイルを統一します。

stub から戻り値を返すには、二つの方法があります。

allow(foo).to receive(:bar).and_return('baz')
allow(foo).to receive(:bar) { 'baz' }

この Cop はデフォルトではand_returnを使用するように指摘します(つまり、ブロックを使っていた場合警告されます)。

この挙動は、.rubocop.ymlで変更することができます。

# `and_return`の使用を強制したい場合(default)
RSpec/ReturnFromStub:
  EnforcedStyle: and_return

# ブロックの使用を強制したい場合
RSpec/ReturnFromStub:
  EnforcedStyle: block

RSpec/VoidExpect

RSpec では、以下のように空のexpectが書かれていても、何も検査されません。

it do
  # 何も検査しないが、エラーにはならない
  expect(something)
end

この Cop は、上記のようなコードに対して警告を追加します。

RSpec/InvalidPredicateMatcher

RSpec では、以下のようにして predicate method (メソッド名が?で終わるメソッド)に対応するマッチャーを呼び出すことが出来ます。

it do
  # expect('str'.start_with?('s')).to be_truthy と同じ
  expect('str').to be_start_with('s')
end

ですが、人間は?をつけたくなってしまいます。つまり、以下のような間違いを犯してしまいます。

it do
  expect('str').to be_start_with?('s')
  #                            ^ この ? は要らない
end

この Cop は上記のような間違いを指摘します。

この余分な?は実際にRSpecを実行した場合もエラーになりますが、そのエラーメッセージは若干分かりづらいです。

expected "str" to respond to `star_with??`

この Cop を使用することでより良いメッセージを手に入れることが出来ます。

RSpec/PredicateMatcher

前述した通り、RSpecには predicate method をマッチャーとして呼び出すことができます。

この Cop は、predicate method をマッチャーとして呼び出せるのにbe_truthyを使っている場合を検出します。

it do
  # これは be_start_with matcher を使用できる。
  expect('str'.start_with?('s')).to be_truthy

  # こう書いた方がスマート
  expect('str').to be_start_with('s')
end

なお、.rubocop.ymlで設定をすることで、be_truthyを使うような形を強制することも出来ます。

# `expect('str'.start_with?('s')).to be_truthy` と書きたい場合
RSpec/PredicateMatcher:
  EnforcedStyle: explicit

また、この Cop にはStrictオプションも存在します。 これはデフォルトではtrueになっていますが、falseにすることで厳密にはpredicate matcherでは置き換えられないものに対しても警告を出すようになります。

RSpec/PredicateMatcher:
  Strict: false
it do
  # これはだいたいの場合に置いて be_start_with matcher を使用できる
  expect('str'.start_with?('s')).to be true
end

Strict: falseによって、be_truthy以外にもbe trueeq trueを使用している場合にも警告を与えるようになります。
be_truthyと違いbe trueを使用している場合、そのテストは「trueとして評価される値を返す」ことを期待しているのではなく、「厳密にtrueを返す」ことを期待しているテストである可能性があります。
そのため、be trueは必ずしもpredicate matcherで置き換えられるとは限りません。

ただし、「厳密にtrueを返す」ことを期待するようなテストを書きたいのはライブラリを作っている場合などに限られると思います。 そのため、Rails applicationなどではStrict: falseにして運用してもほとんど問題はないでしょう。

RSpec/ExpectInHook

RSpec では、beforeafterなどのhookと、itの中で行えることは同じです。 そのため、beforeの中でexpectを書いてしまっても特に問題なく動きます。

describe do
  before do
    # before の中で expect は動く
    expect(something).to eq 1
  end

  it do
    # ...
  end
end

ですが、expectbeforeではなく、itの中に書かれるべきです。 この Cop は、上記のように it 以外のところに書かれたexpectに対して警告を出します。

まとめ

RuboCop-RSpec 1.16.0 で追加された Cop は以上になります。 これを気に RuboCop-RSpec を導入してみてはいかがでしょうか?