「Rubyベストプラクティス」を読む1
数か月前に、一度「Rubyベストプラクティス」を読んでいたけれど、良い内容だったので
復習のために、もう一度読みなおしてみる。大事そうなところを日記に書いておこう。
まずは1章から、テストの話。
メソッドを分割する
本に載っている例。以下のようなコードをテストするとする。
class Questioner # questionの文字列を出力して、標準入力を待つ。 # 入力の内容によって、以下のような動きをする # y Y Yes YeS YES yes など => trueを返す。 # n N No nO など => falseを返す。 # それ以外 => もう一度 askを呼ぶ。 def ask(question) puts question response = gets.chomp case(response) when /^y(es)?$/i true when /^no?$/i false else puts "I don't understand." ask question end end end
まずはテストしやすいように、コードを分割する。この場合、getsをほかの部分と分離させる。
class Questioner def ask(question) puts question response = yes_or_no(gets.chomp) response.nil? ? ask(question) : response end def yes_or_no(response) case(response) when /^y(es)?$/i true when /^no?$/i false end end end
これで、yes_or_noメソッドは簡単にテストできる。例えば、minitestを使えば
def setup @questioner = Questioner.new end def test_yes %w(y Y Yes YES yes).each do |yes| assert @questioner.yes_or_no(yes), "#{yes.inspect} expected to parse as true" end end
とできる。askメソッド以外はテスト出来るようになる。
スタブを使う
次に、askメソッドを呼び出しているようなメソッドをテストする場合。
class Questioner def inquire_about_happiness ask("Are you happy?") ? "Good" : "That's Too Bad" end end
askは内部でgetsを使っているので、テストしにくい。このようなときは、特異メソッド定義を使って、askをスタブで置き換える。
def setup @questioner = Questioner.new end def test_yes def @questioner.ask(question); true; end assert_equal "Good", @questioner.inquire_about_happiness end def test_no def @questioner.ask(question); false; end assert_equal "That's Too Bad", @questioner.inquire_about_happiness end
モックを使う
putsやgetsを使っているaskメソッドをテストするには、IOオブジェクトのように振舞うオブジェクトを使えばいい。
まずはputsやgetsを、IOオブジェクトへのメソッド呼び出しに書き直す。
class Questioner def initialize(in=STDIN, out=STDOUT) @input, @output = in, out end def ask(question) @output.puts question response = yes_or_no(@input.gets.chomp) response.nil? ? ask(question) : response end end
テストではIOオブジェクトをStringIOなどにすれば、askメソッドもテストできる。
def setup @in = StringIO.new @out = StringIO.new @questioner = Questioner.new(@in, @out) @question = "Are you happy?" end def test_ask @in << "yes" @in.rewind assert @questioner.ask(@question), "Expected yes to by true"; assert_equal "#{@question}\n", @output.string end