RSpec によるテスト駆動開発はじめのいっぽ
目次
この記事は?
Why
- 業務上、Rspec を使って、テストを実装する必要があったので調査した記事をとりまとめて備忘録としたい
- テストの実装に苦手意識があるので、克服したい
基本的な使い方
install と init ファイルの生成
rspec を動かす上で必要な、gem install と spec_helper,rb ファイルなどの生成を行う。
$ gem install rspec $ rspec --init create spec/spec_helper.rb create .rspec
サンプルコード
Human クラス のテストを行うコードは以下のように書く。
lib/human.rb
class Human attr_accessor :name, :hands def initialize(name="Kozzy") @name = name @hands = 2 end def alived? true end end
spec/lib/human_spec.rb
require "spec_helper" require "human" describe Human do it "named 'Koji'" do human = Human.new expect(human.name).to eq 'Kozzy' end it "has hands" do human = Human.new expect(human.fangs).to eq 2 end it "is alived" do human = Human.new expect(human).to be_alived end end
$ rspec spec/lib/human_spec.rb
メモ
describe, context, subject, letなどの書き分け
テスト名の宣言
describe
: テストグループ ( クラス名, メソッド名, )context
: テスト条件 ( 変数の違いや前提条件の違い )it
: テスト結果変数宣言について
subject
: テスト対象 = expectの引数になるものlet
: expectの引数になり得ないもの@hoge
: 使わない
before と let の実行タイミング
before
とlet!
- どちらも、ブロックが定義された時に実行される。
let
- ちなみにletは、変数が初めて使用された時だけ遅延評価され、specテストが終わるまでキャッシュとして使える。
before(:each)
を使おう
before(:all)
とbefore(:each)
の違い
before(:all)
はcontext
/describe
ブロックが始まる時に一度だけ実行されるbefore(:each)
は各スペック内のitの前に実行される通常、各スペックは独立してテストしたい。 でも、
before(:all)
を使うとそれができなくなる。
Matcher 一覧
同じ値になるか確認
expect(actual).to eq(expected) # passes if actual == expected expect(actual).to eql(expected) # passes if actual.eql?(expected)
同一のオブジェクトかを確認
expect(actual).to be(expected) # passes if actual.equal?(expected) expect(actual).to equal(expected) # passes if actual.equal?(expected)
大小を確認
expect(actual).to be > expected expect(actual).to be >= expected expect(actual).to be <= expected expect(actual).to be < expected expect(actual).to be_within(delta).of(expected)
正規表現がマッチするか確認
expect(actual).to match(/expression/)
同一クラスか確認
expect(actual).to be_an_instance_of(expected) # passes if actual.class == expected expect(actual).to be_a(expected) # passes if actual.is_a?(expected) expect(actual).to be_an(expected) # an alias for be_a expect(actual).to be_a_kind_of(expected) # another alias
True or False
expect(actual).to be_truthy # passes if actual is truthy (not nil or false) expect(actual).to be true # passes if actual == true expect(actual).to be_falsy # passes if actual is falsy (nil or false) expect(actual).to be false # passes if actual == false expect(actual).to be_nil # passes if actual is nil
例外処理
expect { do_exception }.to raise_error expect { do_exception }.to raise_error(ErrorClass) expect { do_exception }.to raise_error("message") expect { do_exception }.to raise_error(ErrorClass, "message")
例外が吐かれる事を確認
expect { do_exception }.to throw_symbol expect { do_exception }.to throw_symbol(:symbol) expect { do_exception }.to throw_symbol(:symbol, 'value')
yieldの確認
expect { |b| 5.tap(&b) }.to yield_control # passes regardless of yielded args expect { |b| yield_if_true(true, &b) }.to yield_with_no_args # passes only if no args are yielded expect { |b| 5.tap(&b) }.to yield_with_args(5) expect { |b| 5.tap(&b) }.to yield_with_args(Fixnum) expect { |b| "a string".tap(&b) }.to yield_with_args(/str/) expect { |b| [1, 2, 3].each(&b) }.to yield_successive_args(1, 2, 3) expect { |b| { :a => 1, :stuck_out_tongue: => 2 }.each(&b) }.to yield_successive_args([:a, 1], [:b, 2])
値を持っているか確認
expect(actual).to be_xxx # passes if actual.xxx? expect(actual).to have_xxx(:arg) # passes if actual.has_xxx?(:arg)
範囲に含まれている事を確認
expect(1..10).to cover(3)
コレクションを確認
expect(actual).to include(expected) expect(actual).to start_with(expected) expect(actual).to end_with(expected) expect(actual).to contain_exactly(individual, items) # ...which is the same as: expect(actual).to match_array(expected_array)
Rspec のベストプラクティス
Contextsを使う
- Contextsはテストを明らかにし、まとめる素晴らしい方法です。 長い目で見ると、この方法はテストを読みやすくします。
説明を短く
- specの説明は 40文字を超えない ようにしましょう。
単一条件テスト
- 独立したユニットでは、 各例はただ一つの振る舞いだけテストするのが望ましい です。
Subjectを使う
- もしも、同じsubjectに対して複数のテストをしていたら、subject{}を使ってDRYしましょう。
letとlet!を使う
- 変数に値を入れる必要がある時はbeforeブロックの代わりにletを使いましょう。
Shared Examples
- テストを作るのは素晴らしいです。毎日少しづつ自信がつきます。が、結局色んな所にコードの重複が発生します。shared exampleを使ってテストをDRYしましょう。
FactoryGirl について
- FactoryGirlによって、テストデータを定義・生成することができる
継承する
ファクトリをネスト定義することで、親の内容を継承できる。
factory.rb
FactoryGirl.define do factory :user do name "Kozzy" email "kozzy@hoge.com" admin false end end
_spec.rb
# インスタンスを生成 user = create(:user)
最新のものは Factory_bot と名を変えているらしい
shared_example について
- shared_example を用いると、共通部分の括りだしを行うことができる
shared_examples_for 'Some Example' do it 'do something' do # Some Tests here end end describe '#index' do context 'case 1' do it_behaves_like 'Some Example' end context 'case 2' do it_behaves_like 'Some Example' end end
パラメータ渡しもできる
RSpec.shared_examples "some example" do |parameter| let(:something) { parameter } it "uses the given parameter" do expect(something).to eq(parameter) end end RSpec.describe SomeClass do include_examples "some example", "parameter1" include_examples "some example", "parameter2" end
定義した example を呼び出す方法
include_examples "name" # include the examples in the current context it_behaves_like "name" # include the examples in a nested context it_should_behave_like "name" # include the examples in a nested context matching metadata # include the examples in the current context
書籍
ちょっと前に Everyday Rails を買っていた。体系的に学び直すにはやはりこれかな。