最近のテスト事情

むかしに比べると、かなりテストが書けるようになってきたし、TDDもだんだん慣れてきた。最近テスト書いてて便利だと思ったことについてメモっておく。

スタブを使ってbefore_filterをスキップする

describe 'GET index' do
  context 'ログインしている場合' do
    before(:each) do
      controller.any_instances.stub(:authenticate_user).and_return(true)
    end

    it 'hogehogeなfugafugaを取得する' do
      get index, params
      assigns[:fugafuga].should be_hogehoge
    end
  end
end

ログイン判定のような、リクエストをはじく処理をbefore_filterで実装することがよくあるけど、そういうコントローラーをテストする場合、スタブが便利だということにようやく気づいた。スタブによって、メソッドの中身をごまかして好きな値を返すようにできる。だから、before_filterをスキップしたい場合は、とにかくスタブしてtrueを返すようにしとけばいい。skip_before_filterでもスキップすることはできるけど、僕はスタブを使う方が好み。

FactoryGirlを使いこなす

FactoryGirl.define do
  factory :user do
    # 連番を使えばuniquenessのバリデーションにかからなくなる
    sequence(:name) {|n| "user #{n}" }
    sequence(:email) {|n| "user#{n}@example.com" }
    age { rand(18..30) }

    after(:build) do |user|
      # 余計なデータを作るコールバックがあればスキップできる
      User.skip_callback(:after, :create, :create_data)
    end

    # ネストしたfactoryで上書きできる
    factory :naoty do
      name 'naoty'
      email 'naoty@example.com'
      age 18
    end

    # traitで属性のグループに名前をつけられる
    trait :resigned do
      resigned_at { Time.now }
    end
  end
end

user = FactoryGirl.create(:user)
p user.name #=> "user 1"

naoty = FactoryGirl.create(:naoty)
p naoty.name #=> "naoty"

resigned_user = FactoryGirl.create(:user, :resigned)
p resigned_user.name #=> "user 2"
p resigned_user.resigned_at #=> "2013-01-19 00:43:59 +0900"

factory_girlはテスト用のデータを簡単につくるためのgem。似たようなgemは他にもあるけど、こういうgemを使うと、テストデータを作るロジックとテストコードを分離できる。なので、いろんなテストで使われるテストデータを重複なく簡単に作ることができる。

FactoryGirlでテストデータを作成するときに、よくひっかかるのがバリデーションやafter_saveなどのコールバック内の余計な処理だと思う。こういう鬱陶しい処理は、FactoryGirlのコールバックを使ってスキップしてる。

traitは特殊なデータを作る場合にすごく役に立つ。上記の例のような「退会ユーザー」をテストに使いたいときなど、特殊なデータの属性をひとまとめにしてFactoryGirl.create(:user, :resigned)のように簡単に作成できる。

changeマッチャが便利

describe '#resign' do
  let(:user) { FactoryGirl.create(:user) }

  it 'resigned_atを更新する' do
    lambda {
      user.resign
    }.should change(user, :resigned?).from(false).to(true)
  end
end

モデルの更新系のメソッドをテストするとき、changeマッチャが非常に便利。上の例で言うと、user.resigned?の結果がlambda内の処理を実行した前後でfalseからtrueに変わることをテストしている。