h2. A Simple Stack - eliminating duplication h3. What to specify next? Picking the next thing to specify is always a challenge (just as picking the next thing to test is in TDD). Ideally, you want to specify the simplest thing that you can imagine implementing. Of course, thinking about implementation seems to fly in the face of a core tenet of TDD and BDD: focus on interface - focus on behaviour. So you have a couple of opposing forces and notions guiding this decision. The bottom line is that it's not that important which one you pick next. The important thing is that as soon as you recognize that you've headed down the wrong path (one which requires a lot of implementation to meet the newest specification), roll back immediately and pick a different "next step". For our problem, there seem to be (at least) two logical potential next steps. We know that pushing on to an empty stack results in the stack no longer being empty. So one logical next step might be to specify how an empty stack behaves when sending it a different message. Another might be to send push to a non-empty stack. But what I'd really like to know is what happened to the item we just pushed onto the stack! To specify that, we have to move diagonally a bit. We need to specify the stack's response to a new message starting in a new state - a new context. We're going to start with a stack with one item already pushed onto it and specify what happens when you send it 'top'. Why top and not pop? In this case, top is simpler than pop - top just returns the top item, while pop also removes it from the stack. h3. Send top to a stack with one item Start by adding a new context and spec in stack_spec.rb. context "A stack with one item" do specify "should return top when you send it 'top'" do end end
$ spec stack_spec.rb -f s

A new stack
- should be empty

An empty stack
- should not be empty after 'push'

A stack with one item
- should return top when you send it 'top'

Finished in 0.000693 seconds

3 contexts, 4 specifications, 0 failures
Add the setup... context "A stack with one item" do setup do @stack = Stack.new @stack.push "one item" end specify "should return top when you send it 'top'" do end end
$ spec stack_spec.rb -f s

A new stack
- should be empty

An empty stack
- should not be empty after 'push'

A stack with one item
- should return top when you send it 'top'

Finished in 0.000733 seconds

3 contexts, 4 specifications, 0 failures
No change in the output. Add the expectation... context "A stack with one item" do setup do @stack = Stack.new @stack.push "one item" end specify "should return top when you send it 'top'" do @stack.top.should_equal "one item" end end
$ spec stack_spec.rb -f s

A new stack
- should be empty

An empty stack
- should not be empty after 'push'

A stack with one item
- should return top when you send it 'top' (FAILED - 1)

1)
NoMethodError in 'A stack with one item should return top when you send it 'top''
undefined method `top' for #
./stack_spec.rb:35:in `should return top when you send it 'top''

Finished in 0.000863 seconds

3 contexts, 4 specifications, 1 failure
The NoMethodError tells us to add the 'top' method. class Stack ... def top end end
$ spec stack_spec.rb -f s

A new stack
- should be empty

An empty stack
- should not be empty after 'push'

A stack with one item
- should return top when you send it 'top' (FAILED - 1)

1)
ExpectationNotMetError in 'A stack with one item should return top when you send it 'top''
nil should equal "one item"
./stack_spec.rb:35:in `should return top when you send it 'top''

Finished in 0.000867 seconds

3 contexts, 4 specifications, 1 failure
The ExpectationNotMetError tells us to implement something. What's the simplest thing we can add to meet all of the current expectations? class Stack ... def top "one item" end end
$ spec stack_spec.rb -f s

A new stack
- should be empty

An empty stack
- should not be empty after 'push'

A stack with one item
- should return top when you send it 'top'

Finished in 0.000744 seconds

3 contexts, 4 specifications, 0 failures
Again we have duplication between the specs and the code ("one item") and we want to get rid of it. This time, however, we have enough specs and entry points that there is actually something to refactor to. If you're not familiar with refactoring, check out http://www.refactoring.com/. We'll do some now, but we won't be examining the steps too deeply. The basic idea is that we want to change structure/implementation without affecting behaviour. Running the specs and watching them pass between each step proves that the behaviour is in tact. First, we save the item we add and return that from top ... class Stack def initialize @empty = true end def empty? @empty end def push item @empty = false @item = item end def top @item end end
$ spec stack_spec.rb -f s

A new stack
- should be empty

An empty stack
- should not be empty after 'push'

A stack with one item
- should return top when you send it 'top'

Finished in 0.000846 seconds

3 contexts, 4 specifications, 0 failures
Now let's use the item to determine "emptiness"... class Stack def initialize @empty = true end def empty? @item.nil? end def push item @empty = false @item = item end def top @item end end
$ spec stack_spec.rb -f s

A new stack
- should be empty

An empty stack
- should not be empty after 'push'

A stack with one item
- should return top when you send it 'top'

Finished in 0.000764 seconds

3 contexts, 4 specifications, 0 failures
Now we can eliminate @empty (which means we can eliminate initialize as well). class Stack def empty? @item.nil? end def push item @item = item end def top @item end end
$ spec stack_spec.rb -f s

A new stack
- should be empty

An empty stack
- should not be empty after 'push'

A stack with one item
- should return top when you send it 'top'

Finished in 0.000737 seconds

3 contexts, 4 specifications, 0 failures
So this time we were able to refactor, and in doing so not only reduce the duplication but also reduce the size of the stack class. Sweet. To be continued... Previous | Next