詳細
・プロダクトコード、テストコードのひな型を生成
・テストコードを記述
・冗長なコードを記述
・テストコードをリファクタリング
・プロダクトコードのリファクタリング:メソッドの共通処理を抽出(extract method)
・プロダクトコードのリファクタリング:メソッド定義自体を動的に行う
の流れでTDD+リファクタリングを行ってみます。
(※RSpecを利用するので本当はBDDなのですが、BDDっぽい書き方をしていないのでTDDとします)
仕様
Hogeクラスにhoge1, hoge2, hoge3メソッドを作成。
各メソッドはメソッド名と同じ文字列を返却するのみ。
引数はなし。
rspec初期化
rspec -i
プロダクトコード、テストコードのひな型を生成
自作gemのRSpecPiccoloを利用します。
https://github.com/tbpgr/rspec_piccolo
下記のコマンドでHogeクラスをファイル名hoge.rbで作成して
hoge1,hoge2,hoge3のプロダクトコードのひな形とテストコードのひな形を生成します。
piccolo e Hoge hoge hoge1 hoge2 hoge3 -p
下記のようにファイルが生成されました。
構成
tree ├ lib | └ hoge.rb └ spec └ hoge_spec.rb
RSpecPiccoloによって生成されたプロダクトコードのひな形
lib/hoge.rb
# encoding: utf-8 class Hoge def hoge1 # TODO: implement your code end def hoge2 # TODO: implement your code end def hoge3 # TODO: implement your code end end
RSpecPiccoloによって生成されたテストコードのひな形
lib/hoge_spec.rb
# encoding: utf-8 require "spec_helper" require "hoge" describe Hoge do context :hoge1 do cases = [ { case_no: 1, case_title: "case_title", expected: "expected", }, ] cases.each do |c| it "|case_no=#{c[:case_no]}|case_title=#{c[:case_title]}" do begin case_before c # -- given -- hoge = Hoge.new # -- when -- # TODO: implement execute code # actual = hoge.hoge1 # -- then -- # TODO: implement assertion code # expect(actual).to eq(c[:expected]) ensure case_after c end end def case_before(c) # implement each case before end def case_after(c) # implement each case after end end end context :hoge2 do cases = [ { case_no: 1, case_title: "case_title", expected: "expected", }, ] cases.each do |c| it "|case_no=#{c[:case_no]}|case_title=#{c[:case_title]}" do begin case_before c # -- given -- hoge = Hoge.new # -- when -- # TODO: implement execute code # actual = hoge.hoge2 # -- then -- # TODO: implement assertion code # expect(actual).to eq(c[:expected]) ensure case_after c end end def case_before(c) # implement each case before end def case_after(c) # implement each case after end end end context :hoge3 do cases = [ { case_no: 1, case_title: "case_title", expected: "expected", }, ] cases.each do |c| it "|case_no=#{c[:case_no]}|case_title=#{c[:case_title]}" do begin case_before c # -- given -- hoge = Hoge.new # -- when -- # TODO: implement execute code # actual = hoge.hoge3 # -- then -- # TODO: implement assertion code # expect(actual).to eq(c[:expected]) ensure case_after c end end def case_before(c) # implement each case before end def case_after(c) # implement each case after end end end end
テストコードを記述
spec/hoge_spec.rb
# encoding: utf-8 require "spec_helper" require "hoge" describe Hoge do context :hoge1 do cases = [ { case_no: 1, case_title: "hoge1 case", expected: "hoge1", }, ] cases.each do |c| it "|case_no=#{c[:case_no]}|case_title=#{c[:case_title]}" do begin case_before c # -- given -- hoge = Hoge.new # -- when -- actual = hoge.hoge1 # -- then -- expect(actual).to eq(c[:expected]) ensure case_after c end end def case_before(c) # implement each case before end def case_after(c) # implement each case after end end end context :hoge2 do cases = [ { case_no: 1, case_title: "hoge2 case", expected: "hoge2", }, ] cases.each do |c| it "|case_no=#{c[:case_no]}|case_title=#{c[:case_title]}" do begin case_before c # -- given -- hoge = Hoge.new # -- when -- actual = hoge.hoge2 # -- then -- expect(actual).to eq(c[:expected]) ensure case_after c end end def case_before(c) # implement each case before end def case_after(c) # implement each case after end end end context :hoge3 do cases = [ { case_no: 1, case_title: "hoge3 case", expected: "hoge3", }, ] cases.each do |c| it "|case_no=#{c[:case_no]}|case_title=#{c[:case_title]}" do begin case_before c # -- given -- hoge = Hoge.new # -- when -- actual = hoge.hoge3 # -- then -- expect(actual).to eq(c[:expected]) ensure case_after c end end def case_before(c) # implement each case before end def case_after(c) # implement each case after end end end end
テストを実行します(まだプロダクトコードを実装していないので全件失敗します)
$ rspec Run options: include {:focus=>true} All examples were filtered out; ignoring {:focus=>true} FFF Failures: 1) Hoge hoge3 |case_no=1|case_title=hoge3 case Failure/Error: expect(actual).to eq(c[:expected]) expected: "hoge3" got: nil (compared using ==) # ./spec/hoge_spec.rb:102:in `block (4 levels) in <top (required)>' 2) Hoge hoge2 |case_no=1|case_title=hoge2 case Failure/Error: expect(actual).to eq(c[:expected]) expected: "hoge2" got: nil (compared using ==) # ./spec/hoge_spec.rb:65:in `block (4 levels) in <top (required)>' 3) Hoge hoge1 |case_no=1|case_title=hoge1 case Failure/Error: expect(actual).to eq(c[:expected]) expected: "hoge1" got: nil (compared using ==) # ./spec/hoge_spec.rb:28:in `block (4 levels) in <top (required)>' Finished in 0.002 seconds 3 examples, 3 failures Failed examples: rspec ./spec/hoge_spec.rb:91 # Hoge hoge3 |case_no=1|case_title=hoge3 case rspec ./spec/hoge_spec.rb:54 # Hoge hoge2 |case_no=1|case_title=hoge2 case rspec ./spec/hoge_spec.rb:17 # Hoge hoge1 |case_no=1|case_title=hoge1 case Randomized with seed 21394
まずは冗長なコードを作成
まずは共通化など一切無しで実装してみます。
# encoding: utf-8 class Hoge def hoge1 'hoge1' end def hoge2 'hoge2' end def hoge3 'hoge3' end end
テストを実行します。(全件成功)
$ rspec Run options: include {:focus=>true} All examples were filtered out; ignoring {:focus=>true} ... Finished in 0.002 seconds 3 examples, 0 failures Randomized with seed 20945
テストコードをリファクタリング
テストコードが冗長なのでリファクタリングします。
spec/hoge_spec.rb
# encoding: utf-8 require "spec_helper" require "hoge" describe Hoge do %w{1 2 3}.each do |i| context "hoge#{i.to_s}".to_sym do cases = [ { case_no: 1, case_title: "hoge#{i} case", expected: "hoge#{i}", }, ] cases.each do |c| it "|case_no=#{c[:case_no]}|case_title=#{c[:case_title]}" do begin case_before c # -- given -- hoge = Hoge.new # -- when -- actual = hoge.send "hoge#{i}" # -- then -- expect(actual).to eq(c[:expected]) ensure case_after c end end def case_before(c) # implement each case before end def case_after(c) # implement each case after end end end end end
テストを実行します
$ rspec Run options: include {:focus=>true} All examples were filtered out; ignoring {:focus=>true} ... Finished in 0.002 seconds 3 examples, 0 failures Randomized with seed 21035
プロダクトコードのリファクタリング:メソッドの共通処理を抽出(extract method)
lib/hoge.rb
# encoding: utf-8 class Hoge def hoge1 hoge(1) end def hoge2 hoge(2) end def hoge3 hoge(3) end private def hoge(index) "hoge#{index}" end end
テストを実行します
$ rspec Run options: include {:focus=>true} All examples were filtered out; ignoring {:focus=>true} ... Finished in 0.00208 seconds 3 examples, 0 failures Randomized with seed 8826
プロダクトコードのリファクタリング:メソッド定義自体を動的に行う
lib/hoge.rb
# encoding: utf-8 class Hoge %w{1 2 3}.each do |i| define_method "hoge#{i}".to_sym do "hoge#{i}" end end end
テストを実行します
$ rspec Run options: include {:focus=>true} All examples were filtered out; ignoring {:focus=>true} ... Finished in 0.00208 seconds 3 examples, 0 failures Randomized with seed 56847
リファクタリング完了です。