スはスペックのス〜RSpecによるテスト駆動開発の実演

スはスペックのス〜RSpecによるテスト駆動開発の実演〜 - 角谷信太郎 (1/3)‐ニコニコ動画(9)
スはスペックのス〜RSpecによるテスト駆動開発の実演〜 - 角谷信太郎 (2/3)‐ニコニコ動画(9)
スはスペックのス〜RSpecによるテスト駆動開発の実演〜 - 角谷信太郎 (3/3)‐ニコニコ動画(9)
を見ながらテストしてみた。
スライドはこちら。S is for Spec

感想

凄くいい動画・資料でした。
いやぁ、いい時代だ。


1本目は、TDDについての解説です。
2、3本目はボーリングのスコア算出プログラムの実演です。
1本目で解説した内容をテストを書くという「プロセス」を通して実演しています。
この「プロセス」がいいし、TDDって素晴らしいと思えます。


動画自体は1.5時間くらいあって長いし、
実演部は随時止めながら手動かしたので
全部で2時間ちょっとくらいかかりましたが
見る価値、やる価値は十分ありました。

一応ソース

#spec/game_spec.rb
require File.expand_path(File.dirname(__FILE__) + "/../game.rb")
#require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
#[1,4,4,5,6,4,5,5,10,0,1,7,3,6,4,10,2,8,6] => 133

describe Game, "すべてガターの場合" do
  before do
    @game = Game.new
    20.times { roll_gutter }
  end

  it "スコアは0点" do
    @game.score.should == 0
  end
end

describe Game, "すべて1ピンの場合" do
  before do
    @game = Game.new
    20.times { @game.roll(1) }
  end

  it "スコアは20点" do
    @game.score.should == 20
  end
end

describe Game, "ストライクの場合" do
  before do
    @game = Game.new
    roll_strike
    @game.roll(3)
    @game.roll(4)
    18.times { roll_gutter }
  end

  it "スコアは24点" do
    @game.score.should == 24
  end
end

describe Game, "パーフェクトゲーム場合" do
  before do
    @game = Game.new
    12.times { roll_strike }
  end

  it "スコアは300点" do
    @game.score.should == 300
  end
end

describe Game, "スペアの場合" do
  before do
    @game = Game.new
    roll_spare
    @game.roll(4)
    @game.roll(3) # 21
    16.times { roll_gutter }
  end

  it "スコアは21点" do
    @game.score.should == 21
  end
end

#[1,4,4,5,6,4,5,5,10,0,1,7,3,6,4,10,2,8,6] => 133
describe Game, "Uncle Bobの受け入れゲームの場合" do
  before do
    @game = Game.new
    [1,4,4,5,6,4,5,5,10,0,1,7,3,6,4,10,2,8,6].each {|pin| @game.roll pin}
  end

  it "スコアは133点" do
    @game.score.should == 133
  end
end

private
def roll_gutter
  @game.roll(0)
end

def roll_strike
  @game.roll(10)
end

def roll_spare
  @game.roll(5)
  @game.roll(5)
end
#game.rb
class Game
  FRAMES_OF_A_GAEME = 10
  def initialize
    @rolls = []
  end

  def roll(pins)
    @rolls << pins
  end
  
  def score
    roll_idx = 0
    score = 0
    FRAMES_OF_A_GAEME.times do |frame|
      if strike?(roll_idx)
        # strike
        score += strike_bounus(roll_idx)
        roll_idx += 1
      elsif spare?(roll_idx)
        # spare
        score += spare_bounus(roll_idx)
        roll_idx += 2
      else
        score += score_of_frame(roll_idx)
        roll_idx += 2        
      end
    end
    score
  end

  private
  def strike?(roll_idx)
    @rolls[roll_idx] == 10
  end
  
  def spare?(roll_idx)
    @rolls[roll_idx] + @rolls[roll_idx + 1] == 10
  end
  
  def score_of_frame(roll_idx)
    @rolls[roll_idx] + @rolls[roll_idx + 1]
  end
  
  def strike_bounus(roll_idx)
    10 + @rolls[roll_idx + 1] + @rolls[roll_idx + 2]
  end
  
  def spare_bounus(roll_idx)
    10 + @rolls[roll_idx + 2]
  end
end


10フレーム目が特殊な気がするので
追加で書いてみた。

describe Game, "10フレーム目の1投目がストライクの場合" do
  before do
    @game = Game.new
    18.times { roll_gutter }
    roll_strike
    @game.roll(3)
    @game.roll(4) # 17
  end

  it "スコアは17点" do
    @game.score.should == 17
  end
end

describe Game, "10フレーム目の2投目がストライクの場合" do
  before do
    @game = Game.new
    19.times { roll_gutter }
    roll_strike
    @game.roll(4) # 14
  end

  it "スコアは14点" do
    @game.score.should == 14
  end
end

describe Game, "10フレーム目でスペアの場合" do
  before do
    @game = Game.new
    18.times { roll_gutter }
    roll_spare
    @game.roll(4) # 14
  end

  it "スコアは14点" do
    @game.score.should == 14
  end
end

あとで

テスト自体は、もう少しきれいになるので、修正しよう。
autotestについて調べよう。

以下、自分用メモ

1つのゴール

"clean code that works"
動作するきれいなコード

1つのテーマ

"The translation of a feeling into a test is a common theme of TDD."
感情をテストにすることが、TDDのテーマである。

不安だ
何かがおかしい
これでいい
退屈だ
→テストを構造化する
→書くのを止める

2つの主張

設計の技法
開発の進め方

2つのルール

テストに失敗した時だけコードを書く
重複を取り除く

3つのモード

Red
Green
Refactoring

3つの技法

"Fake It"
いんちき
"Triangulate"
三角測量
"Obvious implementation"
ふつうに実装する

4つのモード

Think+3つのモード