マスターの導きを受けつつ、TDD で Ruby を学びながら悟りを開く - Koans

Written by @dr_taka_n at 2011/08/14 23:55 []

Koans とは

クイズ形式で Ruby を学ぶ Koans。その手法がなかなか凝っている。

koans

The goal is to learn the Ruby language, syntax, structure, and some common functions and libraries. We also teach you culture. Testing is not just something we pay lip service to, but something we live. It is essential in your quest to learn and do great things in the language.

Koans は Ruby 言語の文法、構造、そして、幾つかの共通関数やライブラリを学ぶことをゴールとしたもので、TDD の手法を用いてそれを実現している。

path_to_enlightenment.rb というファイルを起動することで、あるテストケースがカルマにダメージを与えたとのメッセージが表示され、マスターから助言を授かることになる。
表示されるメッセージを頼りにテストケースをパスするように修正していくことで、次の新たな設問(新たなカルマへのダメージ)へと進み、30ちょっとのカテゴリの計272問(2010/12/23版)の設問をクリアすることで悟りをひらけることになる。

設問をクリアしていく過程は以下のようになる。
例えば2番目の設問は以下の通りとなっている。

koans2

マスターが「君はまだ悟りをひらいていない」と言っている。”The answers you seek…” ということで “This shoud be true – Please fix this” とある。
「please meditate on the following code:」ということで、about_asserts.rb:16:in test_assert_with_message’` が指定されているので、該当ファイルの該当行を確認すると、

  # Enlightenment may be more easily achieved with appropriate
  # messages.
  def test_assert_with_message
    assert false, "This should be true -- Please fix this"  # line: 16
  end

assert が false となっている。当然これではテストは通らないので、true に書き変えて保存する。

再度 path_to_enlightenment.rb を起動してあげると、

$ ruby path_to_enlightenment.rb 
AboutAsserts#test_assert_truth has expanded your awareness.
AboutAsserts#test_assert_with_message has expanded your awareness.
AboutAsserts#test_assert_equality has damaged your karma.

The Master says:
  You have not yet reached enlightenment.
  You are progressing. Excellent. 2 completed.

The answers you seek...
  Failed assertion, no message given.

Please meditate on the following code:
  /home/hoge/Dropbox/work/koans/about_asserts.rb:25:in `test_assert_equality'

learn the rules so you know how to break them properly
your path thus far [.X________________________________________________] 2/274

次の設問へと続く。

基本は、テストケースのアサーションの期待値を埋めていくことで設問に答えていくことになる。

メッセージ(“The ansers you seek…“)に既に答えが出てしまってはいるが、そこはじっくり読まずに、テストケースとテストメソッド名の方に集中して何を学ばせようとしているのか頭の片隅に置きながら期待値を考えていると、よく考えられた設問になっているなぁと感心してしまう。

最初の方こそ上記のようなホントに初歩的な設問だが、徐々に内容が濃くなってくるので、なかなか良い復習、勉強になる。

また、一気に 274 問実施するのは流石にキツイので、できる時間に少しづつ実施することが可能な作りになっているのもうれしい。何問目まで回答できているかは .path_progress というファイルが覚えておいてくれているので、特に意識することなく、path_to_enlightenment.rb を起動すれば前回の設問から続きを行うことができる。

watchr を使って回答に集中

設問に答えては path_to_enlightenment.rb を起動するというのはメンドイ。
自動化しておく。

watchr gem を使うとファイルを監視下におき、編集された際に実行させたいことを定義することができる。 回答を記述するファイルを編集するたびに自動的に path_to_enlightenment.rb が実行されるようにしておく。

watchr をインストールしていない場合はインストールしておく。

$ sudo gem install watchr

path_to_enlightenment.rb と同じディレクトリに以下のファイルを用意しておく。

koans.watchr:

def run
  system("clear; ruby path_to_enlightenment.rb")
end

watch('.*\.rb$') { run }
run

watchr を起動しておく。

$ watchr koans.watchr

これで回答を行うファイルを編集する度にコンソールがリフレッシュされ path_to_enlightenment.rb の結果を確認することができる。

triangle.rb

大半は assertion の期待値を埋めていくのだが、テストケースを通す為の関数を書く設問もある。

以下の2問。

  • 設問その1:about_triangle_project.rb
class AboutTriangleProject < EdgeCase::Koan
  def test_equilateral_triangles_have_equal_sides
    assert_equal :equilateral, triangle(2, 2, 2)
    assert_equal :equilateral, triangle(10, 10, 10)
  end

  def test_isosceles_triangles_have_exactly_two_sides_equal
    assert_equal :isosceles, triangle(3, 4, 4)
    assert_equal :isosceles, triangle(4, 3, 4)
    assert_equal :isosceles, triangle(4, 4, 3)
    assert_equal :isosceles, triangle(10, 10, 2)
  end

  def test_scalene_triangles_have_no_equal_sides
    assert_equal :scalene, triangle(3, 4, 5)
    assert_equal :scalene, triangle(10, 11, 12)
    assert_equal :scalene, triangle(5, 4, 2)
  end
end
  • 設問その2:about_triangle_project_2.rb
class AboutTriangleProject2 < EdgeCase::Koan
  # The first assignment did not talk about how to handle errors.
  # Let's handle that part now.
  def test_illegal_triangles_throw_exceptions
    assert_raise(TriangleError) do triangle(0, 0, 0) end
    assert_raise(TriangleError) do triangle(3, 4, -5) end
    assert_raise(TriangleError) do triangle(1, 1, 3) end
    assert_raise(TriangleError) do triangle(2, 4, 2) end
 end
end

設問その1のテストの要件を満たす関数を用意し、かつ、設問その2の制約もパスする必要がある。

答えは提供されていない(見つけられなかった。。)ので、ベストアンサーかどうかはわからないが、以下の関数を書くことでテストをパスしている。
ツッコミありましたらご指摘願いたい。

triangle.rb:

def triangle(a, b, c)
  raise TriangleError.new if [a, b, c].any? { |e| e <= 0 }
  raise TriangleError.new if ((a + b) <= c) || ((a + c) <= b) || ((b + c ) <= a)

  case [a, b, c].uniq.size
    when 1 then :equilateral
    when 2 then :isosceles
    else :scalene
  end
end
blog comments powered by Disqus