Rails tips: RSpecテストを遅くする悪い書き方3種(翻訳)
テストが遅くなる原因はさまざまで、コードに関係するものもあればそうでないものもあります。今回は、specを高速化して改善するちょっとした変更のコツをご紹介します。
🔗 1. true
かfalse
だけを期待する場合はbe_truthy
やbe_falsey
を避ける
まずは以下のコードをご覧ください。
expect(true).to be_truthy
expect(1).to be_truthy
expect('string').to be_truthy
expect(nil).to be_falsey
expect(false).to be_falsey
どのexpectationもパスするので、false
やtrue
が返されると思うかもしれませんが、何か不具合があってfalse
とtrue
以外のものが返されると、テストでキャッチできなくなってしまう可能性があります。
解決方法
値そのものを指定します。
expect(true).to eq(true)
expect(1).to eq(true)
expect('string').to eq(true)
expect(false)to eq(false)
expect(nil).to eq(false)
🔗 2. FactoryBot.build
は避ける
訳注
原文のFactoryGirlはFactoryBotに置き換えました。
FactoryBot.build
を呼び出してもデータベースにレコードは作成されないと思うかもしれませんが、とにかくUse Factory Bot's build_stubbed for a Faster Test Suiteをご覧ください。ファクトリー内で関連付けが宣言されていると、レコードが作成されてしまいます。たとえば次のようなファクトリーがあるとしましょう。
FactoryBot.define do
factory :user do
contact
company
end
end
そしてFactoryBot.build :user
を呼び出すと、データベースにレコードが2件作成されます。テストの冒頭でファクトリーを初期化してexampleを10件実行すれば、レコードが20件も作成されます。このレコードが不要であれば、改善の余地が大いにあります。
解決方法
FactoryBotl.build_stubbed
を使うことです。こちらはデータベースにレコードを作成することはありません。
🔗 3. Model.new
をスタブ代わりにするのは避ける
以下の例で考えてみましょう。シンプルなSampleClass
クラスとSampleApi
クラスがあります。
class SampleApi
def login; end
end
class SampleClass
def call
api.login
end
private
def api
SampleApi.new
end
end
そしてSampleClass#call
メソッドをテストしたいとします。
require 'spec_helper'
describe SampleClass do
describe '#call' do
it 'calls API' do
api = SampleApi.new
allow(SampleApi).to receive(:new).and_return(api)
allow(api).to receive(:login)
sample_class = SampleClass.new
sample_class.call
expect(api).to have_received(:login).once
end
end
end
ここまではよさそうに見えます。次はSampleApi
に新しいメソッドを追加して、それを#login
メソッドより前に呼び出したいとします。やってみましょう。
class SampleApi
def login;end
def before_login_action;end
end
class SampleClass
def call
api.before_login_action
api.login
end
private
def api
@api ||= SampleApi.new
end
end
テストを実行してみると、greenのまま変わりません。クラスの実装を変更したのにこうなったというのは悪い知らせです。スタブ化されてないインスタンスを使うと、実行されたメソッドに対する制御が失われてしまいます。
この種の問題を洗い出すのは大変で、テスト駆動開発(TDD)アプローチを行わない開発者がメソッドを更新した場合は特にそうです。
解決方法
代わりにinstance_double
を使います。api = SampleApi.new
をapi = instance_double(SampleApi, login: double)
に置き換えれば、ちゃんとエラーが出力されます。
Double "SampleApi (instance)" received unexpected message :before_login_action with (no args)
この解決法は元の書き方とくらべても決して遅くはなく、しかも完全にコントロール可能です。メソッドがn
回実行されることを期待するメソッドに対してinstance_double
を組み合わせれば、期待どおりの結果が得られます。
お知らせ: RSpec & TDDの電子書籍を無料でダウンロード
もっと稼ぎたい方や会社をさらに発展させたい方へ: テスティングのスキルの重要性にお気づきでしょうか?テストを正しく書き始めることが、唯一のファーストステップです。無料でダウンロードいただける私の書籍『RSpec & Test Driven Developmentの無料ebook』をどうぞお役立てください。
概要
原著者の許諾を得て翻訳・公開いたします。