2013年8月9日金曜日

RspecでFactory Methodパターンをテストするサンプル


工場の工員が製品を製造するというFactory Methodパターンのテストを考えてみる。
工員と製品は工場に所属していて、工員が製品を製造すると、工場の在庫(製品)が増える。

まずは必要なモデルを生成する。
rails g model koujou name:string
rails g model kouin koujou:references name:string
rails g model seihin koujou:references seihin_num:integer
rake db:migrate
工場モデルに工員、製品間の1対多の関係を設定しておく。
class Koujou < ActiveRecord::Base
  has_many :kouins
  has_many :seihins
end
次に、工員モデルに製品の製造メソッドを追加する。
class Kouin < ActiveRecord::Base
  belongs_to :koujou

  def seizou
    seihin = Seihin.new
    seihin.koujou_id = koujou_id
    max_seihin_num = Seihin.maximum(:seihin_num)
    if max_seihin_num.blank?
      seihin.seihin_num = 1
    else
      seihin.seihin_num = max_seihin_num + 1
    end
    return seihin
  end
end
下記のような感じでRspecのテストコードを記述してみる。
テストコード内でsaveしてもDBにはcommitされないので、製造後の製品の配列のサイズの確認は常に1で問題ない。
require 'spec_helper'

describe Koujou do
  it '製品製造後の在庫(製品)増加の確認.' do
    koujou = Koujou.new
    koujou.name = 'テスト'
    koujou.save

    p koujou.seihins

    kouin = Kouin.new
    kouin.koujou_id = koujou.id
    kouin.name = '浜松浜子'
    kouin.save

    seihin = kouin.seizou
    seihin.save

    p koujou.seihins

    expect(koujou).to have(1).seihins
  end
end
ところが、テストが通らない。
製造後の製品の配列のサイズは0のままである。
bundle exec rspec spec/models/koujou_spec.rb
Rack::File headers parameter replaces cache_control after Rack 1.5.
[]
[]
 [31mF [0m

Failures:

  1) Koujou 製品製造後の在庫(製品)増加の確認.
      [31mFailure/Error: [0m  [31mexpect(koujou).to have(1).seihins [0m
        [31mexpected 1 seihins, got 0 [0m
 [36m     # ./spec/models/koujou_spec.rb:23:in `block (2 levels) in <top (requir
ed)>' [0m

Finished in 0.53903 seconds
 [31m1 example, 1 failure [0m

Failed examples:

 [31mrspec ./spec/models/koujou_spec.rb:6 [0m  [36m# Koujou 製品製造後の在庫(製
品)増加の確認. [0m

Randomized with seed 17099
よく考えてみると前回の記事と関連した問題である。
子モデルの値を参照する毎にDBから最新の値を取得しているのではなく、インスタンスに変更が加わらない限りは一度DBから参照した値をそのまま保持しているので、最初に参照した時点で配列が空になっている状態をその後も返しているわけである。

下記のように、途中で子モデルの値を確認する余計なコードを外して再度実行すると、チェックの時点で初めてDBから値を取得するのでテストが通るようになる。
require 'spec_helper'

describe Koujou do
  it '製品製造後の在庫(製品)増加の確認.' do
    koujou = Koujou.new
    koujou.name = 'テスト'
    koujou.save

    kouin = Kouin.new
    kouin.koujou_id = koujou.id
    kouin.name = '浜松浜子'
    kouin.save

    seihin = kouin.seizou
    seihin.save

    expect(koujou).to have(1).seihins
  end
end
bundle exec rspec spec/models/koujou_spec.rb
Rack::File headers parameter replaces cache_control after Rack 1.5.
 [32m. [0m

Finished in 0.52803 seconds
 [32m1 example, 0 failures [0m

Randomized with seed 58919
なんとなく気持ち悪いので、下記のように、expectの内部で製造を行った上で、チェックする時点で明示的にDBから値を取得するようにして、差分の確認はbyというマッチャを使うと安定しそうだ。
require 'spec_helper'

describe Koujou do
  it '製品製造後の在庫(製品)増加の確認.' do
    koujou = Koujou.new
    koujou.name = 'テスト'
    koujou.save

    kouin = Kouin.new
    kouin.koujou_id = koujou.id
    kouin.name = '浜松浜子'
    kouin.save

    expect {
      seihin = kouin.seizou
      seihin.save
    }.to change{Koujou.find(koujou.id).seihins.size}.by(1)
  end
end
bundle exec rspec spec/models/koujou_spec.rb
Rack::File headers parameter replaces cache_control after Rack 1.5.
 [32m. [0m

Finished in 0.59903 seconds
 [32m1 example, 0 failures [0m

Randomized with seed 65120

0 件のコメント:

コメントを投稿