第 20 回 Ruby 勉強会@関西 初級者レッスン演習課題解答例(RSpec版)
投稿者 okkez
Test::Unit版 と同じことを RSpec でやってみた。
第 20 回 Ruby 勉強会@関西 に解答例(answer.pdf)がアップされていますがそれの捕捉説明的なものです。前回と同様、配布資料(print.pdf)が手元にある前提で進めます。
Test::Unit 版とは書いた時期が違うので微妙に実装が違っていたりします。
ちなみに RSpec を使って書くのは初めてです。RSpec についてはるびまの記事を参照してください。
なお、stack_spec.rb は、そのとき注目している機能に関係のある部分のみ掲載します。
実装するメソッド
- empty?
- スタックが空なら true、そうでなければ false を返す。
- size
- スタックのサイズを返す。
- push(val)
- 引数の値をスタックの一番上に積む。
- pop
- スタックの一番上の値を取り除いて返す。スタックが空の場合は Stack::EmptyStackErrorが発生する。
Step1
- Stack#empty? のテスト
- 「新しいスタックの empty? は真」
stack_spec.rbrequire 'stack' describe Stack, "when empty" do before do @empty_stack = Stack.new end it "should be empty" do @empty_stack.should be_empty end after do @empty_stack = nil end end
Stack が空のときの動作を記述。 * spec -c で実行すると色つきで結果が表示される
stack.rbclass Stack def empty? true end end
- とりあえず Test::Unit 版と同じように Fake it してみた
Step2
- Stack#push と Stack#pop のテスト
- 「新しいスタックに 3 を push して pop すると 3 が返る」
stack_spec.rbdescribe Stack, "when push 3" do before do @stack = Stack.new @stack.push(3) end it "should pop 3" do @stack.pop.should == 3 end after do @stack = nil end end
- describe の引数に書いた内容を before のブロックに記述する
- it 〜 end に実際にチェックしたい内容を記述する
- after のブロックに後始末の処理を記述する
- before と after は省略可能
stack.rbclass Stack def empty? true end def push(val) end def pop 3 end end
- Test::Unit 版と同じように Fake it
Step3
- Stack#size のテスト
- 「新しいスタックに 3 を push すると size は 1」
stack_spec.rbdescribe Stack, "when push 3" do before do @stack = Stack.new @stack.push(3) end it "should pop 3" do @stack.pop.should == 3 end it "should size 1" do @stack.size.should == 1 end after do @stack = nil end end
- Step2 と事前準備の内容は同じなので同じ describe ブロック内に it 〜 end を追加する
stack.rbclass Stack def empty? true end def push(val) end def pop 3 end def size 1 end end
- 実装はひたすら Fake it
Step4
- Stack#size のテスト (2)
- 「新しいスタックに 3 を push すると size は 1」
- 「さらに 5 を push すると size は 2」
stack_spec.rbdescribe Stack, "when push 3 and 5" do before do @stack = Stack.new @stack.push(3) @stack.push(5) end it "should size 2" do @stack.size.should == 2 end end
- 前のステップとは状況が違うので新しい describe ブロックを用意した
stack.rbclass Stack def initialize @size = 0 end def empty? true end def push(val) @size += 1 end def pop 3 end def size @size end end
- Fake it のままでは通らないので、ちょっとまじめに実装。
Step5
- Stack#empty? のテスト (2)
- 「新しいスタックに 3 を push すると empty? は偽」
stack_spec.rbdescribe Stack, "when push 3" do before do @stack = Stack.new @stack.push(3) end it "should pop 3" do @stack.pop.should == 3 end it "should size 1" do @stack.size.should == 1 end it "should not be empty" do @stack.should_not be_empty end after do @stack = nil end end
- 事前準備の状況が Step2, Step3 と同じなので同じ describe に振舞を追加
stack.rbclass Stack def initialize @size = 0 end def empty? size == 0 end def push(val) @size += 1 end def pop 3 end def size @size end end
Step6
- Stack#pop のテスト (2)
- 「新しいスタックを pop すると Stack::EmptyStackError が発生する」
stack_spec.rbdescribe Stack, "when empty" do before do @empty_stack = Stack.new end it "should be empty" do @empty_stack.should be_empty end it "should raise Stack::EmptyStackError" do lambda{ @empty_stack.pop }.should raise_error Stack::EmptyStackError end after do @empty_stack = nil end end
stack.rbclass Stack class EmptyStackError < StandardError end def initialize @size = 0 end def empty? size == 0 end def push(val) @size += 1 end def pop raise EmptyStackError if empty? 3 end def size @size end end
Step7
- Stack#pop のテスト (3)
- 「新しいスタックに 3 を push して 5 を push して pop すると size は 1」
stack_spec.rbdescribe Stack, "when push 3 and 5, then pop" do before do @stack = Stack.new @stack.push(3) @stack.push(5) @stack.pop end it "should size 1" do @stack.size.should == 1 end after do @stack = nil end end
stack.rbclass Stack class EmptyStackError < StandardError end def initialize @size = 0 end def empty? size == 0 end def push(val) @size += 1 end def pop raise EmptyStackError if empty? @size -= 1 3 end def size @size end end
Step8
- Stack#pop のテスト (4)
- 「新しいスタックに 3 を push して 5 を pushして pop すると 5 が返る」
stack_spec.rbdescribe Stack, "when push 3 and 5" do before do @stack = Stack.new @stack.push(3) @stack.push(5) end it "should size 2" do @stack.size.should == 2 end it "should pop 5" do @stack.pop.should == 5 end after do @stack = nil end end
stack.rbclass Stack class EmptyStackError < StandardError end def initialize @size = 0 @stack = [] end def empty? size == 0 end def push(val) @size += 1 @stack.push(val) end def pop raise EmptyStackError if empty? @size -= 1 @stack.pop end def size @size end end
Step9
最後にリファクタリングします。
stack_spec.rbrequire 'stack' describe Stack, "when empty" do before do @empty_stack = Stack.new end it "should be empty" do @empty_stack.should be_empty end it "should raise Stack::EmptyStackError" do lambda{ @empty_stack.pop }.should raise_error Stack::EmptyStackError end after do @empty_stack = nil end end describe Stack, "when push 3" do before do @stack = Stack.new @stack.push(3) end it "should pop 3" do @stack.pop.should == 3 end it "should size 1" do @stack.size.should == 1 end it "should not be enpty" do @stack.should_not be_empty end after do @stack = nil end end describe Stack, "when push 3 and 5" do before do @stack = Stack.new @stack.push(3) @stack.push(5) end it "should size 2" do @stack.size.should == 2 end it "should pop 5" do @stack.pop.should == 5 end after do @stack = nil end end describe Stack, "when push 3 and 5, then pop" do before do @stack = Stack.new @stack.push(3) @stack.push(5) @stack.pop end it "should size 1" do @stack.size.should == 1 end after do @stack = nil end end
- 上記の仕様を満たすように実装コードを書き換えます。
- 本来はちょっとずつ書き換えるべきですが、最終結果のみ書いておきます
stack.rbrequire 'forwardable' class Stack extend Forwardable def_delegators(:@stack, :empty?, :push, :size) def initialize @stack = [] end def pop raise EmptyStackError if empty? @stack.pop end class EmptyStackError < StandardError end end
- forwardable ライブラリを使用するとメソッドを指定して特定のオブジェクトに委譲できる
- 使用方法はリファレンスマニュアルを確認してください


