[]RSpecの拡張

RSpecのスタックの例だと、スタックの仕様を「空」、「要素が1つだけ」、「ほとんど満杯」、「満杯」の4つのコンテキストで表しているんだけど、これだと要素が2や3のときの場合がなくて、仕様としては不十分じゃないだろうか。これはたぶんスタックの振る舞いが変わる4つの境界をコンテキストとして選んでいるからであって、仕様というよりはテストケースっぽいような。

仕様を記述するのであれば、仕様と上のような仕様の具体例を分けて考えたいと思う。たとえばスタックであれば「空」、「満杯」、「空でもなく満杯でもない」の三つをコンテキストする。この場合のコンテキストは抽象的で、一つ以上の具体例に対応する。「空」の場合は一つだけだけど、「満杯」や「空でもなく満杯でもない」は、いろいろな具体例がありえる。要素が一つだけのスタック、ほとんど満杯のスタックは、「空でもなく満杯でもない」コンテキストの具体例の一種だ。

試しに、一つのコンテキストで複数の具体例を表せるように、RSpecを拡張してみた。add_instanceで具体例を追加できる。specifyはcontextの具体例の数だけ繰り返し実行する。スタックの場合、下のような感じになるけど、もっときれいに仕様っぽく書けないかな。そもそも仕様っぽさってなんだって話になってくるのか。。


require File.dirname(__FILE__) + "/stack"

module Spec
module Runner
class Context
def add_instance(&block)
@instances ||= []
@instances << block
end

alias org_run run

def run(reporter, dry_run=false)
if (@instances == nil)
org_run(reporter, dry_run)
else
reporter.add_context(@name)
@instances.each do |instance|
@specifications.each do |specification|
specification.run(reporter, instance, @teardown_block, dry_run)
end
end
end
end
end
end
end


=begin
Stackの仕様を、スタックの状態に応じて三つのコンテキストに分ける
(1)空
(2)空でも満杯でもない
(3)満杯
=end

context "空" do

# コンテキストのインスタンスは一つだけ
add_instance do
@stack = Stack.new
end

specify "should accept an item when sent push" do
lambda { @stack.push Object.new }.should_not_raise
end

specify "should complain when sent top" do
lambda { @stack.top }.should_raise StackUnderflowError
end

specify "should complain when sent pop" do
lambda { @stack.pop }.should_raise StackUnderflowError
end
end

context "空でもなく満杯でもない" do

# コンテキストのインスタンスを複数定義
# (1..9).each do |size|
[1,9].each do |size|
add_instance do
@stack = Stack.new
size.times do |i|
@stack.push (i+1)
end
@last_push_member = size
end
end

specify "should accept an item when sent push" do
lambda { @stack.push Object.new }.should_not_raise
end

specify "should return top when sent top" do
@stack.top.should_be @last_push_member
end

specify "should not remove top when sent top" do
@stack.top.should_be @last_push_member
@stack.top.should_be @last_push_member
end

specify "should return top when sent pop" do
@stack.pop.should_be @last_push_member
end

specify "should remove top when sent pop" do
@last_push_member.times do
@stack.pop
end
lambda { @stack.pop }.should_raise StackUnderflowError
end
end

context "満杯" do

# コンテキストのインスタンスを一つだけ定義
add_instance do
@stack = Stack.new
(1..10).each { |i| @stack.push i }
end

specify "should complain on push" do
lambda { @stack.push Object.new }.should_raise StackOverflowError
end

specify "should return top when sent top" do
@stack.top.should_be 10
end

specify "should not remove top when sent top" do
@stack.top.should_be 10
@stack.top.should_be 10
end

specify "should return top when sent pop" do
@stack.pop.should_be 10
end

specify "should remove top when sent pop" do
@stack.pop.should_be 10
@stack.pop.should_be 9
end
end