テスト駆動開発でFizzBuzz問題を解く
お久しぶりの更新です。
今回はFizzBuzz問題をテスト駆動開発(以下TDD)で解く手順について書いていきます。
使用言語はrubyです。
- FizzBuzz問題とは?
- テストコード
- 実装
- まとめ
以上のような流れで書いていきます。
FizzBuzz問題とは?
早速例を以下に示します
1, 2, fizz, 4, buzz, fizz, 7, 8 fizz, buzz, 11, fizz, 13, 14, fizzbuzz
このように1から数えていき、3の倍数であればfizz、5の倍数であればbuzz、どちらも満たしていればfizzbuzzを返すという問題です。
テストコード
ではfizzbuzzの実装に入っていくのですが、TDDではまずはテストコードを書きます。テストコードは次のようになります。
# -*- coding: utf-8 -*- require 'test/unit' require './fizz_buzz' class FizzBuzzTest < Test::Unit::TestCase def setup @fizz_buzz = FizzBuzz.new end def test_at1 r = @fizz_buzz.at 1 assert_equal(1, r) end def test_at3 r = @fizz_buzz.at 3 assert_equal('fizz', r) end def test_at5 r = @fizz_buzz.at 5 assert_equal('buzz', r) end def test_at15 r = @fizz_buzz.at 15 assert_equal('fizzbuzz', r) end def teardown end end
上から説明すると
- test/unit、fizz_buzzファイルの読み込み
- setupでFizzBuzzのインスタンスを作成
- testメソッドでは1, 3, 5, 15の時に結果がどうなるかをチェック
- テストコードはsetup~teardownの間に書く
最初はこれをコピペでも良いかと思います。
そして実行すると次のようになります。
/usr/local/rvm/rubies/ruby-1.9.3-p194/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:36:in `require': cannot load such file -- ./fizz_buzz (LoadError) from /usr/local/rvm/rubies/ruby-1.9.3-p194/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:36:in `require' from fizz_buzz_test.rb:3:in `<main>'
fizz_buzzファイルがないということです。
というわけで作って行きましょう!!
実装
まずはfizz_buzzファイルを作成します。
fizz_buzz.rb
class FizzBuzz end
そこでテストコードを実行すると次のようになります。
# Running tests: EEEE Finished tests in 0.002399s, 1667.3614 tests/s, 0.0000 assertions/s. 1) Error: test_at1(FizzBuzzTest): NoMethodError: undefined method `at' for #<FizzBuzz:0x007fb5c1982ed8> fizz_buzz_test.rb:11:in `test_at1' 2) Error: test_at15(FizzBuzzTest): NoMethodError: undefined method `at' for #<FizzBuzz:0x007fb5c1982438> fizz_buzz_test.rb:26:in `test_at15' 3) Error: test_at3(FizzBuzzTest): NoMethodError: undefined method `at' for #<FizzBuzz:0x007fb5c19819c0> fizz_buzz_test.rb:16:in `test_at3' 4) Error: test_at5(FizzBuzzTest): NoMethodError: undefined method `at' for #<FizzBuzz:0x007fb5c183f800> fizz_buzz_test.rb:21:in `test_at5' 4 tests, 0 assertions, 0 failures, 4 errors, 0 skips
ざっくりいうとメソッドがないと言われています。
なのでメソッドを作ります。
class FizzBuzz def at end end
すると
# Running tests: EEEE Finished tests in 0.002626s, 1523.2292 tests/s, 0.0000 assertions/s. 1) Error: test_at1(FizzBuzzTest): ArgumentError: wrong number of arguments (1 for 0) /Users/nigorinumahiroki/prog/ruby/FizzBuzzTestCode/fizz_buzz.rb:2:in `at' fizz_buzz_test.rb:11:in `test_at1' 2) Error: test_at15(FizzBuzzTest): ArgumentError: wrong number of arguments (1 for 0) /Users/nigorinumahiroki/prog/ruby/FizzBuzzTestCode/fizz_buzz.rb:2:in `at' fizz_buzz_test.rb:26:in `test_at15' 3) Error: test_at3(FizzBuzzTest): ArgumentError: wrong number of arguments (1 for 0) /Users/nigorinumahiroki/prog/ruby/FizzBuzzTestCode/fizz_buzz.rb:2:in `at' fizz_buzz_test.rb:16:in `test_at3' 4) Error: test_at5(FizzBuzzTest): ArgumentError: wrong number of arguments (1 for 0) /Users/nigorinumahiroki/prog/ruby/FizzBuzzTestCode/fizz_buzz.rb:2:in `at' fizz_buzz_test.rb:21:in `test_at5' 4 tests, 0 assertions, 0 failures, 4 errors, 0 skips
今度はメソッドにいれる引数が変だよと言われます
なので
class FizzBuzz def at(n) end end
結果は
# Running tests: FFFF Finished tests in 0.004781s, 836.6451 tests/s, 836.6451 assertions/s. 1) Failure: test_at1(FizzBuzzTest) [fizz_buzz_test.rb:12]: <1> expected but was <nil>. 2) Failure: test_at15(FizzBuzzTest) [fizz_buzz_test.rb:27]: <"fizzbuzz"> expected but was <nil>. 3) Failure: test_at3(FizzBuzzTest) [fizz_buzz_test.rb:17]: <"fizz"> expected but was <nil>. 4) Failure: test_at5(FizzBuzzTest) [fizz_buzz_test.rb:22]: <"buzz"> expected but was <nil>. 4 tests, 4 assertions, 4 failures, 0 errors, 0 skips
若干進みました!
というわけでテスト1を成功させてみます。
class FizzBuzz def at(n) return 1 end end
すると
# Running tests: .FFF Finished tests in 0.004811s, 831.4280 tests/s, 831.4280 assertions/s. 1) Failure: test_at15(FizzBuzzTest) [fizz_buzz_test.rb:27]: <"fizzbuzz"> expected but was <1>. 2) Failure: test_at3(FizzBuzzTest) [fizz_buzz_test.rb:17]: <"fizz"> expected but was <1>. 3) Failure: test_at5(FizzBuzzTest) [fizz_buzz_test.rb:22]: <"buzz"> expected but was <1>. 4 tests, 4 assertions, 3 failures, 0 errors, 0 skips
一番上の . FFFに注目すると先ほどまではFFFFだったのに対し今度は . FFFになっています。
これは最初のアサーションが成功しているということです。
次は一気に全部成功させてみましょう。
class FizzBuzz def at(n) if n == 1 return 1 elsif n==3 return 'fizz' elsif n==5 return 'buzz' else return 'fizzbuzz' end end end
結果は
# Running tests: .... Finished tests in 0.002073s, 1929.5707 tests/s, 1929.5707 assertions/s. 4 tests, 4 assertions, 0 failures, 0 errors, 0 skips
全部成功!!
完成!!
。。。ではないことは明らかですね。というわけでテストコードを若干変更
def test_at6 r = @fizz_buzz.at 6 assert_equal('fizz', r) end
上記のコードを追加します。
すると確実にこれはfailureになります。
というわけでハードコーディングでは限界があるので上記のテストも合格するコードを書きます
class FizzBuzz def at(n) fizz = false buzz = false if (n%3) == 0 fizz = true end if (n%5) == 0 buzz = true end if fizz && buzz return 'fizzbuzz' elsif fizz && !buzz return 'fizz' elsif buzz && !fizz return 'buzz' else return n end end end
上記のコードをテストすると。。。
# Running tests: .... Finished tests in 0.001967s, 2033.5536 tests/s, 2033.5536 assertions/s. 4 tests, 4 assertions, 0 failures, 0 errors, 0 skips
というわけで合格しました!!
これで本当に完成です。
ただし万全ではないです。仮に 0以下の値が入ったら。。。などというのは考えていません。
最低限のfizzbuzzです。
ちなみに実行ファイルも作ってみました。
fizz_buzz_do.rb
require './fizz_buzz.rb' @fizz_buzz = FizzBuzz.new for i in 1..15 p @fizz_buzz.at i end
結果は
1 2 "fizz" 4 "buzz" "fizz" 7 8 "fizz" "buzz" 11 "fizz" 13 14 "fizzbuzz"
となります。
まとめ
以前もTDDの記事を書きました。
今回はそのためだいぶ要領よくできました。
ちなみに今回のコードはgithubにも上がっているのでチェケラーよろしくお願いします。
nigohiroki/FizzBuzzTestCode · GitHub
個人的にTDDのどんどん出来上がっていく感じが好きです。
rubyはデフォルトでtestunitが入っているのでとっつきやすいと思います。
そんなわけで「もっとよいコードをかけるぜ!!」
などという人がいればどんどんコメントお願いします!!
それでは~