昇順確認 shared_example: 小林研 Rails Tips (10)

はじめに

Rails Tips の 10 回目です。連日の model のテストを簡単に書くための shared_example の紹介の続きです。しばらくは私の Shared example の雛形 (lib/templates/rspec/model/model_spec.rb)を一つずつ解説していく形です。昨日で属性に関するテストが一通り終わったので、次の scope やクラスメソッドに関するブロックです。今日からまた一つずつ紹介していきます。

#  describe "<%= class_name %> クラスについて" do
#    context "order_sort_order" do
#      subject { described_class.order_sort_order }
#      it_behaves_like "昇順確認", 0, ->(o) { o.sort_order }
#    end

#    context "order_bar_desc" do
#      subject { described_class.order_bar_desc }
#      it_behaves_like "降順確認", 99999, ->(o) { o.bar }
#    end
#
#    context "Class methods" do
#      subject { described_class }
#
#      it_behaves_like "単一メソッド呼び出し" do
#        let(:test_set) do
#          {
#              foo: [nil, [true, false, true]],
#          }
#        end
#      end
#    end

#    describe "get_or_create method" do
#      subject { -> { described_class.get_or_create(*params) }}
#      context '既存のものを取得' do
#        let(:params) { [object.xxx, object.yyy] }
#        it_behaves_like "オブジェクト数が変化しない?", <%= class_name %>
#      end
#
#      context '新規作成' do
#        let(:params) { [object.xxx, other.yyy] }
#        it_behaves_like "オブジェクトが1増えるか?", <%= class_name %>
#      end
#    end
#  end

昇順確認

今日は「昇順確認」になります。データの並び替えに関するテストになります。キャンパスは sort_order という整数型の属性があり、並び順が記録されています。この sort_order で並び替えをする order_sort_order という scope のテストが昇順確認になります。最初の属性は最小値、二つ目の属性はモデルデータから確認したい属性値を作成するための Proc を渡します。

  describe "Campus クラスについて" do
    context "order_sort_order" do
      subject { described_class.order_sort_order }
      it_behaves_like "昇順確認", 0, ->(o) { o.sort_order }
    end

学生モデルは並び替えの宝庫です。例えば、ひらがなの読みから並び替え計算用の文字列を作成した yomi_suuji 属性があります。この yomi_suuji で並び替えを実施する order_yomi という scope のテストは以下のようになります。yomi_suuji が文字列なので、初期値が "" になっています。

    context "order_yomi" do
      subject { described_class.order_yomi }
      before { described_class.all.map(&:save) }
      it_behaves_like "昇順確認", "", ->(o) { o.yomi_suuji }
    end

他の型も同様で、学生の誕生日は Date 型で保持されています。こちらもどの値よりも過去の日付が初期値として設定されればいいことになります。

    context "order_birthday" do
      subject { described_class.order_birthday }
      it_behaves_like "昇順確認", Date.new(1960, 4, 1), ->(o) { o.birthday }
    end

また、本科の学修番号を保持する h_gakushuu_number という属性があります。これも文字列なのですが、他高専から専攻科に入った学生はこの値がありません。このように null 値があるとテストができないので、 || 演算子を使って null の置き換え値を作るとよいです。この部分にはすべての値よりも大きくなる文字列などが設定されるといいです。

    context "order_h_gakushuu_number" do
      subject { described_class.order_h_gakushuu_number }
      it_behaves_like "昇順確認", "", ->(o) { o.h_gakushuu_number || "XXXXXX" }
    end

この shared_example は以下のようになります。取得した Array に対して、Proc を呼び出し start で指定されたものと比較します。比較結果が start よりも大きければテストが通過します。評価が終わると現在の値を start に置き換えるので、すべての要素に対して常に前の値より大きい単調増加の関係にあることがテストできることになります。

shared_examples_for "昇順確認" do |start, block|
  it "昇順に並んでいること" do
    subject.each do |object|
      value = block.call(object)
      expect(value <=> start).to be >= 0
      start = value
    end
  end
end

正しく動作させるための実装

このテストが正しく通過するためには、モデルに対して scope を設定すればよいことになります。Arel は Rails のプライベートメソッドなので本来使うべきではないのでしょうが、Rails 2 の頃から使い続けているため、今さらやめられなくなってしまいました。campus の order_sort_order は以下のように scope を記述します。

scope :order_sort_order, -> { order arel_table[:sort_order] }

上にしました gakusei の scope は以下のようになります。上には書いていない s_gakushuu_number も同じ scope になります。

  scope :order_birthday, -> { order arel_table[:birthday] }
  scope :order_h_gakushuu_number, -> { order arel_table[:h_gakushuu_number] }
  scope :order_s_gakushuu_number, -> { order arel_table[:s_gakushuu_number] }
  scope :order_yomi, -> { order arel_table[:yomi_suuji] }

おわりに

まだしばらくモデルのテストが続きます。今日からは scope やクラスメソッドについて記述していきますので、これで日数が稼げそうです。