LoginSignup
23
24

More than 5 years have passed since last update.

RSpec2.14からRSpec3に対応するときにわかったこと

Posted at

所感

  • 全体的にテストのスコープをより大事にするようになったような印象
  • なのでテストを書く側もちゃんと意識するべき
  • 明示的に書くことを推奨するようになったような印象
  • やっぱりexpectをつかたほうが健全

コマンドラインオプションもいくつか整理されています。

DocumentationFormatterの'spec', 's'廃止

rspec -cfs /path/to/file_or_dir
rspec -fs /path/to/file_or_dir
rspec -f spec /path/to/file_or_dir

などとして使っていた場合はそれぞれ

rspec -cfd /path/to/file_or_dir
rspec -fd /path/to/file_or_dir
rspec -f doc /path/to/file_or_dir

のように指定するように変わりました.

--line-number, -l オプションの廃止

指定した行付近のexampleを実行するには

rspec -l 10 /path/to/file

の代わりに以下のように指定するようになりました.

rspec /path/to/file:10

described_classはネストされた方のクラスを参照する

class Hoge
end

class Moge
end

describe Hoge do
  it 'ここでのdescribed_classはHogeクラス' do
    expect(described_class).to eq(Hoge)
  end

  describe Moge do
    it 'RSpec2ではHogeクラス、3ではMogeクラスになる' do
      expect(described_class).to eq(Hoge)
    end
  end

  context '例えば以下のようにテストを生成したとき' do
    sample_id = [100, 999, 1, 5]

    sample_id.each do |id|
      context id do
        it 'RSpec2ではこれでGreen, RSpec3ではFixnumなのでRed' do
          expect(described_class).to eq(Hoge)
        end
      end
    end
  end
end

RSpec2.14での実行結果

RSpec2.14
Finished in 0.00311 seconds
6 examples, 0 failures

RSpec3での実行結果

Failures:

  1) Hoge Moge RSpec2ではHogeクラス、3ではMogeクラスになる
     Failure/Error: expect(described_class).to eq(Hoge)

       expected: Hoge
            got: Moge

       (compared using ==)

       Diff:
       @@ -1,2 +1,2 @@
       -Hoge
       +Moge
     # ./sample_spec.rb:14:in `block (3 levels) in <top (required)>'

  2) Hoge 例えば以下のようにテストを生成したとき 100 RSpec2ではこれでGreen, RSpec3ではFixnumなのでRed
     Failure/Error: expect(described_class).to eq(Hoge)

       expected: Hoge
            got: 100

       (compared using ==)
     # ./sample_spec.rb:24:in `block (5 levels) in <top (required)>'

  3) Hoge 例えば以下のようにテストを生成したとき 999 RSpec2ではこれでGreen, RSpec3ではFixnumなのでRed
     Failure/Error: expect(described_class).to eq(Hoge)

       expected: Hoge
            got: 999

       (compared using ==)

※中略※

Finished in 0.00209 seconds (files took 0.09767 seconds to load)
6 examples, 5 failures

be_true, be_falseはbe_truthy, be_falsyに

RSpec2でのbe_true, be_falseはtruthyかどうか、falsyかどうかを検査しているだけでした。

つまりRSpec2では

RSpec2
describe 'be_true, be_false matcher' do
  it 'Stringインスタンスはtruthy' do
    expect('hogeee').to be_true
  end
  it 'nilはfalsy' do
    expect(nil).to be_false
  end
end
2 examples, 0 failures

となるので非常に紛らわしいです。
ので、RSpec3からはbe_truthy, be_falsyと書くように変更になりました。

RSpec3
describe 'be_true, be_falseはbe_truthy, be_falsyに' do
  it 'Stringインスタンスはtruthy' do
    expect('hogeee').to be_truthy
  end
  it 'nilはfalsy' do
    expect(nil).to be_falsy
  end
end
2 examples, 0 failures

true, falseのチェックはbe true, be falseで

it 'trueのチェック' do
  expect(true).to be true
end
it 'falseのチェック' do
  expect(false).to be false
end
2 examples, 0 failures

before :all, after :all ブロック内でlet, subjectで宣言した識別子を使うとエラー

let, subjectは各itブロックごとに初期化されるものなので、そもそもbefore :allafter :allなどのすべてのテストのスコープにまたがった部分では使われるべきではありません。
RSpec3ではそこがより厳密になりました。

describe 'letのスコープをより厳密に' do
  let (:hoge) { 'hogemoge' }

  before :all do
    hoge
  end

  it { expect(hoge).to eq('hogemoge') }
end

describe 'subjectのスコープをより厳密に' do
  subject { 'hogemoge' }

  before :all do
    subject
  end

  it { is_expected.to eq('hogemoge') }
end

上記のテストコードはRSpec3では以下の様な出力になります。

letのスコープをより厳密に
  example at ./sample_spec.rb:8 (FAILED - 1)

subjectのスコープをより厳密に
  example at ./sample_spec.rb:18 (FAILED - 2)

Failures:

  1) letのスコープをより厳密に
     Failure/Error: hoge
     RuntimeError:
       let declaration `hoge` accessed in a `before(:context)` hook at:
         /path/to/sample_spec.rb:5:in `block (2 levels) in <top (required)>'

       `let` and `subject` declarations are not intended to be called
       in a `before(:context)` hook, as they exist to define state that
       is reset between each example, while `before(:context)` exists to
       define state that is shared across examples in an example group.
     # ./sample_spec.rb:5:in `block (2 levels) in <top (required)>'

  2) subjectのスコープをより厳密に
     Failure/Error: subject
     RuntimeError:
       subject accessed in a `before(:context)` hook at:
         /path/to/sample_spec.rb:15:in `block (2 levels) in <top (required)>'

       `let` and `subject` declarations are not intended to be called
       in a `before(:context)` hook, as they exist to define state that
       is reset between each example, while `before(:context)` exists to
       define state that is shared across examples in an example group.
     # ./sample_spec.rb:15:in `block (2 levels) in <top (required)>'

Finished in 0.00048 seconds (files took 0.09662 seconds to load)
2 examples, 2 failures

Failed examples:

rspec ./sample_spec.rb:8 # letのスコープをより厳密に
rspec ./sample_spec.rb:18 # subjectのスコープをより厳密に

RSpec2.14で実行するときは、上記のis_expected.toshouldに書き換えて実行してみてください。下記のような出力になるはずです。

letのスコープをより厳密に
  should eq "hogemoge"

subjectのスコープをより厳密に
  should eq "hogemoge"

Finished in 0.00088 seconds
2 examples, 0 failures

たちが悪いのは、after :allフックでのみこのエラーが出た場合、テストの実行自体はGreenになってしまうことです。

下記のようにテストコードのbeforeafterに書き換えて実行すると。。。

describe 'letのスコープをより厳密に' do
  let (:hoge) { 'hogemoge' }

  after :all do
    hoge
  end

  it { expect(hoge).to eq('hogemoge') }
end

describe 'subjectのスコープをより厳密に' do
  subject { 'hogemoge' }

  after :all do
    subject
  end

  it { is_expected.to eq('hogemoge') }
end
letのスコープをより厳密に
  should eq "hogemoge"

An error occurred in an `after(:context)` hook.
  RuntimeError: let declaration `hoge` accessed in an `after(:context)` hook at:
  /path/to/sample_spec.rb:5:in `block (2 levels) in <top (required)>'

`let` and `subject` declarations are not intended to be called
in an `after(:context)` hook, as they exist to define state that
is reset between each example, while `after(:context)` exists to
cleanup state that is shared across examples in an example group.

  occurred at /path/to/rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/rspec-core-3.1.3/lib/rspec/core/memoized_helpers.rb:155:in `fetch'


subjectのスコープをより厳密に
  should eq "hogemoge"

An error occurred in an `after(:context)` hook.
  RuntimeError: subject accessed in an `after(:context)` hook at:
  /path/to/sample_spec.rb:15:in `block (2 levels) in <top (required)>'

`let` and `subject` declarations are not intended to be called
in an `after(:context)` hook, as they exist to define state that
is reset between each example, while `after(:context)` exists to
cleanup state that is shared across examples in an example group.

  occurred at /path/to/rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/rspec-core-3.1.3/lib/rspec/core/memoized_helpers.rb:155:in `fetch'


Finished in 0.00124 seconds (files took 0.09662 seconds to load)
2 examples, 0 failures

0 failures...
テストは全部Greenですね。。。

引数がないことのテストは明示的にno_argsを使う

テストコード

describe '引数をとらないことをテストするとき明示的にno_argsを使う' do
  it 'no_args使うと意図したテストになる' do
    expect(Array).to receive(:new).with(no_args)
    Array.new
  end
  it 'no_args使わなかったらエラー' do
    expect(Array).to receive(:new).with()
    Array.new
  end
end

実行結果

引数をとらないことをテストするとき明示的にno_argsを使う
  no_args使うと意図したテストになる
  no_args使わなかったらエラー (FAILED - 1)

Failures:

  1) 引数をとらないことをテストするとき明示的にno_argsを使う no_args使わなかったらエラー
     Failure/Error: expect(Array).to receive(:new).with()
     ArgumentError:
       `with` must have at least one argument. Use `no_args` matcher to set the expectation of receiving no arguments.
     # ./sample_spec.rb:7:in `block (2 levels) in <top (required)>'

Finished in 0.00569 seconds (files took 0.0962 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./qiita_spec.rb:6 # 引数をとらないことをテストするとき明示的にno_argsを使う no_args使わなかったらエラー

shouldメソッドにmatcherが渡されなかったときテストが成功しているように見えてしまう

例えばエクスペクテーションの行が長すぎて折り返したいとき、間違ったところで折り返してしまうとテストがスルーされてしまいます。

そもそもRSpec3を使っていればshouldを使うとDeprecation Warningが出るんですが、これが2.14を使っているときはとても気づきにくくなっています。

テストコード

describe 'shouldの罠' do
  context 'エクスペクテーションの行が長いとき' do
    it '間違ったところで折り返すとテストがスルーされる' do
      tooooooooooooo_loooooooooooooooong_valueeeeeee = 'hoge'
      tooooooooooooo_loooooooooooooooong_expected_valueeeeeeeee = 'hoge'
      tooooooooooooo_loooooooooooooooong_valueeeeeee.should
        eq('mogeeeee')
    end

    it 'expectを使うとちゃんとエラーが出る' do
      tooooooooooooo_loooooooooooooooong_valueeeeeee = 'hoge'
      tooooooooooooo_loooooooooooooooong_expected_valueeeeeeeee = 'hoge'
      expect(tooooooooooooo_loooooooooooooooong_valueeeeeee).to
        eq('mogeeeee')
    end
  end
end

実行結果(RSpec2.14のとき)

shouldの罠
  エクスペクテーションの行が長いとき
    間違ったところで折り返すとテストがスルーされる
    expectを使うとちゃんとエラーが出る (FAILED - 1)

Failures:

  1) shouldの罠 エクスペクテーションの行が長いとき expectを使うとちゃんとエラーが出る
     Failure/Error: expect(tooooooooooooo_loooooooooooooooong_valueeeeeee).to
     ArgumentError:
       The expect syntax does not support operator matchers, so you must pass a matcher to `#to`.
     # ./qiita_spec.rb:13:in `block (3 levels) in <top (required)>'

Finished in 0.0008 seconds
2 examples, 1 failure

Failed examples:

rspec ./qiita_spec.rb:10 # shouldの罠 エクスペクテーションの行が長いとき expectを使うとちゃんとエラーが出る

一つ目のテストは成功しているように見えてしまいますが、意図した比較はできていません。
expectを使うとちゃんとtoに引数を渡してくださいとおこられます。

公式のアナウンスを見たい方はこちら

日本語訳は以下
http://nilp.hatenablog.com/entry/2014/05/28/003335

23
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
24