Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

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

概要

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


  • 2018/05/01: 初版公開
  • 2023/11/14: 更新

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が返されると思うかもしれませんが、何か不具合があって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 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.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』をどうぞお役立てください。

関連記事

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

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

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


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。