testで似た処理をまとめよう

最近railsで開発している時に、TDDにもちょっとづつ慣れてきたつもりになっていますが、テストを書いている時、同じような処理をコピペでごまかしている自分がいました。
DRYに反する自分。でも、どうにもできない自分。
「これじゃいかん!」と言うことで休日を利用してチャレンジしました。

前提

Hogeという、テーブルの中に

    • title
    • content
    • author

という属性が存在する。

やりたい事

    • 各属性title,content,authorともに、nilだとvalidではじかれる事を、テストで確認できるようにしたい。


今までのテスト

def setup
  @hoge = Hoge.new
end

def test_valid_failure_nil_title
  @hoge.title = nil
  @hoge.content = "fuga"
  @hoge.author = "piyo"
  assert_equal false, @hoge.valid?
end

def test_valid_failure_nil_content
  @hoge.title = "fuga"
  @hoge.content = nil
  @hoge.author = "piyo"
  assert_equal false, @hoge.valid?
end

def test_valid_failure_nil_author
  @hoge.title = "fuga"
  @hoge.content = "piyo"
  @hoge.author = nil
  assert_equal false, @hoge.valid?
end

んー、似てることやってるのに凄く効率が悪い…


と言うことで、改造してみました。

結果

def create_default_description(substitute_value)
  @hoge = Hoge.new
  description = {"title" => "hoge",      …  (1)
                 "content" => "fuga",
                 "author" => "piyo"
                }
  description.update(substitute_value)   …  (3)
  
  description.each do |key,value|
	  eval("@hoge.#{key} = value")       
  end
end

def test_create_failure_nil_title
  substitute_value = {"title" => nil}  …  (2)
  create_default_description(substitute_value)
  assert_equal false, @hoge.valid?
end

def test_create_failure_nil_content
(省略)


と言う感じです。

一般化したメソッドに、どうやって、title,content,authorに
違う値をいれれば良いのか悩んでしまったのですが、

(1) 一般化したメソッド内に、description というHashに、初期値を用意してあげておく。
(2) テストしたい属性もHashにいれてあげて、引数として渡す。
(3) updateメソッドを使ってあげる事で、初期値と引数の差分の所だけ更新してもらう。

と言うことで解決できることが分かりました。
リファレンスマニュアルさまさまです!!

これなら、属性の数が増えても、そして、nil以外の色々な値を試したくなっても大丈夫そう。かな?
しかし、衝撃の事実。ソースが長くなっている…

そもそも、全てのケースを試さないで大丈夫なような、効率の良いテストをしっかり考えられるように、勉強しなければなぁと思った一日でした。


以下は、実際に挙動を確かめるためのソースです。

class Hoge
  attr_accessor :title, :content, :author
  
  def initialize
    @title 
    @content
    @author
  end
end


def create_default_description(s_v)
  @hoge = Hoge.new
	description = {"title" => "hoge",
                       "content" => "fuga",
                       "author" => "piyo"
                      }
  description.update(s_v)
  
  description.each do |key,value|
	  eval("hoge.#{key} = value")
  end

  p hoge  #=> #<Content:0x2b5582c @author="piyo", @content="fuga", @title=nil>
end

substitute_value = {"title" => nil}
create_default_description(substitute_value)