【Rails】検索機能のモデル単体テストの手順を詳しく解説

「【Rails】検索機能のモデル単体テストの手順を詳しく解説」のアイキャッチ画像

本記事では、検索機能のモデル単体テストの実施手順を、詳しく解説します。LIKE句を利用した「あいまい検索」の実装方法はこちらをご覧ください。


成功画面


まず、以下が本テスト成功の結果コードです。
テストは様々な検証方法がありますので、こちらはあくまで一例です。本記事ではこの結果を目指し、準備を進めていきます。

# テスト実行(ファイル指定)
bundle exec rspec spec/models/item_spec.rb

# テスト結果
Item
  #search
    一致するデータが存在する場合
      検索文字列に完全一致する配列を返すこと
      検索文字列に部分一致する配列を返すこと
      updated_at降順で配列を返すこと
      price降順で配列を返すこと
      価格が15,000円〜25,000円に含まれるのitemが1件抽出されること
    一致するデータが存在しない場合
      検索文字列が一致しない場合、空の配列を返すこと
      検索文字列が空白の場合、すべての配列を返すこと

Finished in 1.79 seconds (files took 5.44 seconds to load)
7 examples, 0 failures

単体テスト実施の流れ


検索機能の単体テストは、大まかに以下の流れで実施します。

①事前準備
②テスト内容の設計
③テストファイルの作成
④テスト実施/検証


実施手順

⓪はじめに


・テストで使用する言語について
Ruby on Railsでは、RSpecという独自の言語を利用してテストを行います。
そしてRspecは、「rspec-rails」というgemのインストールすることで使用することができます。


・ダミーデータの作成について
RSpecを使用したテストを行う場合、FactoryBotを使って、簡単にダミーデータ(モデルインスタンス)を生成することができます。こちらもRSpec同様、「factory_bot_rails」というgemのインストールし、使用します。


①事前準備


ここでは「rspec-rails」「factory_bot_rails」の準備を、以下手順で行います。

a) RSpecの導入
b) RSpecの設定
c) RSpecの利用に向けた事前検証
d) factory_botの導入
e) factory_botでダミーデータ(インスタンス)生成

それでは実際にこれらの準備に進みましょう。



a) RSpecの導入


まず以下のように、「rspec-rails」「web-console」というgemを追記します。

※「web_console」はtest環境で動かすと不具合が起きる可能性があるため、develop環境のみに記述します。

group :development, :test do
  gem 'rspec-rails'
end

group :development do
  gem 'web-console'
end

編集後、bundle installをします。

$ bundle install


b) RSpecの設定


次にRSpecの基本設定として、RSpec用の設定ファイルを作成します。
以下を実行すると、create以下のファイルが作成されます。

$ rails g rspec:install

    create  .rspec
    create  spec
    create  spec/spec_helper.rb
    create  spec/rails_helper.rb

「rails_helper.rb」「spec_helper.rb」について

・rails_helper.rb
テスト共通の設定、メソッドなどを記述するファイルです。ファイルを読み込むことで設定内容を適用します。

・spec_helper.rb
rails_helperと同様の役割を果たすファイルですが、こちらはRSpecをRails無しで使う場合に利用します。


RSpecの設定は以上です。
次に、RSpecが正常に機能するかの確認を行います。



c) RSpecの利用に向けた事前検証


事前検証は以下のコマンドを実行して、No example以下の結果となれば、準備完了です。

$ bundle exec rspec

    No examples found.

    Finished in 0.00106 seconds (files took 1.12 seconds to load)
    0 examples, 0 failures


d) factory_botの導入


RSpecの事前検証を終えたら、ダミーデータを生成するfactory_botを導入します。
これは先ほどのgemfile内、RSpecと同じグループに追記、bundle installをして完了です。

group :development, :test do
  gem 'rspec-rails'
  gem 'factory_bot_rails'
end


e) factory_botでダミーデータ(インスタンス)生成


続いて、以下のように「factories」ディレクトリを作成し、その中に「items.rb」(作成したインスタンスの複数形のファイル名)という名前のファイルを作成します。


 app
    spec
    models
      specファイル
    controllers
      specファイル
    factories
      items.rb ⇦ 作成ファイル


このitems.rbに、ダミーデータを作成します。
この準備によって、specファイル(テストコード記述ファイル)から指定のメソッドにより、ダミーデータを元にインスタンスの生成や、DB保存が可能になります。

※カラムはご自身のDBに合わせてください。
※値は適当で構いません。


「factory :item2, class: Item do」について

FactoryBotのデータは、主に以下2種類の方法で設定が可能です。

①モデル名をそのまま定義する場合

factory :item do
“設定内容”
end

②モデル名以外の名前を定義する場合

factory :“任意の名前”, class: “モデル名” do
“設定内容”
end

今回3種類のダミーデータを作成していますが、1つ目では①を、2,3つ目では②の方法で定義しています。これは、それぞれ別のデータとして明示する必要があるためです。


事前準備は以上です。少し長いですが、やっていることはシンプルですね。
それではテスト内容の設計に進みましょう。



②テスト内容の設計


テスト内容には、基本的に、期待する結果と期待しない結果の両方を盛り込みます。
その際、テストしたい機能の内容を踏まえて、パターンを洗い出していくと整理しやすいかと思います。

今回はざっくりですが、以下の図のように洗い出しました。



並び替え検索の「XXの昇順」は、例えば「商品を価格が昇順で並び替え検索」。範囲検索の「XXがA〜Bに含魔れる」は、「価格が1,000〜5,000円の商品を範囲検索」、などが挙げられます。

それでは以上で洗い出したテスト内容を、対象のspecファイルに記述していきます。



③テストファイルの作成


今回はモデルの単体テストなので、テストファイルを spec/models/ に「item_spec.rb」の名前で作成します。


 app
    spec
    models
      item_spec.rb ⇦ 作成ファイル
    controllers
      specファイル
    factories
      items.rb


そして先ほど洗い出した条件をもとに、以下のようにテスト処理を記述します。


require 'rails_helper'
  describe '#search' do

    context "一致するデータが存在する場合" do
      # filter
      it "検索文字列に完全一致する配列を返すこと" do
        item = create(:item)
        # "test-name1"と一致するデータがitemに存在することを期待する
        expect(Item.search("test-name1")).to include(item)
      end
      it "検索文字列に部分一致する配列を返すこと" do
        item = create(:item)
        # "t"を含むデータがitemに存在することを期待する
        expect(Item.search("t")).to include(item)
      end
      # sort
      it "updated_at降順で配列を返すこと" do
        create(:item)
        create(:item2)
        create(:item3)
        # 更新日時の古いid順は「2,3,1」であることを期待する
        expect(Item.order("updated_at DESC").map(&:id)).to eq [2,3,1]
      end
      it "price降順で配列を返すこと" do
        create(:item)
        create(:item2)
        create(:item3)
        # 価格の高いid順は「3,1,2」であることを期待する
        expect(Item.order("price DESC").map(&:id)).to eq [3,1,2]
      end
      # between
      it "価格が15,000円〜25,000円に含まれるのitemが1件抽出されること" do
        create(:item)
        create(:item2)
        create(:item3)
        items = Item.where(price: 15000..25000)
        # 価格が15000~25000に含まれるデータ「1件」であることを期待する
        expect(items.count).to eq 1
      end
    end

    context "一致するデータが存在しない場合" do
      it "検索文字列が一致しない場合、空の配列を返すこと" do
        item = create(:item)
        # "あああ"と一致するデータがitemに存在しないことを期待する
        expect(Item.search("あああ")).to be_empty
      end

      it "検索文字列が空白の場合、すべての配列を返すこと" do
        item = create(:item)
        # 検索文字列が空の場合、すべてのデータが返されることを期待する
        expect(Item.search("")).to include(item)
      end
    end

  end
end

「create」「build」について

FactoryBotでは、インスタンス作成(テストデータをメモリ上に保存)のみ行うbuildメソッドと、DB保存まで行うcreateメソッドがあります。
今回は、DBからデータを探す検索機能であるため、createメソッドを使用します。



④テスト実施/検証


テストコードの記述を終えたら、テストを実行します。そしてItem以下の結果となれば、無事テスト成功です。


$ bundle exec rspec spec/models/item_spec.rb

Item
  #search
    一致するデータが存在する場合
      検索文字列に完全一致する配列を返すこと
      検索文字列に部分一致する配列を返すこと
      updated_at降順で配列を返すこと
      price降順で配列を返すこと
      価格が15,000円〜25,000円に含まれるのitemが1件抽出されること
    一致するデータが存在しない場合
      検索文字列が一致しない場合、空の配列を返すこと
      検索文字列が空白の場合、すべての配列を返すこと

Finished in 1.79 seconds (files took 5.44 seconds to load)
7 examples, 0 failures


おわりに


検索機能のモデル単体テストの実施手順は以上になります。

中でも設計部分について、どんなテストを実行すれば良いか分からなくなった場合は、まず「期待する結果と期待しない結果の両方」を紙などに洗い出すと良いかと思います。

また検索機能の実装自体は、LIKE句を使用した方法はこちらをご参照ください。高度な検索機能を実装をできる「ransack」というgemを使用した実装方法はこちらにまとめていますので、よろしければご参照ください。