所感
- 全体的にテストのスコープをより大事にするようになったような印象
- なのでテストを書く側もちゃんと意識するべき
- 明示的に書くことを推奨するようになったような印象
- やっぱり
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での実行結果
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では
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
と書くように変更になりました。
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 :all
やafter :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.to
をshould
に書き換えて実行してみてください。下記のような出力になるはずです。
letのスコープをより厳密に
should eq "hogemoge"
subjectのスコープをより厳密に
should eq "hogemoge"
Finished in 0.00088 seconds
2 examples, 0 failures
たちが悪いのは、after :all
フックでのみこのエラーが出た場合、テストの実行自体はGreenになってしまうことです。
下記のようにテストコードのbefore
をafter
に書き換えて実行すると。。。
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
に引数を渡してくださいとおこられます。