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 object we just pushed! 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 object, while pop also removes it from the stack. h3. Send top to a stack with one item Start by adding a new context in stack_spec.rb. Remember, the context is an object (or set of objects) in a known state, so we'll go ahead and add the setup right away 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 keep its mouth shut when you send it 'push'
- 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 keep its mouth shut when you send it 'push'
- 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 keep its mouth shut when you send it 'push'
- 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 keep its mouth shut when you send it 'push'
- 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 keep its mouth shut when you send it 'push'
- 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
So now we've hard coded something even more obviously wrong than before. True/false/empty, etc - that was all a bit more subtle. This case is much more obvious. There are a couple of ways to view the problem. One is that we've got this hard coded value. The bigger problem is that we now have duplication between the specs and the code. We want to get rid of it. How do you eliminate duplication? Refactor! And since we have a suite (albeit small) of passing specifications, we can now refactor safely. 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 keep track of the item we add... 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 keep its mouth shut when you send it 'push'
- 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 that 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 keep its mouth shut when you send it 'push'
- 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 keep its mouth shut when you send it 'push'
- 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
You may wonder why we chose to refactor at this point, but we didn't the first time we had duplication between the tests and code. Remember the first step we took? It was to return true for empty?. Though more subtle than "one item", that was duplication between the specs and the code. The problem was that we didn't have any other methods on the stack that we could use. We would have had to contrive implementation that we had no specification for. At this point, however, we were able to make use of data we were passing into the the stack from the specification, so we were in a position to change things without adding things we hadn't specified yet. To be continued... Previous | Next