MOE3: SupportOrder のテスト
Order#target と Order#target=
理想は TDD なんだけど、実際に処理を書いてみないと挙動のイメージがつかめないことが多いので、どうしてもある程度コードが形になってからテストを用意するスタイルになってしまう。
安定してきたらテスト先行に移れると思うけど当面はやむなしか。
実装は Order
だけど、実際に使用されるのは SupportOrder
か ConvoyOrder
なので、SupportOrder
のテストとして書いた。ConvoyOrder
のテストは書くとしても差分が必要な場合だけになる予定。
spec/models/support_order_spec.rb
初めて shared_context
と let
を活用してみたので、いろいろ実験した結果長くなった。試しに全文貼ってみる。
正直 SupportOrder#new
で生成した場合の挙動や target
属性未指定で生成した場合の挙動はテストする必要があったのか疑問だけど、「どうなるか分からない」で放置するのも怖かったので書くだけ書いておいた。
行軍解決処理のテストはこの何倍も長くなるんだろうなあ。
# -*- coding: utf-8 -*- require 'spec_helper' describe SupportOrder do let(:hold_order) { FactoryGirl.create :hold_order } let(:move_order) { FactoryGirl.create :move_order } shared_context "SupportOrder#new で生成", support_order_gen: :new do let(:support_order) { FactoryGirl.build :support_order, target: target } end shared_context "SupportOrder#create で生成", support_order_gen: :create do let(:support_order) { FactoryGirl.create :support_order, target: target } end shared_context "target が nil の場合", target: :nil do let(:target) { nil } end shared_context "target が HoldOrder の場合", target: :hold do let(:target) { hold_order } end shared_context "target が MoveOrder の場合", target: :move do let(:target) { move_order } end describe "#new", support_order_gen: :new do context "target が nil の場合", target: :nil do subject { support_order.target } example { expect(subject).to be_nil } end context "target が HoldOrder の場合", target: :hold do subject { support_order.target } example { expect(subject).not_to be_nil } end context "target が MoveOrder の場合", target: :move do subject { support_order.target } example { expect(subject).not_to be_nil } end end describe "#create", support_order_gen: :create do context "target が nil の場合", target: :nil do subject { support_order.target } example { expect(subject).to be_nil } end context "target が HoldOrder の場合", target: :hold do subject { support_order.target } example { expect(subject).not_to be_nil } end context "target が MoveOrder の場合", target: :move do subject { support_order.target } example { expect(subject).not_to be_nil } end end describe "#target", support_order_gen: :new do context "target が HoldOrder の場合", target: :hold do subject { support_order.target } example { expect(subject).not_to eq hold_order } example { expect(subject.id).to be_nil } example { expect(subject.sample).to be_true } example { expect(subject.unit).to eq hold_order.unit } example { expect(subject.province).to eq hold_order.province } example { expect(subject.persisted?).to be_false } end context "target が MoveOrder の場合", target: :move do subject { support_order.target } example { expect(subject).not_to eq move_order } example { expect(subject.id).to be_nil } example { expect(subject.sample).to be_true } example { expect(subject.unit).to eq move_order.unit } example { expect(subject.src).to eq move_order.src } example { expect(subject.dst).to eq move_order.dst } example { expect(subject.persisted?).to be_false } end end describe "#target", support_order_gen: :create do context "target が HoldOrder の場合", target: :hold do subject { support_order.target } example { expect(subject).not_to eq hold_order } example { expect(subject.id).not_to be_nil } example { expect(subject.sample).to be_true } example { expect(subject.unit).to eq hold_order.unit } example { expect(subject.province).to eq hold_order.province } example { expect(subject.persisted?).to be_true } end context "target が MoveOrder の場合", target: :move do subject { support_order.target } example { expect(subject).not_to eq move_order } example { expect(subject.id).not_to be_nil } example { expect(subject.sample).to be_true } example { expect(subject.unit).to eq move_order.unit } example { expect(subject.src).to eq move_order.src } example { expect(subject.dst).to eq move_order.dst } example { expect(subject.persisted?).to be_true } end end describe "#target=", support_order_gen: :new do context "後から設定", target: :nil do before do support_order.target = hold_order end subject { support_order.target } example { expect(subject).not_to be_nil } example { expect(subject).not_to eq hold_order } example { expect(subject.unit).to eq hold_order.unit } example { expect(subject.province).to eq hold_order.province } example { expect(subject.persisted?).to be_false } end context "書き換え", target: :hold do before do @lt = support_order.target support_order.target = move_order end subject { support_order.target } example { expect(subject).not_to be_nil } example { expect(subject).not_to eq move_order } example { expect(subject.unit).to eq move_order.unit } example { expect(subject.src).to eq move_order.src } example { expect(subject.dst).to eq move_order.dst } example { expect(subject.persisted?).to be_false } example { expect(@lt.persisted?).to be_false } end end describe "#target=", support_order_gen: :create do context "後から設定", target: :nil do before do support_order.target = hold_order end subject { support_order.target } example { expect(subject).not_to be_nil } example { expect(subject).not_to eq hold_order } example { expect(subject.unit).to eq hold_order.unit } example { expect(subject.province).to eq hold_order.province } example { expect(subject.persisted?).to be_true } end context "書き換え", target: :hold do before do @lt = support_order.target support_order.target = move_order end subject { support_order.target } example { expect(subject).not_to be_nil } example { expect(subject).not_to eq move_order } example { expect(subject.unit).to eq move_order.unit } example { expect(subject.src).to eq move_order.src } example { expect(subject.dst).to eq move_order.dst } example { expect(subject.persisted?).to be_true } example { expect(@lt.persisted?).to be_false } end end end
Order#target= リファクタリング
少しだけ綺麗になった。
class Order < ActiveRecord::Base #(略) # target に渡された Order は自動的に複製される alias_method :association_target=, :target= def target=(target) if target sample = target.dup sample.sample = true else sample = nil end self.target.destroy if self.target self.association_target = sample save! if persisted? end #(略) end
belongs_to
宣言によって設定された Order#target=
メソッドは Order.create
(および Order.new
)の実行時にも呼ばれる。
引数として nil
と Order
の派生クラスのオブジェクト以外が渡された場合の動きは考慮していないが、その場合は sample.sample = true
で NoMethodError
例外が飛ぶ。
万が一 sample=
メソッドを持つオブジェクトでも self.assosiation_target = sample
で ActiveRecord::AssociationTypeMismatch
例外になるので検出漏れはない。
例外が飛ぶ時点で呼び出し側のバグは明白なので、Order#target=
としてはこれ以上の配慮は不要だろう。