[Rails] RSpecのモックとスタブの使い方

こんにちは、hachi8833です。

なかなかRSpecをうまく書けずに悩んでましたが、morimorihogeさんに教えてもらって、RSpecのモックとスタブの使い分け方が少し見えてきたのでメモします。

スタブの目的

そもそもスタブを使用する目的は、本当にテストしたいところだけをテストできるように、本筋に関係ない部分(バリデーションなど)をとにかく動くようにする、動かしたい場所まで辿り着くことです。例えて言うなら、ペンキを塗ったりエアブラシを使うときに、塗料が付いて欲しくない部分に貼るマスキングテープをイメージするとよいでしょう。テストしたい箇所以外はスタブで塞いでしまうわけです(以下の4行目)。

#vtypes_controller_spec
describe "POST create" do
  it "create_from_text が呼ばれ,client_vtypes_path を再表示すること" do
    Vtype.stub(:type_string).and_return(10)      # ここがスタブ
    post :create, text:"data_text", vtype:"10"   # テストしたいアクション
    response.should redirect_to(client_vtypes_path(filtered_vtype:"10")) # アクションの結果をテスト
  end
end

上の4行目では、下のモデルにあるtype_stringメソッドを偽って、type_stringが10という値を返したということにしてくれています。
以下はVtypeモデルです。

# Vtypeモデル
# name:: 種別 - 日本語 (string),null 不可
# sort_order:: 並び順カラム (integer),null 不可
class Vtype < ActiveRecord::Base
  def self.type_string(num)
    return self.find_by_sort_order(num).name
  end
end

なお以下はコントローラのcreateアクションの抜粋です。

#vtypes コントローラ
def create
  @vtype  = params[:filtered_vtype]
  if @vtype.save
    flash[:notice] = "#{@v.name}を作成しました"
    redirect_to client_vtypes_path(filtered_vtype: @vtype) and return
  else
    @vtype = params[:filtered_vtype]
    render action:'new'
  end
end

スタブのメリットは、オブジェクトやメソッドのふりをするだけなので、データを用意するフィクスチャーやファクトリーよりも高速である点です。また、本筋に関係ない部分が未実装であっても、スタブを使用できます。完全にシステムをだますことができるわけです。

このことからわかるように、「スタブで塞いだ箇所はテストされたことになりません」。スタブで塞いだ部分は、結合テストなど他のどこかの箇所で最終的にテストされていなければなりません。極端に言えば、テストがスタブだけですべて固められてしまっていればそもそもテストの意味がありません。

従って、スタブを多用し過ぎると、その部分を他でテストし忘れていた場合、「テストは通るのに本番でエラーになる」ということになりかねません。

Railsの場合、スタブを使う場所は基本的には単体テスト、主にコントローラになると思います。コントローラのテストは「不要である」と言われることが多く、初心者はコントローラのテストを作るべきかどうか迷いがちですが、結合テストや受け入れテスト(features、requests、acceptanceなど)が別途行われるのであれば、コントローラではアクションが動作することだけ確認すればよいと思います。実際、scaffoldで生成されるコントローラspecはアクションが動くことを確認しているだけです。

モックの目的

モックはスタブと似た面がありますが、モックはそれ自体がテストになり得る点がスタブとは決定的に異なります。これは、テストする対象のオブジェクトが実装されてないとモックは使えないからです。テスト対象オブジェクトがなくても動くスタブとはその点が違います。

実は、モックはその内部でスタブをこっそり使っています。モックを実行すると、最初にスタブを内部で作成してから動かし、次に実際のオブジェクトに同じデータを与えて動かし、両者の結果が同じかどうかを比較します。結果が合えばパス、違えば失敗となるわけです。

describe "POST create" do
  before :each do
    mock_budget.should_receive(:budget_main).and_return(mock_budget_main)
    mock_budget.should_receive(:budget_owner).and_return(mock_budget_owner)
    mock_budget.should_receive(:budget_unit).and_return(mock_budget_unit)
  end

  it "redirects to new_contract_path when creation of contract succeeds" do
    mock_budget.should_receive(:save).and_return(true)
    post :create, budget:{these: :params}
    response.should redirect_to(new_contract_path(budget_id:mock_budget.id))
  end
end

従って、モックはスタブと同様のマスキングテープ的な使い方もできますし、それ自体をテストコードにすることもできます。上のコードの3, 4, 5行目はbeforeの中でスタブ的に使っていますし、9行目ではテストとして書かれています。

おまけ

モックとスタブはてっきりRSpec固有の機能かと思ったら、元々テスティング一般で使用される概念だったんですね。

今回参考にした「Ruby on Railsでのモックとスタブの作成」にさらっと『もしかすると私達は、これらの概念の表現としては不適切な言葉を選んでしまったのかもしれません。しかし今や、これらの言葉を使わざるをえません。』と書かれていることからわかるように、モックとスタブという言葉は割りと混乱を呼んでいるように思えます。

モックとスタブについてこれまでいろんな説明がなされていますが、どれもどうもしっくりこなかったので、自分にとってのまとめとしてこの記事を書きました。

Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833 コボラー、ITコンサル、ローカライズ業界を経てなぜかWeb開発者志願。 これまでにRuby on Rails チュートリアルの大半、Railsガイドのほぼすべてを翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

Rubyスタイルガイドを読む

BigBinary記事より

ActiveSupport探訪シリーズ