- tl;dr
- 茶器
- awspec でのカスタムマッチャ
- 抹茶を点てる
— RSpec::Matchers.define による抹茶の定義
— chain を使って, マッチャを拡張する
— fail 時のメッセージを定義する
— ヘルパーメソッドを利用する - 以上
- 参考
tl;dr
awspec のコードを見ていて, どんな風に独自のマッチャを実装しているのか, ずーっと気になっていたので, Rspec のカスタム抹茶を点てる方法について調べてみたのと, 簡単なサンプル抹茶を点ててみたメモです.
茶器
以下の茶器 (環境) を利用します.
$ ruby --version ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-linux] $ bundle exec rspec --version RSpec 3.7 - rspec-core 3.7.1 - rspec-expectations 3.7.0 - rspec-mocks 3.7.0 - rspec-support 3.7.1
awspec でのカスタムマッチャ
例えば, 以下のようなマッチャが定義されています.
# https://github.com/k1LoW/awspec/blob/master/lib/awspec/matcher/have_db_parameter_group.rb RSpec::Matchers.define :have_db_parameter_group do |name| match do |db_instance_identifier| db_instance_identifier.has_db_parameter_group?(name, @parameter_apply_status) end chain :parameter_apply_status do |parameter_apply_status| @parameter_apply_status = parameter_apply_status end end
とある RDS DB インスタンス (my-rds
) にパラメータグループ default.mysql5.6
が付与されているか, ステータス (parameter_apply_status) は pending-reboot
となっていることをテストするマッチャです.
以下のように利用します.
# https://github.com/k1LoW/awspec/blob/master/spec/type/rds_spec.rb describe rds('my-rds') do ... it { should have_db_parameter_group('default.mysql5.6') } it do should have_db_parameter_group('default.mysql5.6')\ .parameter_apply_status('pending-reboot') end end
抹茶を点てる
RSpec::Matchers.define による抹茶の定義
RSpec::Matchers.define
を利用することで, 以下のようなマッチャを定義することが出来ます.
require 'rspec/expectations' RSpec::Matchers.define :have_word do |expected| match do |actual| actual.include?(expected) end end RSpec.describe 'foo-bar-bar' do it { should have_word('foo') } end
match ブロック内に現在の状態 (actual) とあるべき状態 (expected) を比較するロジックを記載すれば良さそうです.
これを実行すると, 以下のように出力されます.
$ bundle exec rspec sample1-1.rb foo-bar-bar should have word "foo" Finished in 0.00164 seconds (files took 0.15382 seconds to load) 1 example, 0 failures
念の為, 以下のように書いて, fail になることを確認します.
... 略 ... RSpec.describe 'foo-bar-bar' do it { should have_word('baz') } end
以下, 実行結果です.
$ bundle exec rspec sample1-1.rb foo-bar-bar should have word "foo" foo-bar-bar should have word "baz" (FAILED - 1) Failures: 1) foo-bar-bar should have word "baz" Failure/Error: it { should have_word('baz') } expected "foo-bar-bar" to have word "baz" # ./sample1-1.rb:14:in `block (2 levels) in <top (required)>' Finished in 0.02444 seconds (files took 0.20172 seconds to load) 2 examples, 1 failure
LGTM.
chain を使って, マッチャを拡張する
先の例では, foo-bar-bar
という文字列の中に, foo
というワードが含まれていることをテストしていますが, foo
というワードが 1 つ含まれていることをテストするコードを書くとすると, 以下のように書きたくなると思います. (少なくとも, 自分は書きたいと思います)
RSpec.describe 'foo-bar-bar' do it { should have_word('foo').count(1) } end
これを実現する為に, chain を使って, チェーンするメソッドを定義することが出来ますので, 以下のように書いてみました.
require 'rspec/expectations' RSpec::Matchers.define :have_word do |expected| match do |actual| if @num.nil? actual.include?(expected) else actual.split('-').count(expected) == @num end end chain :count do |num| @num = num end end
chain ブロック内で count
メソッドに渡された引数をインスタンス変数に代入しています.
chain :count do |num| @num = num end
実際にテストを走らせてみます.
$ bundle exec rspec sample1-2.rb foo-bar-bar should have word "foo" foo-bar-bar should have word "foo" Finished in 0.00133 seconds (files took 0.18253 seconds to load) 2 examples, 0 failures
異常値を count メソッドに定義して, fail になることも確認しておきます.
$ bundle exec rspec sample1-2.rb foo-bar-bar should have word "foo" foo-bar-bar should have word "foo" (FAILED - 1) Failures: 1) foo-bar-bar should have word "foo" Failure/Error: it { is_expected.to have_word('foo').count(2) } expected "foo-bar-bar" to have word "foo" # ./sample1-2.rb:23:in `block (2 levels) in <top (required)>' Finished in 0.03156 seconds (files took 0.26405 seconds to load) 2 examples, 1 failure Failed examples: rspec ./sample1-2.rb:23 # foo-bar-bar should have word "foo"
LGTM.
fail 時のメッセージを定義する
fail した際のメッセージについてもカスタマイズしたメッセージを出力することが出来ます.
require 'rspec/expectations' RSpec::Matchers.define :have_word do |expected| match do |actual| if @num.nil? actual.include?(expected) else actual.split('-').count(expected) == @num end end ... 略 ... failure_message do |actual| if @num.nil? "#{actual} に #{expected} は含まれていない." else "#{actual} に #{expected} は #{@num} 個含まれていない." end end end
failure_message ブロックに fail 時に出力したいメッセージを記載します.
実際に fail させてみると, 以下のように出力されます.
$ bundle exec rspec sample1-3.rb foo-bar-bar should have word "foo" foo-bar-bar should have word "foo" (FAILED - 1) Failures: 1) foo-bar-bar should have word "foo" Failure/Error: it { is_expected.to have_word('foo').count(2) } foo-bar-bar に foo は 2 個含まれていない. # ./sample1-3.rb:32:in `block (2 levels) in <top (required)>' Finished in 0.04706 seconds (files took 0.26292 seconds to load) 2 examples, 1 failure Failed examples: rspec ./sample1-3.rb:32 # foo-bar-bar should have word "foo"
イイ感じです.
ヘルパーメソッドを利用する
以下のように書くことで, 検証ロジックを別メソッドに定義することが出来ます.
RSpec::Matchers.define :have_word do |expected| match do |actual| include_word?(actual, expected) end def include_word?(actual, expected) if @num.nil? actual.include?(expected) else actual.split('-').count(expected) == @num end end ... 略 ... end
テストを実行してみます.
$ bundle exec rspec sample1-4.rb foo-bar-bar should have word "foo" foo-bar-bar should have word "foo" Finished in 0.00129 seconds (files took 0.18528 seconds to load) 2 examples, 0 failures
LGTM.
以上
カスタム抹茶を点てるには…
- RSpec::Matchers.define を利用する
- match ブロック内に検証のロジックを書く
- メソッドチェインを利用したい場合には chain ブロック内で引数をインスタンス変数に代入する
- fail 時のメッセージは failure_message で上書きすることが出来る
- ヘルパーメソッドを利用して, 検証のロジックを match ブロックから追い出すことも可能
という感じでしょうか.
参考
本チュートリアルを行うにあたり, 以下のサイトを参考にさせて頂いております. 有難うございました.
relishapp.com
qiita.com