テストダブル(スタブ、モック、スパイ)とサンプルコードについてまとめます。 この記事は既出情報です。個人メモ的なものなのであまり役にたたないと思います。
テストダブル(Test Double)とは?
テストダブルはテストの代役である。
テスト用に本物ではない代役のオブジェクトを利用する。
以下にテストダブルの種類をあげます。
テストスタブ(Test Stub)とは?
テストスタブとはテストの呼び出しに対してあらかじめ用意された結果を返す手法である。
そのままではテスト時に取扱いにくい内容をStub out(もみ消す)ために利用される。
DBや外部サービスとの連携などが典型的な例。
状態をテストする。
スタブを利用する側がテストをしたい場所になる。
モックオブジェクト(Mock Object)とは?
モックオブジェクトとは期待値の一連の呼び出し仕様を表した手法である。
振る舞いをテストする。
モックの利用箇所そのものがテストになる。
テストスパイ(Test Spy)とは?
スパイは間接的な出力を検証するために、出力を記録しておく手法である。
記録を保持するが検証は行わない点がモックと異なる。
フェイクオブジェクト(Fake Object)とは?
フェイクオブジェクトとは実物よりも単純な実装を使う手法である。
DBに対するインメモリデータベースが典型例。
ダミーオブジェクト(Dummy Object)とは?
利用しないパラメータに対して数合わせのためなどに受け渡されるオブジェクト。
動きさえすれば値はなんでもいい。
相互の関係
- モックオブジェクトはスタブの機能を含む
- スパイはスタブの機能を含む
- スタブはその他は状態の検証を行う。
- モックオブジェクトはテストダブルの中で唯一振る舞いの検証を行う
スタブ・モック・スパイのサンプルコード
RSpec のサンプルコードです
テスト対象
require 'sample/version' require 'pp' class FizzBuzz def fizzbuzz(limit, printer) printer.start ret = init_range.init(limit).each_with_object([]) do |e, memo| printer.loop memo << case when e % 15 == 0 then 'FizzBuzz' when e % 5 == 0 then 'Buzz' when e % 3 == 0 then 'Fizz' else e.to_s end end.join(',') printer.end ret end def init_range Limit end end class Limit def self.init(limit) (1..limit) end end class Printer def start print "start" end def loop print "loop" end def end print "end" end end
テストコード
require 'spec_helper' require 'sample' describe FizzBuzz do it 'no test double' do expect(subject.fizzbuzz(15, Printer.new)).to eq('1,2,Fizz,4,Buzz,Fizz,7,8,Fizz,Buzz,11,Fizz,13,14,FizzBuzz') end it 'use test stub' do # ループ範囲に利用している Range をスタブに差し替えてみます limit5_mock = double("Limit 5") allow(limit5_mock).to receive(:init).and_return((1..5)) allow(subject).to receive(:init_range).and_return(limit5_mock) expect(subject.fizzbuzz(15, Printer.new)).to eq('1,2,Fizz,4,Buzz') end it 'use mock object' do # ループ範囲に利用している Range をモックに差し替えてみます limit3_mock = double("Limit 3") # initが引数 15 で呼び出されていることを確認しつつ (1..3) を返却します expect(limit3_mock).to receive(:init).with(15).and_return((1..3)) expect(subject).to receive(:init_range).and_return(limit3_mock) expect(subject.fizzbuzz(15, Printer.new)).to eq('1,2,Fizz') end it 'use spy object' do # 処理開始時、処理中、処理終了時にテキストを出力する機能をスパイに置き換えます printer = spy("Printer") expect(subject.fizzbuzz(3, printer)).to eq('1,2,Fizz') # 処理開始時に呼び出されていることを確認 expect(printer).to have_received(:start) # 処理中に3回呼び出されていることを確認 expect(printer).to have_received(:loop).exactly(3).times # 処理終了時に呼び出されていることを確認 expect(printer).to have_received(:end) end end