第20回 Ruby 勉強会 @ 関西

投稿者 okkez 2007-11-02 11:52:00 GMT

行ってきた。

第 20 回 Ruby 勉強会

内容は置いといて、初級者レッスンの解答例(answer.pdf)に補足説明を入れておく。 資料は上記サイトでダウンロードしてくださいな。

以下では、配布資料(print.pdf)が手元にあることを前提に進めていきます。

実装するメソッド

  • empty?
    • スタックが空なら true、そうでなければ false を返す。
  • size
    • スタックのサイズを返す。
  • push(val)
    • 引数の値をスタックの一番上に積む。
  • pop
    • スタックの一番上の値を取り除いて返す。 スタックが空の場合は Stack::EmptyStackErrorが発生する。

テスト駆動開発版

TDD でやってくのです。

Step1

  • Stack#empty? のテスト
  • 「新しいスタックの empty? は真」

ということなのでまずはテストを書きます。

test_stack.rb
require 'stack' require 'test/unit' class TestStack < Test::Unit::TestCase def setup @stack = Stack.new end def test_empty? assert(@stack.empty?, 'a new stack is empty.') end end

空っぽのテストなので簡単ですね。

  • Stack#empty? のテストをしたいのでテストメソッド名は test_empty?
  • assert は 第一引数の式が真で無い場合にに失敗する

で、実装。普通は失敗してから Fake it するのですが、print.pdfでは既に失敗してるのでここではいきなり成功しときます。

stack.rb
class Stack def empty? true end end

ここでは成功さえすれば良いのでいきなり true を返します。

Step2

  • Stack#push と Stack#pop のテスト
  • 「新しいスタックに 3 を push して pop すると 3 が返る」

まずテスト。

test_stack.rb
require 'stack' require 'test/unit' class TestStack < Test::Unit::TestCase def setup @stack = Stack.new end def test_empty? assert(@stack.empty?, 'a new stack is empty.') end def test_push_and_pop @stack.push(3) assert_equal(3, @stack.pop, 'pop returns the last value.') end end
  • Stack#push と Stack#pop のテストなのでテストメソッド名は test_push_and_pop
stack.rb
class Stack def empty? true end def push(val) end def pop return 3 end end
  • Stack#push はテストが通るので、まだ空っぽでいい。
  • Stack#pop はテストを通すために 3 を返す。

明らかにテストが不足しているが、まだ書かない。一歩ずつ確実に進んで行く。

Step3

  • Stack#size のテスト
  • 「新しいスタックに 3 を push すると size は 1」

なにがなんでもまずテスト。

test_stack.rb
require 'stack' require 'test/unit' class TestStack < Test::Unit::TestCase def setup @stack = Stack.new end def test_empty? assert(@stack.empty?, 'a new stack is empty.') end def test_push_and_pop @stack.push(3) assert_equal(3, @stack.pop, 'pop returns the last value.') end def test_push_and_size @stack.push(3) assert_equal(1, @stack.size, 'push increments the size.') end end
  • push してから size を確認するのでテストメソッド名は test_push_and_size
  • まだ一個しか push しない
  • 二個 push したらどうなる?とかこの段階では考えない。
stack.rb
class Stack def empty? true end def push(val) end def pop return 3 end def size 1 end end
  • 3 を push したら size が 1 になればいいのだから Stack#size は 1 を返せばいい
  • しつこく Fake it
  • テストを通すことだけ考える。

Step4

  • Stack#size のテスト (2)
  • 「新しいスタックに 3 を push すると size は 1」
  • 「さらに 5 を push すると size は 2」

とりあえずテストを書いてみる。

test_stack.rb
require 'stack' require 'test/unit' class TestStack < Test::Unit::TestCase def setup @stack = Stack.new end def test_empty? assert(@stack.empty?, 'a new stack is empty.') end def test_push_and_pop @stack.push(3) assert_equal(3, @stack.pop, 'pop returns the last value.') end def test_push_and_size @stack.push(3) assert_equal(1, @stack.size, 'push increments the size.') @stack.push(5) assert_equal(2, @stack.size, 'push increments the size.') end end

素直に書けばこうなるはず。

実装コード

stack.rb
class Stack def initialize @size = 0 end def empty? true end def push(val) @size += 1 end def pop return 3 end def size return @size end end
  • Fake it では通らなくなったので、マジメに実装。
  • 放っておいても今のところ大丈夫な部分は放置。

Step5

  • Stack#empty? のテスト (2)
  • 「新しいスタックに 3 を push すると empty? は偽」

いつまでも Stack#empty? が true しか返さないのはまずいので、false を返す場合のテストを書いてみる。

test_stack.rb
require 'stack' require 'test/unit' class TestStack < Test::Unit::TestCase def setup @stack = Stack.new end def test_empty? assert(@stack.empty?, 'a new stack is empty.') end def test_push_and_pop @stack.push(3) assert_equal(3, @stack.pop, 'pop returns the last value.') end def test_push_and_size @stack.push(3) assert_equal(1, @stack.size, 'push increments the size.') @stack.push(5) assert_equal(2, @stack.size, 'push increments the size.') end def test_push_and_empty? @stack.push(3) assert_equal(false, @stack.empty?, 'a stack with data is not empty.') end end

で、テストが失敗するのでテストが通るように実装コードを変更します。

stack.rb
class Stack def initialize @size = 0 end def empty? return @size == 0 end def push(val) @size += 1 end def pop return 3 end def size return @size end end
  • @size == 0 が true/false のいずれかを返すので return します
  • 関係ない部分は弄りません

Step6

  • Stack#pop のテスト (2)
  • 「新しいスタックを pop すると Stack::EmptyStackError が発生する」

誰がなんと言おうがテストコードを先に書きます。

test_stack.rb
require 'stack' require 'test/unit' class TestStack < Test::Unit::TestCase def setup @stack = Stack.new end def test_empty? assert(@stack.empty?, 'a new stack is empty.') end def test_push_and_pop @stack.push(3) assert_equal(3, @stack.pop, 'pop returns the last value.') end def test_push_and_size @stack.push(3) assert_equal(1, @stack.size, 'push increments the size.') @stack.push(5) assert_equal(2, @stack.size, 'push increments the size.') end def test_push_and_empty? @stack.push(3) assert_equal(false, @stack.empty?, 'a stack with data is not empty.') end def test_empty_pop assert_raise(Stack::EmptyStackError, 'to pop a empty stack raise an error.') {@stack.pop} end end
  • 空のスタックに対して pop するのでテストメソッドは test_empty_pop
  • assert_raise はブロックに例外が発生する可能性のある処理を記述する

実装コード

stack.rb
class Stack class EmptyStackError < StandardError; end def initialize @size = 0 end def empty? return @size == 0 end def push(val) @size += 1 end def pop raise EmptyStackError if empty? return 3 end def size return @size end end
  • Stack#pop を修正。
    • Stack#empty? が true の場合に例外を発生させる
  • 関係ない部分は意地でも書き換えない

Step7

  • Stack#pop のテスト (3)
  • 「新しいスタックに 3 を push して 5 を push して pop すると size は 1」

例によってテストを先に書きましょう。

test_stack.rb
require 'stack' require 'test/unit' class TestStack < Test::Unit::TestCase def setup @stack = Stack.new end def test_empty? assert(@stack.empty?, 'a new stack is empty.') end def test_push_and_pop @stack.push(3) assert_equal(3, @stack.pop, 'pop returns the last value.') end def test_push_and_size @stack.push(3) assert_equal(1, @stack.size, 'push increments the size.') @stack.push(5) assert_equal(2, @stack.size, 'push increments the size.') end def test_push_and_empty? @stack.push(3) assert_equal(false, @stack.empty?, 'a stack with data is not empty.') end def test_empty_pop assert_raise(Stack::EmptyStackError, 'to pop a empty stack raise an error.') {@stack.pop} end def test_push_push_pop_and_size @stack.push(3) @stack.push(5) @stack.pop assert_equal(1, @stack.size, 'pop decrements the size.') end end
  • テストメソッド名は test_push_push_pop_and_size
  • 特別なことはしていません

実装コード

stack.rb
class Stack class EmptyStackError < StandardError; end def initialize @size = 0 end def empty? return @size == 0 end def push(val) @size += 1 end def pop raise EmptyStackError if empty? @size -= 1 return 3 end def size return @size end end
  • 関係ない部分は弄らない、と

Step8

  • Stack#pop のテスト (4)
  • 「新しいスタックに 3 を push して 5 を pushして pop すると 5 が返る」

そろそろ佳境ですが、テストを先に書きます。

test_stack.rb
require 'stack' require 'test/unit' class TestStack < Test::Unit::TestCase def setup @stack = Stack.new end def test_empty? assert(@stack.empty?, 'a new stack is empty.') end def test_push_and_pop @stack.push(3) assert_equal(3, @stack.pop, 'pop returns the last value.') end def test_push_and_size @stack.push(3) assert_equal(1, @stack.size, 'push increments the size.') @stack.push(5) assert_equal(2, @stack.size, 'push increments the size.') end def test_push_and_empty? @stack.push(3) assert_equal(false, @stack.empty?, 'a stack with data is not empty.') end def test_empty_pop assert_raise(Stack::EmptyStackError, 'to pop a empty stack raise an error.') {@stack.pop} end def test_push_push_pop_and_size @stack.push(3) @stack.push(5) @stack.pop assert_equal(1, @stack.size, 'pop decrements the size.') end def test_push_push_and_pop @stack.push(3) @stack.push(5) assert_equal(5, @stack.pop, 'pop returns the last value.') end end

実装コード

stack.rb
class Stack class EmptyStackError < StandardError; end def initialize @size = 0 @values = Array.new end def empty? return @size == 0 end def push(val) @size += 1 @values[@size - 1] = val end def pop raise EmptyStackError if empty? val = @values[@size - 1] @size -= 1 return val end def size return @size end end

外から見た動作ではスタックぽく動くようになりましたね。

さて、 irb で中身を覗いてみましょう。

irb(main):001:0> load 'stack.rb'
=> true
irb(main):002:0> s = Stack.new
=> #<Stack:0x2ba04c1d04a8 @values=[], @size=0>
irb(main):003:0> s.push 1
=> 1
irb(main):004:0> s.push 2
=> 2
irb(main):005:0> s.push 3
=> 3
irb(main):006:0> s.inspect
=> "#<Stack:0x2ba04c1d04a8 @values=[1, 2, 3], @size=3>"
irb(main):007:0> s.pop
=> 3
irb(main):008:0> s.pop
=> 2
irb(main):009:0> s.pop
=> 1
irb(main):010:0> s.inspect
=> "#<Stack:0x2ba04c1d04a8 @values=[1, 2, 3], @size=0>"
irb(main):011:0>

@values の中身が Stack#pop しても消えませんね。これはスタックとしては拙いのでリファクタリングしましょう。

というわけで次回に続く。

カテゴリ  | タグ ,  | コメントなし | トラックバックなし

コメント

トラックバック

トラックバックリンク:
http://typo.okkez.net/trackbacks?article_id=26

コメントは許可されていません