第20回 Ruby 勉強会 @ 関西
投稿者 okkez
行ってきた。
内容は置いといて、初級者レッスンの解答例(answer.pdf)に補足説明を入れておく。 資料は上記サイトでダウンロードしてくださいな。
以下では、配布資料(print.pdf)が手元にあることを前提に進めていきます。
実装するメソッド
- empty?
- スタックが空なら true、そうでなければ false を返す。
- size
- スタックのサイズを返す。
- push(val)
- 引数の値をスタックの一番上に積む。
- pop
- スタックの一番上の値を取り除いて返す。 スタックが空の場合は Stack::EmptyStackErrorが発生する。
テスト駆動開発版
TDD でやってくのです。
Step1
- Stack#empty? のテスト
- 「新しいスタックの empty? は真」
ということなのでまずはテストを書きます。
test_stack.rbrequire '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.rbclass Stack def empty? true end end
ここでは成功さえすれば良いのでいきなり true を返します。
Step2
- Stack#push と Stack#pop のテスト
- 「新しいスタックに 3 を push して pop すると 3 が返る」
まずテスト。
test_stack.rbrequire '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.rbclass 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.rbrequire '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.rbclass 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.rbrequire '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.rbclass 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.rbrequire '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.rbclass 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.rbrequire '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.rbclass 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.rbrequire '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.rbclass 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.rbrequire '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.rbclass 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 しても消えませんね。これはスタックとしては拙いのでリファクタリングしましょう。
というわけで次回に続く。


