Rails tips: RSpecテストを遅くする悪い書き方3種(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

Rails tips: RSpecテストを遅くする悪い書き方3種(翻訳)

テストが遅くなる原因はそれはもうさまざまで、コードに関係するものもあればそうでないものもあります。今回は、specを高速化して改善するちょっとした変更のコツをご紹介します。

1. truefalseを常に期待する場合はbe_truthybe_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もパスしますので、falsetrueが返っているとお考えになるかもしれませんが、何か不具合があってこれ以外のものが返されると、テストでキャッチできなくなってしまう可能性があります。

解決方法

値そのものを指定します。

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 Girl’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.newapi = 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』をどうぞお役立てください。

関連記事

Rails: テスティングアンチパターン –前編(翻訳)

Rails: テスティングアンチパターン –後編(翻訳)

TestProf: Ruby/Railsの遅いテストを診断するgem(翻訳)

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好き。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

ActiveSupport探訪シリーズ