TDD
Test-Driven Development
テストコードを先に書いてからプログラムを書くという手法。
テストに関しては新入社員研修でJavaのJUnitをやりました。ただあまりついていけなく今も身についているとも言えない状況。会社で使うRubyでも標準のTest Unitというフレームワークがあるということ、会社の上司から勧められたこともあって以下のページを参考にやってみました。
参考1
Ruby初級者向けレッスン 第46回 ─── Test::Unit
まずTest-Unitって何よ?ってことが分からなかったのでこれを見ながらお勉強。書いたコードは以下。
myarray.rb
class MyArray def initialize @array = [] end def empty? @array.empty? end def << o @array << o self end end
test_myarray.rb
require 'test/unit' require './myarray' class TestMyArray < Test::Unit::TestCase def setup @array = MyArray.new end def test_empty_by_empty_myarray assert(@array.empty?) end def test_empty_by_nonempty_myarray @array << "1st" assert(!@array.empty?) end end
配列が空かどうかを確かめるテストコード。assert関数の使い方、テスト実行時のオプションの解説があって有益でした。
あとなぜかeclipseだと以下のエラーでテストコード実行できないんですねー未解決
>
C:/Ruby200-x64/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file -- test/unit/ui/console/testrunner (LoadError)
from C:/Ruby200-x64/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require'
from C:/pleiades/eclipse/configuration/org.eclipse.osgi/77/0/.cp/testing/dltk-testunit-runner.rb:1:in `
from C:/Ruby200-x64/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require'
from C:/Ruby200-x64/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require'<
cygwinでコマンドだと通るんだけどね。
参考2
エンジニアのスキルを伸ばす“テスト駆動開発”を学んでみよう | Think IT(シンクイット)
TDDでリファクタリングを行う適切なタイミングとは? | Think IT(シンクイット)
ボウリングでスペアを取得した場合のテストケースを考える | Think IT(シンクイット)
ボウリングでストライクを取得した場合のテストケースを考える | Think IT(シンクイット)
連載形式であと2つぐらいでるのかな。日付見る限り隔月~2月くらいなので1月に出るだろうか。
やってみた限りボーリングのスコア計算という身近なテーマでTDDを実践。
Red→Green→Refactor!のリズムは体感できたし、楽しい!と何より思えました。
(ただcygwinのコンソールだとRedやGreenに色が変わってくれない…)
スコアとストライクの混合がまだのようですが、カバレッジの話も今後してくれるのかと期待。
あと今業務でテストをやっていること(Rspec、turnipで自動化)もあってテストに関心がありました。もちろん本業でもテストはやらなきゃいけないので(Unit Test使っているかは不明)良い経験。
あとはリーダブルコードお夏ごろ読んでリファクタリングの障りを実践したこともあって、テストとリファクタ、設計が学べることからTDDは初学者に本当有益。Ruby言語の勉強1からやっていたけどなかなか継続できていなかったのでしばらくはこのペースでテスト起点で勉強していこうかなと思いました。
以下書いたコード。
bowling_game.rb
class BowlingGame def initialize #変数定義と初期化 @score = 0 @spare = false @last_pins = 0 @shot_num = 1 @strike_bonus_count = 0 @double_bonus_count = 0 end def record_shot(pins) #倒したピンを記録する @score += pins calc_strike_bonus(pins) calc_spare_bonus(pins) @last_pins = pins proceed_next_shot(pins) end def score @score end private #外からアクセスされない def calc_spare_bonus(pins)#スペアボーナス計算関数 if @spare @score += pins @spare = false end if @shot_num == 2 && @last_pins + pins == 10 @spare = true end end def calc_strike_bonus(pins)#ストライクボーナス計算関数 if @strike_bonus_count > 0 @score += pins @strike_bonus_count -= 1 end if @double_bonus_count > 0 @score += pins @double_bonus_count -= 1 end if pins == 10 if @strike_bonus_count == 0 @strike_bonus_count = 2 else @double_bonus_count = 2 end end end def proceed_next_shot(pins)#投球回数判定関数 if @shot_num == 1 @shot_num = 2 else @shot_num += 1 end end end
bowling_game_test.rb
require 'minitest/autorun' require 'test/unit' require './bowling_game' class BowlingGameTest < MiniTest::Unit::TestCase def setup @game = BowlingGame.new end def test_全ての投球がガター @game = BowlingGame.new record_many_shots(20,0) assert_equal 0, @game.score end def test_全ての投球で1ピンだけ倒した record_many_shots(20,1) assert_equal 20, @game.score end def test_スペアをとると次の投球のピン数を加算 @game.record_shot(3) @game.record_shot(7) #スペアで14点獲得 @game.record_shot(4) record_many_shots(17,0) #残りはガター assert_equal 18, @game.score end def test_スペアをとると次の投球のピン数を加算2 @game.record_shot(2) @game.record_shot(5) @game.record_shot(5) #2フレーム目の1投目 @game.record_shot(1) record_many_shots(16,0) #残りはガター assert_equal 13, @game.score end def test_ストライク @game.record_shot(10) #ストライク @game.record_shot(3) @game.record_shot(3) @game.record_shot(1) record_many_shots(15,0) #残りはガター assert_equal 23, @game.score end def test_連続ストライク @game.record_shot(10) #ストライク @game.record_shot(10) #ストライク @game.record_shot(3) @game.record_shot(1) record_many_shots(14,0) #残りはガター assert_equal 41, @game.score end def test_ターキー @game.record_shot(10) #ストライク 30 @game.record_shot(10) #ストライク 23 @game.record_shot(10) #ストライク 14 @game.record_shot(3) @game.record_shot(1) record_many_shots(12,0) #残りはガター assert_equal 71, @game.score end def record_many_shots(count,pins) count.times do @game.record_shot(pins) end end end