おいちゃんと呼ばれています

ウェブ技術や日々考えたことなどを綴っていきます

Rails から Elasticsearch を使っているときのテストの書き方(elasticsearch-rails, RSpec)

f:id:inouetakuya:20141103002159p:plain

みんな大好き babyshark では全文検索に Elasticsearch を使っています。テストの書き方について日本語情報が少ないようなので、僕はこういう感じで書いてますというのを共有します。

環境は下記のとおりです。

  • Elasticserch 1.3.1
  • analysis-kuromoji(ブラグイン)2.3.0

gem

searchable.rb

まず、先に Elasticsearch へ検索クエリをどうやって投げているのかを簡単に擬似コードで説明すると下記のような感じです。

ActiveSupport::Concern を使って、Video モデルに search というクラスメソッドを生やしています。

# app/models/concerns/searchable.rb

module Searchable
  extend ActiveSupport::Concern

  included do
    include Elasticsearch::Model

    # 環境ごとにインデックス名を別にする
    index_name [Rails.application.engine_name, Rails.env].join('_')

    # ...
  end

  module ClassMethods
    def search(query, sort: nil, ...)
      search_definition = {
        query: {
          # ...
        }
      }

      __elasticsearch__.search(search_definition)
    end
  end
end

spec_helper.rb

spec_helper.rb について、ポイントは 2つです。

  • 各テストを独立させるために、毎回検索インデックスをクリアする
  • 検索インデックスのクリアは Elasticsearch に関するテストに限定して実行する
# spec_helper.rb

RSpec.configure do |config|
  config.before :each, elasticsearch: true do
    Video.__elasticsearch__.create_index!(force: true)
  end

  config.include FactoryGirl::Syntax::Methods

  # ...
end

seachable_spec.rb

テストは下記のように書いています。

  • テストデータ登録後、検索インデックスへの反映を待つ(データが insert されるとコールバックで Elasticsearch に POST するようにしている)
  • 検索インデックスのクリアを実行するために elasticsearch: true を付ける
# spec/models/concerns/searchable_spec.rb

require 'spec_helper'

describe Searchable, elasticsearch: true do

  describe 'self.search' do
    let!(:video1) {
      create(:video,
        title: 'みつばちの悪戯 及川奈央',
        tags:  [
                 create(:tag, name: '色っぽい'),
                 create(:tag, name: '巨乳'),
               ],
        views_count: 100,
      )
    }

    let!(:video2) {
      create(:video,
        title: '痴漢バスおとり捜査官 麻美ゆま',
        tags:  [create(:tag, name: '凌辱')],
        views_count: 200,
      )
    }

    before do
      # Elasticsearch のインデックスに反映されるのを待つ
      sleep 1
    end

    context '検索キーワードが動画タイトルにマッチするとき' do
      let(:videos) { Video.search('悪戯').records }

      it '検索キーワードにマッチする動画が検索結果に含まれる' do
        expect(videos.include?(video1)).to be_truthy
        expect(videos.include?(video2)).to be_falsey
      end
    end

    context '検索キーワードがタグ名にマッチするとき' do
      let(:videos) { Video.search('色っぽい').records }

      it '検索キーワードにマッチする動画が検索結果に含まれる' do
        expect(videos.include?(video1)).to be_truthy
        expect(videos.include?(video2)).to be_falsey
      end
    end

    context '並び順を指定したとき' do
      let(:videos) { Video.search('', sort: 'views_count.desc').records }

      it '指定された並び順で検索結果が返ってくる' do
        expect(videos[0]).to eq video2
        expect(videos[1]).to eq video1
      end
    end
    
    # ...
  end
end

まあそうだよねという感じですが、こういうのが延々続いています。

context '検索キーワードがタグ名の一部にマッチするとき'
context '検索キーワードが 2つで、いずれも動画タイトルにマッチするとき'
context '検索キーワードが 2つで、ひとつだけ動画タイトルにマッチするとき'
context '検索キーワードが 2つで、ひとつが動画タイトル、ひとつがタグ名にマッチするとき'
context '検索キーワードが 2つで、全角スペースで区切られているとき'
context '検索キーワードが 3つのとき'
context '検索キーワードが空白のとき'
# ...

以上です。ではでは。

PR

ちなみに人妻動画サイトの h300 のほうでも Elasticsearch を使っています。

f:id:inouetakuya:20120416230147j:plain:w300

参考サイト

このエントリーのつづき