読者です 読者をやめる 読者になる 読者になる

FizzBuzz 問題をお題に TDD

ここ何日かRubyをぼちぼち勉強中。

初めてのRuby

初めてのRuby

ある程度ちゃんと文法を覚えようとこの本を写経してる。
ちょっと飽きたので、FizzBuzz をお題にして TDD してみようと思い立ちやってみた。

前提

  • TDD 初心者
  • Ruby 初心者

仕様

  • 1 から 100 までの数字をコンソールに表示する
  • 3 の倍数の場合は、数字の代わりに"Fizz"を表示する
  • 5 の倍数の場合は、数字の代わりに"Buzz"を表示する
  • 3 と 5 の公倍数の場合は、数字の代わりに"FizzBuzz"を表示する

テストコード

require 'test/unit'
require 'fizzbuzz'

class TC_FizzBuzz < Test::Unit::TestCase
  def setup
    @obj = FizzBuzz.new
  end
  def test_multiple_of_3
    assert_equal("Fizz", @obj.say(3))
    assert_equal("Fizz", @obj.say(18))
  end
  def test_multiple_of_5
    assert_equal("Buzz", @obj.say(5))
    assert_equal("Buzz", @obj.say(10))
  end
  def test_multiple_of_3and5
    assert_equal("FizzBuzz", @obj.say(15))
    assert_equal("FizzBuzz", @obj.say(45))
  end
  def test_other_number
    assert_equal(2, @obj.say(2))
    assert_equal(4, @obj.say(4))
    assert_equal(7, @obj.say(7))
    assert_equal(14, @obj.say(14))
    assert_equal(16, @obj.say(16))
  end
  def test_not_decimal
    assert_raise(ArgumentError, "invalid argument") {
      @obj.say(1.5)
    }
  end
  def test_zero
    assert_raise(ArgumentError, "invalid argument") {
      @obj.say(0)
    }
  end
  def test_minus_number
    assert_raise(ArgumentError, "invalid argument") {
      @obj.say(-1)
    }
  end
  def test_string
    assert_raise(ArgumentError, "invalid argument") {
      @obj.say("a")
    }
  end
end

プロダクトコード

class FizzBuzz
  def say(n)
    if n.to_s =~ /\D/ || n <= 0
      raise ArgumentError, "invalid argument"
    end
    if n % 5 == 0 && n % 3 == 0
      return 'FizzBuzz'
    elsif n % 5 == 0
     return 'Buzz'
    elsif n % 3 == 0
      return 'Fizz'
    else
      return n
    end
  end
end

疑問

参考にしたサイトは、どれも 引数 n を取るメソッドをもつクラスを作って、そのメソッドに対してテストしている。
仕様としては、"1 から 100 までの数字をコンソールに表示する"ってのがあるので、そのメソッドを引数1から100までループさせる呼び出し側ロジックが必要なんだけど、それをテストコードに含める?それともTDDってメソッド単位を保証するもの?って疑問。
たとえば、こんなやつ↓

require 'fizzbuzz'

obj = FizzBuzz.new

1.upto(100) do |i|
  puts obj.say(i)
end

というようなことを、Twitter でつぶやいたら、ありがたい助言をいくつか頂いた。
@gab_km さん、@kyon_mm さん、@biac さん、ありがとうございます!

そこから得た現時点での TDD についての理解。

  • コードに不安があるならテストする
  • TDD はテスト(開発)手法のひとつ
  • 狭義には UnitTest を指すことが多い
  • 広義にはいろんなテストレベルに適用することを指す
  • 各テストレベルにおける確認の粒度(メソッドとかクラスとかコンポーネントとか)と合わせてテスト実装を行う

今回のお題においては、メインロジックの方は非常に単純なので、メソッドさえ動作保証できれば一回動かして確認すればいいかなと判断。
テストコードには含めないことにした。
でも、実際にこれをテストコードに含めるとしたら、どういうケースにするのかな?
戻り値では確認できないし、コンソール出力をファイルに落として確認とか?これもあとで調べよう。

最近行われた TDD カンファレンスの TL とか見てると、TDD を既に実践されてる方にとっては何周か回った話なんだと思うし、色々な考え方があるらしいことも知ってる。
まだちゃんと消化できてない部分もあるので、Ruby の文法をある程度やったらテスト駆動開発入門の写経をしながら自分としての理解を深めるつもり。

テスト駆動開発入門

テスト駆動開発入門

  • 作者: ケントベック,Kent Beck,長瀬嘉秀,テクノロジックアート
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2003/09
  • メディア: 単行本
  • 購入: 45人 クリック: 1,058回
  • この商品を含むブログ (161件) を見る