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

## 所感
* 全体的にテストのスコープをより大事にするようになったような印象
* なのでテストを書く側もちゃんと意識するべき
* 明示的に書くことを推奨するようになったような印象
* やっぱり`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はネストされた方のクラスを参照する

```rb
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での実行結果

```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では

```rb: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`と書くように変更になりました。

```rb: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で

```rb
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 :all`や`after :all`などのすべてのテストのスコープにまたがった部分では使われるべきではありません。
RSpec3ではそこがより厳密になりました。

```rb
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.to`を`should`に書き換えて実行してみてください。下記のような出力になるはずです。

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

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

Finished in 0.00088 seconds
2 examples, 0 failures
```

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

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

```rb
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を使う

テストコード

```rb
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を使っているときはとても気づきにくくなっています。

テストコード

```rb
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://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3

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