テストfixtureの中にerbを書いて動的に生成する

テストをちゃんとやろうと試みた事は何度かあった。
でもfixture書くのがダルくてテストを書くまで辿りつけず挫折。


DBのデータからfixtureを作る方法もあるけど、それだとデータが大量すぎて扱いきれない。


でもやっぱりテストはやっておいた方が安心だよなぁと思う今日この頃。
安心というか自分のコードに自信が持てなくなってきたので要所要所はテストしておきたい。


しかしfixtureを書くのがだるい。


そもそもfixtureってDRYじゃない気がする。


そこでふと思いだしたのは「database.yml って DRY じゃないよね。」という話。

あたりで紹介されてます。


Railsyamlはerbを通してからyamlとして扱われるらしいのでfixtureにもerbが書ける*1はず。
やってみよう。

普通のfixtureの気にいらないところ

こういうfixtureがあったとする。

# == Schema Information
# Schema version: 1
#
# Table name: estimates
#
#  id                   :integer       not null, primary key
#  code                 :string(32)    not null
#  quotation_id         :integer       not null
#  issued_at            :datetime
#  issued_staff_id      :integer
#  created_at           :datetime      not null
#  created_staff_id     :integer       not null
#  updated_at           :datetime      not null
#  updated_staff_id     :integer       not null
#  disable              :boolean       not null
#

---
not_issued:
  id:               1
  code:             EST-0001
  created_at:       2009-02-02 22:22:22
  created_staff_id: 1
  updated_at:       2009-02-02 22:22:22
  updated_staff_id: 1
  disable:          false

issued:
  id:               2
  code:             EST-0002
  issued_at:        2009-02-03 23:23:23
  issued_staff_id:  1 
  created_at:       2009-02-02 22:22:22
  created_staff_id: 1
  updated_at:       2009-02-02 22:22:22
  updated_staff_id: 1
  disable:          false
気にいらないところ。
  1. xxx_at系の日時を書くの面倒
  2. DRYじゃない

1. 日時を動的に生成しちゃおう!!!

という事で、こんな風にしてみた。

not_issued:
  id:               1
  code:             EST-0001
  created_at:       <%= Time.now %>
  created_staff_id: 1
  updated_at:       <%= Time.now %>
  updated_staff_id: 1
  disable:          false
意図するものになっているか確認する方法

てっとりばやく確認するには以下の方法が使えます。

$ ruby -r erb -r yaml -e 'YAML.load(ERB.new(ARGF.read).result).to_yaml.display' test/fixtures/estimates.yml
---
not_issued:
  updated_at: Fri Feb 13 15:27:59 +0900 2009
  updated_staff_id: 1
  code: EST-0001
  id: 1
  disable: false
  created_at: Fri Feb 13 15:27:59 +0900 2009
  created_staff_id: 1
:

ステキ

2. DRYに挑戦!!!(YAMLでがんがる)

単純なyamlだったらyamlだけでも満足。
だってyamlにはマージ機能があるんですもの。

---
common_fixture: &common_fixture
  created_at:       <%= Time.now %>
  created_staff_id: 1
  updated_at:       <%= Time.now %>
  updated_staff_id: 1
  disable:          false

not_issued:
  id:               1
  code:             EST-0001
  <<:               *common_fixture

issued:
  id:               2
  code:             EST-0002
  <<:               *common_fixture
  issued_at:        2009-02-03 23:23:23
  issued_staff_id:  1 

とか。いい感じです。

でもfixtureだとこれではダメ。

なぜかというと'YAML.load(ERB.new(ARGF.read).result).to_yaml.display'すると

---
common_fixture:
  updated_staff_id: 1
  updated_at: Fri Feb 13 16:02:07 +0900 2009
  disable: false
  created_staff_id: 1
  created_at: Fri Feb 13 16:02:07 +0900 2009
issued:
  updated_at: Fri Feb 13 16:02:07 +0900 2009
  updated_staff_id: 1
  code: EST-0002
  issued_staff_id: 1
  id: 2
  issued_at: 2009-02-03 23:23:23
  disable: false
  created_at: Fri Feb 13 16:02:07 +0900 2009
  created_staff_id: 1
not_issued:
  updated_at: Fri Feb 13 16:02:07 +0900 2009
  updated_staff_id: 1
  code: EST-0001
  id: 1
  disable: false
  created_at: Fri Feb 13 16:02:07 +0900 2009
  created_staff_id: 1

テンプレートとして使っているcommon_fixture が残ってしまいます。
しかも、common_fixtureには必須レコードがないので実際にfixtureとして使おうとするとエラーになりまする。

2. DRYに挑戦!!!(ERBの力を借りる)

と、いう事でYAMLのマージは使えません。
しかしERBの力を借りればなんでもできる!!!


まぁそんなに難しい事はなくて変数に文字列を入れておいて後から展開すればいいだけの話ではないかと。
こんな感じ

<%
common_fixture = <<EOD
  created_at:       #{Time.now}
  created_staff_id: 1
  updated_at:       #{Time.now}
  updated_staff_id: 1
  disable:          false
EOD
%>
---
not_issued:
  id:               1
  code:             EST-0001
<%= common_fixture %>

issued:
  id:               2
  code:             EST-0002
<%= common_fixture %>
  issued_at:        2009-02-03 23:23:23
  issued_staff_id:  1
評価してみましょう。
---
issued:
  updated_staff_id: 1
  updated_at: Fri Feb 13 18:05:27 +0900 2009
  code: EST-0002
  issued_staff_id: 1
  id: 2
  issued_at: 2009-02-03 23:23:23
  disable: false
  created_staff_id: 1
  created_at: Fri Feb 13 18:05:27 +0900 2009
not_issued:
  updated_staff_id: 1
  updated_at: Fri Feb 13 18:05:27 +0900 2009
  code: EST-0001
  id: 1
  disable: false
  created_staff_id: 1
  created_at: Fri Feb 13 18:05:27 +0900 2009

ほらー。ほらほらー。
か・ん・ぺ・き。

さらなる進化

変数じゃなくて関数にしてみてもおもしろいかもしれませんね。

<%
def common_fixture(id)
  return <<"EOD"
  id:               #{id}
  code:             EST-#{"%04d" % id}
  created_at:       #{Time.now}
  created_staff_id: 1
  updated_at:       #{Time.now}
  updated_staff_id: 1
  disable:          false
EOD
end
%>
---
not_issued:
<%= common_fixture 1 %>

issued:
<%= common_fixture 2 %>
  issued_at:        <%= Time.now %>
  issued_staff_id:  1
これを評価すると。。。
---
issued:
  updated_staff_id: 1
  updated_at: Fri Feb 13 18:09:45 +0900 2009
  code: EST-0002
  issued_staff_id: 1
  id: 2
  issued_at: Fri Feb 13 18:09:45 +0900 2009
  disable: false
  created_staff_id: 1
  created_at: Fri Feb 13 18:09:45 +0900 2009
not_issued:
  updated_staff_id: 1
  updated_at: Fri Feb 13 18:09:45 +0900 2009
  code: EST-0001
  id: 1
  disable: false
  created_staff_id: 1
  created_at: Fri Feb 13 18:09:45 +0900 2009

ステキー!!!!

変数のままでステキな事を

ここまで書いてちょっと思った。
この程度ならわざわざ def common_fixture しなくても変数でできるんじゃね?

<%
common_fixture = <<EOD
  id:               %1$d
  code:             EST-%1$04d
  created_at:       #{Time.now}
  created_staff_id: 1
  updated_at:       #{Time.now}
  updated_staff_id: 1
  disable:          false
EOD
%>
---
not_issued:
<%= common_fixture % 1 %>

issued:
<%= common_fixture % 2 %>
  issued_at:        <%= Time.now %>
  issued_staff_id:  1

これでどう?

評価すると。。。
---
issued:
  updated_staff_id: 1
  updated_at: Fri Feb 13 18:15:34 +0900 2009
  code: EST-0002
  issued_staff_id: 1
  id: 2
  issued_at: Fri Feb 13 18:15:34 +0900 2009
  disable: false
  created_staff_id: 1
  created_at: Fri Feb 13 18:15:34 +0900 2009
not_issued:
  updated_staff_id: 1
  updated_at: Fri Feb 13 18:15:34 +0900 2009
  code: EST-0001
  id: 1
  disable: false
  created_staff_id: 1
  created_at: Fri Feb 13 18:15:34 +0900 2009

感無量ッス


いやーこれでfixture作るのが楽しくなってtestが書け(ry

*1:この表現あってる?