ツナワタリマイライフ

日常ネタから技術ネタ、音楽ネタまで何でも書きます。

RubyでTDD(テスト駆動開発)をしてみた

TDD

Test-Driven Development
テストコードを先に書いてからプログラムを書くという手法。
テストに関しては新入社員研修でJavaJUnitをやりました。ただあまりついていけなく今も身についているとも言えない状況。会社で使う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