はじめに
この記事は書籍「プロを目指す人のためのRuby入門」のテストコードを、筆者自らRSpecに書き換える連載記事の第2回です。
第1回を読んでいない方は、先に第1回を読んでからこの記事に戻ってきてください。
今回は第7章と第8章のテストコードをRSpecに書き換えていきます。
動画とソースコード
この記事の内容は動画(スクリーンキャスト)形式で解説しています。
細かい説明は口頭で話しているので、ぜひ動画もチェックしてください。
(録音状況が悪いため、ときどき大きなノイズが入ります。ごめんなさい🙏)
「プロを目指す人のためのRuby入門」のテストコードをRSpecに書き換える・その2
また、ソースコードはこちらにアップしています。
それでは以下が本編です。
第7章 改札機プログラム
Minitest版
test/gate_test.rb
require 'minitest/autorun'
require './lib/gate'
require './lib/ticket'
class GateTest < Minitest::Test
def setup
@umeda = Gate.new(:umeda)
@juso = Gate.new(:juso)
@mikuni = Gate.new(:mikuni)
end
def test_umeda_to_juso
ticket = Ticket.new(150)
@umeda.enter(ticket)
assert @juso.exit(ticket)
end
def test_umeda_to_mikuni_when_fare_is_not_enough
ticket = Ticket.new(150)
@umeda.enter(ticket)
refute @mikuni.exit(ticket)
end
def test_umeda_to_mikuni_when_fare_is_enough
ticket = Ticket.new(190)
@umeda.enter(ticket)
assert @mikuni.exit(ticket)
end
def test_juso_to_mikuni
ticket = Ticket.new(150)
@juso.enter(ticket)
assert @mikuni.exit(ticket)
end
end
RSpec版(letを使わない場合)
spec/gate_spec.rb
require './spec/spec_helper'
require './lib/gate'
require './lib/ticket'
RSpec.describe 'Gate' do
before do
@umeda = Gate.new(:umeda)
@juso = Gate.new(:juso)
@mikuni = Gate.new(:mikuni)
end
describe 'Umeda to Juso' do
it 'is OK' do
ticket = Ticket.new(150)
@umeda.enter(ticket)
expect(@juso.exit(ticket)).to be_truthy
end
end
describe 'Umeda to Mikuni' do
context 'fare is not enough' do
it 'is NG' do
ticket = Ticket.new(150)
@umeda.enter(ticket)
expect(@mikuni.exit(ticket)).to be_falsey
end
end
context 'fare is enough' do
it 'is OK' do
ticket = Ticket.new(190)
@umeda.enter(ticket)
expect(@mikuni.exit(ticket)).to be_truthy
end
end
end
describe 'Juso to Mikuni' do
it 'is OK' do
ticket = Ticket.new(150)
@juso.enter(ticket)
expect(@mikuni.exit(ticket)).to be_truthy
end
end
end
ポイント
- Minitestの
setup
に相当する処理は、RSpecではbefore
ブロックに記述します。 - このテストコードでは
describe
を使って「どの駅からどの駅まで乗るのか」をグループ分けしています。(例:describe 'Umeda to Juso' do
) - RSpecでは
describe
だけでなく、context
を使ってグループ化することができます。両者の役割はまったく同じなのですが、context
は「テストの条件」を表す場合によく使われます。 - ここでは
context
を使って、「運賃が足りない場合(context 'fare is not enough' do
)」と「足りる場合(context 'fare is enough' do
)」をグループ分けしています。 - 「検証する値が真か偽か」を確認する場合は、
expect(A).to be_truthy
やexpect(A).to be_falsey
という形式で検証します。 -
expect(A).to eq true
やexpect(A).to be true
のような形で検証することもできますが、この場合は検証する値が必ず「true
そのものであること」が要求されます(つまり、1
や"abc"
である場合はテストが失敗します)。 - RSpecにおける真偽値の検証方法については以下の記事も参照してください。
参考:使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」 - Qiita
RSpec版(letを使う場合)
spec/gate_spec.rb
require './spec/spec_helper'
require './lib/gate'
require './lib/ticket'
RSpec.describe 'Gate' do
let(:umeda) { Gate.new(:umeda) }
let(:juso) { Gate.new(:juso) }
let(:mikuni) { Gate.new(:mikuni) }
describe 'Umeda to Juso' do
it 'is OK' do
ticket = Ticket.new(150)
umeda.enter(ticket)
expect(juso.exit(ticket)).to be_truthy
end
end
describe 'Umeda to Mikuni' do
context 'fare is not enough' do
it 'is NG' do
ticket = Ticket.new(150)
umeda.enter(ticket)
expect(mikuni.exit(ticket)).to be_falsey
end
end
context 'fare is enough' do
it 'is OK' do
ticket = Ticket.new(190)
umeda.enter(ticket)
expect(mikuni.exit(ticket)).to be_truthy
end
end
end
describe 'Juso to Mikuni' do
it 'is OK' do
ticket = Ticket.new(150)
juso.enter(ticket)
expect(mikuni.exit(ticket)).to be_truthy
end
end
end
ポイント
- 第1回の記事でも説明したとおり、テストコード内に登場するローカル変数やインスタンス変数は
let
を使って置き換えることができます。 - ここでは
@umeda
、@juso
、@mikuni
という3つのインスタンス変数を、それぞれlet
に置き換えました。 - インスタンス変数の代わりに
let
を使うと、typo(タイプミス)に気づきやすくなるというメリットがあります。
参考: RSpecのletを使うのはどんなときか?(翻訳) - Qiita
第8章 deep_freezeメソッド
Minitest版
test/deep_freezable_test.rb
require 'minitest/autorun'
require './lib/bank'
require './lib/team'
class DeepFreezableTest < Minitest::Test
def test_deep_freeze_to_array
# 配列の値は正しいか?
assert_equal ['Japan', 'US', 'India'], Team::COUNTRIES
# 配列自身がfreezeされているか?
assert Team::COUNTRIES.frozen?
# 配列の要素がすべてfreezeされているか?
assert Team::COUNTRIES.all? { |country| country.frozen? }
end
def test_deep_freeze_to_hash
# ハッシュの値は正しいか?
assert_equal(
{ 'Japan' => 'yen', 'US' => 'dollar', 'India' => 'rupee' },
Bank::CURRENCIES
)
# ハッシュ自身がfreezeされているか?
assert Bank::CURRENCIES.frozen?
# ハッシュの要素(キーと値)がすべてfreezeされているか?
assert Bank::CURRENCIES.all? { |key, value| key.frozen? && value.frozen? }
end
end
RSpec版
spec/deep_freezable_spec.rb
require './spec/spec_helper'
require './lib/bank'
require './lib/team'
RSpec.describe 'Deep freezable' do
describe 'to array' do
it 'freezes deeply' do
# 配列の値は正しいか?
expect(Team::COUNTRIES).to eq ['Japan', 'US', 'India']
# 配列自身がfreezeされているか?
expect(Team::COUNTRIES).to be_frozen
# 配列の要素がすべてfreezeされているか?
expect(Team::COUNTRIES.all? { |country| country.frozen? }).to be_truthy
end
end
describe 'to hash' do
it 'freezes deeply' do
# ハッシュの値は正しいか?
expect(Bank::CURRENCIES).to eq({ 'Japan' => 'yen', 'US' => 'dollar', 'India' => 'rupee' })
# ハッシュ自身がfreezeされているか?
expect(Bank::CURRENCIES).to be_frozen
# ハッシュの要素(キーと値)がすべてfreezeされているか?
expect(Bank::CURRENCIES.all? { |key, value| key.frozen? && value.frozen? }).to be_truthy
end
end
end
ポイント
- RSpecにはpredicateマッチャという機能があります。これは
frozen?
のような?
で終わるメソッドを、be_frozen
のような形で検証できる機能です。 - 具体的にいうと、以下の2つのテストコードはまったく同じ内容を検証しています。
# predicateマッチャを使わない場合
expect(Team::COUNTRIES.frozen?).to be_truthy
# predicateマッチャを使う場合
expect(Team::COUNTRIES).to be_frozen
まとめ
この記事では「プロを目指す人のためのRuby入門」の第7章と第8章までのテストコードをRSpecに書き換えました。
第10章(注:第9章はテストコードがありません)のテストコードは第3回の記事で書き換えていきます。