rspec 0.5.3 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. data/CHANGES +57 -32
  2. data/EXAMPLES.rd +0 -0
  3. data/Rakefile +22 -21
  4. data/bin/spec +9 -11
  5. data/doc/README +1 -3
  6. data/doc/plugin/syntax.rb +27 -5
  7. data/doc/src/core_team.page +22 -0
  8. data/doc/src/default.css +11 -11
  9. data/doc/src/default.template +0 -1
  10. data/doc/src/documentation/index.page +183 -8
  11. data/doc/src/documentation/meta.info +7 -7
  12. data/doc/src/documentation/mocks.page +168 -109
  13. data/doc/src/documentation/underscores.page +20 -0
  14. data/doc/src/examples.page +2 -1
  15. data/doc/src/images/David_and_Aslak.jpg +0 -0
  16. data/doc/src/images/Whats_That_Dude.jpg +0 -0
  17. data/doc/src/index.page +70 -3
  18. data/doc/src/meta.info +18 -11
  19. data/doc/src/tools/index.page +40 -134
  20. data/doc/src/tools/meta.info +9 -3
  21. data/doc/src/tools/rails.page +3 -1
  22. data/doc/src/tools/rake.page +20 -3
  23. data/doc/src/tools/rcov.page +19 -0
  24. data/doc/src/tools/spec.page +99 -0
  25. data/doc/src/tools/test2rspec.page +2 -4
  26. data/doc/src/tutorials/index.page +52 -0
  27. data/doc/src/tutorials/meta.info +31 -0
  28. data/doc/src/tutorials/notes.txt +252 -0
  29. data/doc/src/tutorials/stack.rb +11 -0
  30. data/doc/src/tutorials/stack_01.page +224 -0
  31. data/doc/src/tutorials/stack_02.page +180 -0
  32. data/doc/src/tutorials/stack_03.page +291 -0
  33. data/doc/src/tutorials/stack_04.page +203 -0
  34. data/doc/src/tutorials/stack_04.page.orig +123 -0
  35. data/doc/src/tutorials/stack_05.page +90 -0
  36. data/doc/src/tutorials/stack_05.page.orig +124 -0
  37. data/doc/src/tutorials/stack_06.page +359 -0
  38. data/doc/src/tutorials/stack_06.page.orig +359 -0
  39. data/doc/src/tutorials/stack_spec.rb +41 -0
  40. data/examples/airport_spec.rb +4 -4
  41. data/examples/{spec_framework_spec.rb → bdd_framework_spec.rb} +6 -7
  42. data/examples/mocking_spec.rb +0 -5
  43. data/examples/stack_spec.rb +6 -7
  44. data/examples/sugar_spec.rb +14 -0
  45. data/lib/spec/api.rb +5 -2
  46. data/lib/spec/api/helper/should_base.rb +17 -22
  47. data/lib/spec/api/helper/should_helper.rb +4 -3
  48. data/lib/spec/api/helper/should_negator.rb +3 -2
  49. data/lib/spec/api/mocks/argument_expectation.rb +104 -0
  50. data/lib/spec/api/{mock.rb → mocks/message_expectation.rb} +47 -96
  51. data/lib/spec/api/mocks/mock.rb +63 -0
  52. data/lib/spec/api/mocks/order_group.rb +21 -0
  53. data/lib/spec/api/sugar.rb +47 -0
  54. data/lib/spec/rake/rcov_verify.rb +45 -0
  55. data/lib/spec/rake/spectask.rb +41 -56
  56. data/lib/spec/runner.rb +4 -1
  57. data/lib/spec/runner/backtrace_tweaker.rb +24 -3
  58. data/lib/spec/runner/base_text_formatter.rb +28 -0
  59. data/lib/spec/runner/context.rb +21 -18
  60. data/lib/spec/runner/context_runner.rb +20 -31
  61. data/lib/spec/runner/execution_context.rb +3 -3
  62. data/lib/spec/runner/kernel_ext.rb +10 -1
  63. data/lib/spec/runner/option_parser.rb +32 -14
  64. data/lib/spec/runner/progress_bar_formatter.rb +21 -0
  65. data/lib/spec/runner/rdoc_formatter.rb +15 -5
  66. data/lib/spec/runner/reporter.rb +100 -0
  67. data/lib/spec/runner/specdoc_formatter.rb +20 -0
  68. data/lib/spec/runner/specification.rb +42 -22
  69. data/lib/spec/version.rb +1 -1
  70. data/test/rcov/rcov_testtask.rb +1 -0
  71. data/test/spec/api/duck_type_test.rb +4 -4
  72. data/test/spec/api/helper/raising_test.rb +37 -17
  73. data/test/spec/api/{mock_arg_constraints_test.rb → mocks/mock_arg_constraints_test.rb} +10 -4
  74. data/test/spec/api/mocks/mock_ordering_test.rb +62 -0
  75. data/test/spec/api/{mock_test.rb → mocks/mock_test.rb} +30 -7
  76. data/test/spec/api/mocks/null_object_test.rb +31 -0
  77. data/test/spec/api/sugar_test.rb +71 -0
  78. data/test/spec/runner/backtrace_tweaker_test.rb +52 -4
  79. data/test/spec/runner/context_runner_test.rb +41 -21
  80. data/test/spec/runner/context_test.rb +60 -32
  81. data/test/spec/runner/execution_context_test.rb +4 -3
  82. data/test/spec/runner/failure_dump_test.rb +92 -0
  83. data/test/spec/runner/kernel_ext_test.rb +1 -2
  84. data/test/spec/runner/option_parser_test.rb +48 -28
  85. data/test/spec/runner/progress_bar_formatter_test.rb +48 -0
  86. data/test/spec/runner/rdoc_formatter_test.rb +31 -4
  87. data/test/spec/runner/reporter_test.rb +103 -0
  88. data/test/spec/runner/specdoc_formatter_test.rb +50 -0
  89. data/test/spec/runner/specification_test.rb +49 -11
  90. data/test/test_helper.rb +1 -4
  91. metadata +46 -15
  92. data/doc/src/community.page +0 -7
  93. data/doc/src/documentation/api.page +0 -185
  94. data/doc/src/why_rspec.page +0 -7
  95. data/examples/empty_stack_spec.rb +0 -22
  96. data/examples/team_spec.rb +0 -30
  97. data/lib/spec/api/duck_type.rb +0 -16
  98. data/lib/spec/runner/simple_text_reporter.rb +0 -88
  99. data/test/rcov/rcov_verify.rb +0 -28
  100. data/test/spec/runner/simple_text_reporter_test.rb +0 -123
@@ -0,0 +1,359 @@
1
+ h2. A Simple Stack - Pop - IN PROGRESS - DISREGARD THIS PAGE
2
+
3
+ So what next? We could specify a new context, or we could specify new messages in the existing contexts. How about 'pop'? We'll start with the empty stack:
4
+
5
+ <ruby>
6
+ context "An empty stack" do
7
+
8
+ setup do
9
+ @stack = Stack.new
10
+ end
11
+
12
+ specify "should keep its mouth shut when you send it 'push'" do
13
+ lambda { @stack.push Object.new }.should.not.raise Exception
14
+ end
15
+
16
+ specify "should raise a StackUnderflowError when you send it 'top'" do
17
+ lambda { @stack.top }.should.raise StackUnderflowError
18
+ end
19
+
20
+ specify "should raise a StackUnderflowError when you send it 'pop'" do
21
+ lambda { @stack.pop }.should.raise StackUnderflowError
22
+ end
23
+
24
+ end
25
+ </ruby>
26
+
27
+ ... run the specs ...
28
+
29
+ <pre>
30
+ $ spec stack_spec.rb -v
31
+
32
+ An empty stack
33
+ - should keep its mouth shut when you send it 'push'
34
+ - should raise a StackUnderflowError when you send it 'top'
35
+ - should raise a StackUnderflowError when you send it 'pop' (FAILED - 1)
36
+
37
+ A stack with one item
38
+ - should keep its mouth shut when you send it 'push'
39
+ - should return top when you send it 'top'
40
+
41
+
42
+ 1)
43
+ ExpectationNotMetError in 'An empty stack should raise a StackUnderflowError when you send it 'pop''
44
+ <Proc> should raise <StackUnderflowError> but raised #<NoMethodError: undefined method `pop' for #<Stack:0x36f980>>
45
+ ./stack_spec.rb:18:in `should raise a StackUnderflowError when you send it 'pop''
46
+
47
+ Finished in 0.000967 seconds
48
+
49
+ 2 contexts, 5 specifications, 1 failure
50
+ </pre>
51
+
52
+ The messages here are slightly different than what we've seen so far. Back when we specified sending 'top' to an empty stack, we had already specified sending 'top' to a stack with one element, so we didn't get the <code>NoMethodError</code> that we get here. And since we were expecting a <code>StackUnderflowError</code>, the message tells us that we were expecting one sort of error but got another. This is excellent feedback, as it not only tells us that our expectation was not met, it also tells us exactly what went wrong and indirectly what to do about it: add the pop method:
53
+
54
+ <ruby>
55
+ class Stack
56
+ ...
57
+ def pop
58
+ end
59
+ end
60
+ </ruby>
61
+
62
+ run the specs...
63
+
64
+ <pre>
65
+ $ spec stack_spec.rb -v
66
+
67
+ An empty stack
68
+ - should keep its mouth shut when you send it 'push'
69
+ - should raise a StackUnderflowError when you send it 'top'
70
+ - should raise a StackUnderflowError when you send it 'pop' (FAILED - 1)
71
+
72
+ A stack with one item
73
+ - should keep its mouth shut when you send it 'push'
74
+ - should return top when you send it 'top'
75
+
76
+ 1)
77
+ ExpectationNotMetError in 'An empty stack should raise a StackUnderflowError when you send it 'pop''
78
+ <Proc> should raise <StackUnderflowError> but raised nothing
79
+ ./stack_spec.rb:18:in `should raise a StackUnderflowError when you send it 'pop''
80
+
81
+ Finished in 0.000902 seconds
82
+
83
+ 2 contexts, 5 specifications, 1 failure
84
+ </pre>
85
+
86
+ ...and now the message tells us that nothing was raised. So back to stack.rb...
87
+
88
+ <ruby>
89
+ def pop
90
+ raise StackUnderflowError
91
+ end
92
+ </ruby>
93
+
94
+ ...run the specs...
95
+
96
+ <pre>
97
+ $ spec stack_spec.rb -v
98
+
99
+ An empty stack
100
+ - should keep its mouth shut when you send it 'push'
101
+ - should raise a StackUnderflowError when you send it 'top'
102
+ - should raise a StackUnderflowError when you send it 'pop'
103
+
104
+ A stack with one item
105
+ - should keep its mouth shut when you send it 'push'
106
+ - should return top when you send it 'top'
107
+
108
+ Finished in 0.000812 seconds
109
+
110
+ 2 contexts, 5 specifications, 0 failures
111
+ </pre>
112
+
113
+ ...and they pass. Notice that we did not include any conditional logic in the pop method. As things stand right now, any client that calls pop on any stack (empty or otherwise) will get a StackUnderflowError. Obviously, this is not what we want, but again, rather than going back to the code we're going to specify the behaviour that we're looking for. We do have one other context in place already, so let's specify calling 'pop' on a context with one element.
114
+
115
+ <ruby>
116
+ context "A stack with one item" do
117
+
118
+ setup do
119
+ @stack = Stack.new
120
+ @stack.push "one item"
121
+ end
122
+
123
+ specify "should keep its mouth shut when you send it 'push'" do
124
+ lambda { @stack.push Object.new }.should.not.raise Exception
125
+ end
126
+
127
+ specify "should return top when you send it 'top'" do
128
+ @stack.top.should.equal "one item"
129
+ end
130
+
131
+ specify "should return top when you send it 'pop'" do
132
+ @stack.pop.should.equal "one item"
133
+ end
134
+
135
+ end
136
+ </ruby>
137
+
138
+ Run the specs...
139
+
140
+ <pre>
141
+ $ spec stack_spec.rb -v
142
+
143
+ An empty stack
144
+ - should keep its mouth shut when you send it 'push'
145
+ - should raise a StackUnderflowError when you send it 'top'
146
+ - should raise a StackUnderflowError when you send it 'pop'
147
+
148
+ A stack with one item
149
+ - should keep its mouth shut when you send it 'push'
150
+ - should return top when you send it 'top'
151
+ - should return top when you send it 'pop' (FAILED - 1)
152
+
153
+ 1)
154
+ StackUnderflowError in 'A stack with one item should return top when you send it 'pop''
155
+ StackUnderflowError
156
+ ./stack.rb:16:in `pop'
157
+ ./stack_spec.rb:39:in `should return top when you send it 'pop''
158
+
159
+ Finished in 0.00102000000000002 seconds
160
+
161
+ 2 contexts, 6 specifications, 1 failure
162
+ </pre>
163
+
164
+ ...and we can see that we're getting the StackUnderflowError in a case where we don't want it. So NOW we can add the conditional logic to handle it...
165
+
166
+ <ruby>
167
+ class Stack
168
+
169
+ def push item
170
+ @item = item
171
+ end
172
+
173
+ def top
174
+ raise StackUnderflowError if @item.nil?
175
+ @item
176
+ end
177
+
178
+ def pop
179
+ raise StackUnderflowError if @item.nil?
180
+ @item
181
+ end
182
+
183
+ end
184
+ </ruby>
185
+
186
+ ...run the specs...
187
+
188
+ <pre>
189
+ $ spec stack_spec.rb -v
190
+
191
+ An empty stack
192
+ - should keep its mouth shut when you send it 'push'
193
+ - should raise a StackUnderflowError when you send it 'top'
194
+ - should raise a StackUnderflowError when you send it 'pop'
195
+
196
+ A stack with one item
197
+ - should keep its mouth shut when you send it 'push'
198
+ - should return top when you send it 'top'
199
+ - should return top when you send it 'pop'
200
+
201
+ Finished in 0.000936 seconds
202
+
203
+ 2 contexts, 6 specifications, 0 failures
204
+ </pre>
205
+
206
+ ...and everything passes. Everything seems fine, but there's one thing missing. When you send 'pop' to a non-empty stack, it's also supposed to remove that element from the stack. Here's a typical solution to this problem using TDD:
207
+
208
+ <ruby>
209
+ #typical state-based example
210
+ def test_should_remove_top_item_when_sent_pop
211
+ assert_equal(stack.size, 1)
212
+ stack.pop
213
+ assert_equal(stack.size, 0)
214
+ end
215
+ </ruby>
216
+
217
+ That is VERY tempting because it seems so simple. It tells the story we want to tell. But it does so at the expense of exposing internal state. "So what?" you may ask. "We're talking about something trivial here." you may say. "Exposing size is perfectly logical because the stack is a collection" you may add. While all those things are reasonable, this thinking almost always takes us down the slippery slope of exposing more and more state just because it makes the test easier to write.
218
+
219
+ Now don't get me wrong. Tests should be easy to write. Testable code is a primary goal of what we're doing here. Whether you're doing TDD or BDD, that's absolutely key. But all to often we make bad design decisions in the name of testability, when there are perfectly reasonable alternatives right at our fingertips.
220
+
221
+ So even if you buy these arguments, TDD would probably lead you to this instead:
222
+
223
+ <ruby>
224
+ #typical state-based example
225
+ def test_should_remove_top_item_when_sent_pop
226
+ assert(!stack.empty?)
227
+ stack.pop
228
+ assert(stack.empty?)
229
+ end
230
+ </ruby>
231
+
232
+ That's a little better. We're exposing state, but it's not something as specific as size. But it's still state. We can do better.
233
+
234
+ What we're looking for here is observable behaviour. How does a one-element stack behave after we send it 'pop'. We've stated that the stack should be empty at that point, right? So perhaps the observable behaviour is that it acts like an empty stack:
235
+
236
+ <ruby>
237
+ specify "should raise a StackUnderflowError the second time you sent it 'pop'" do
238
+ @stack.pop
239
+ lambda { @stack.pop }.should.raise StackUnderflowError
240
+ end
241
+ </ruby>
242
+
243
+ Run the specs (this time without the -v flag)...
244
+
245
+ <pre>
246
+ $ spec stack_spec.rb
247
+
248
+ ......F
249
+
250
+ 1)
251
+ ExpectationNotMetError in 'A stack with one item should raise a StackUnderflowError the second time you sent it 'pop''
252
+ <Proc> should raise <StackUnderflowError> but raised nothing
253
+ ./stack_spec.rb:44:in `should raise a StackUnderflowError the second time you sent it 'pop''
254
+
255
+ Finished in 0.000948 seconds
256
+
257
+ 2 contexts, 7 specifications, 1 failure
258
+ </pre>
259
+
260
+ ...implement just enough to meet this specification...
261
+
262
+ <ruby>
263
+ class Stack
264
+
265
+ ...
266
+
267
+ def pop
268
+ raise StackUnderflowError if @item.nil?
269
+ item = @item
270
+ @item = nil
271
+ item
272
+ end
273
+
274
+ end
275
+ </ruby>
276
+
277
+ <pre>
278
+ $ spec stack_spec.rb
279
+
280
+ .......
281
+
282
+ Finished in 0.000859 seconds
283
+
284
+ 2 contexts, 7 specifications, 0 failures
285
+ </pre>
286
+
287
+ ...and all specifications are met. Let's look back at our one-item stack spec so far:
288
+
289
+ <ruby>
290
+ context "A stack with one item" do
291
+
292
+ setup do
293
+ @stack = Stack.new
294
+ @stack.push "one item"
295
+ end
296
+
297
+ specify "should keep its mouth shut when you send it 'push'" do
298
+ lambda { @stack.push Object.new }.should.not.raise Exception
299
+ end
300
+
301
+ specify "should return top when you send it 'top'" do
302
+ @stack.top.should.equal "one item"
303
+ end
304
+
305
+ specify "should return top when you send it 'pop'" do
306
+ @stack.pop.should.equal "one item"
307
+ end
308
+
309
+ specify "should raise a StackUnderflowError the second time you sent it 'pop'" do
310
+ @stack.pop
311
+ lambda { @stack.pop }.should.raise StackUnderflowError
312
+ end
313
+
314
+ end
315
+ </ruby>
316
+
317
+ See some imbalance? We've specified what happens when you send 'pop' repeatedly, but not what happens when you send 'top' repeatedly. 'top' should keep returning the same value, right?
318
+
319
+ <ruby>
320
+ context "A stack with one item" do
321
+
322
+ ...
323
+
324
+ specify "should return top repeatedly when you send it 'top'" do
325
+ @stack.top.should.equal "one item"
326
+ @stack.top.should.equal "one item"
327
+ @stack.top.should.equal "one item"
328
+ end
329
+
330
+ ...
331
+
332
+ end
333
+ </ruby>
334
+
335
+ Run the specs (with the -v flag)...
336
+
337
+ <pre>
338
+ $ spec stack_spec.rb -v
339
+
340
+ An empty stack
341
+ - should keep its mouth shut when you send it 'push'
342
+ - should raise a StackUnderflowError when you send it 'top'
343
+ - should raise a StackUnderflowError when you send it 'pop'
344
+
345
+ A stack with one item
346
+ - should keep its mouth shut when you send it 'push'
347
+ - should return top when you send it 'top'
348
+ - should return top repeatedly when you send it 'top'
349
+ - should return top when you send it 'pop'
350
+ - should raise a StackUnderflowError the second time you sent it 'pop'
351
+
352
+ Finished in 0.001746 seconds
353
+
354
+ 2 contexts, 8 specifications, 0 failures
355
+ </pre>
356
+
357
+ So in this case we did not need any additional implementation, but look at how well rounded the specification becomes.
358
+
359
+ <a href="stack_04.html">Previous</a>
@@ -0,0 +1,359 @@
1
+ h2. A Simple Stack - Pop - IN PROGRESS - DISREGARD THIS PAGE
2
+
3
+ So what next? We could specify a new context, or we could specify new messages in the existing contexts. How about 'pop'? We'll start with the empty stack:
4
+
5
+ <ruby>
6
+ context "An empty stack" do
7
+
8
+ setup do
9
+ @stack = Stack.new
10
+ end
11
+
12
+ specify "should keep its mouth shut when you send it 'push'" do
13
+ lambda { @stack.push Object.new }.should.not.raise Exception
14
+ end
15
+
16
+ specify "should raise a StackUnderflowError when you send it 'top'" do
17
+ lambda { @stack.top }.should.raise StackUnderflowError
18
+ end
19
+
20
+ specify "should raise a StackUnderflowError when you send it 'pop'" do
21
+ lambda { @stack.pop }.should.raise StackUnderflowError
22
+ end
23
+
24
+ end
25
+ </ruby>
26
+
27
+ ... run the specs ...
28
+
29
+ <pre>
30
+ $ spec stack_spec.rb -v
31
+
32
+ An empty stack
33
+ - should keep its mouth shut when you send it 'push'
34
+ - should raise a StackUnderflowError when you send it 'top'
35
+ - should raise a StackUnderflowError when you send it 'pop' (FAILED - 1)
36
+
37
+ A stack with one item
38
+ - should keep its mouth shut when you send it 'push'
39
+ - should return top when you send it 'top'
40
+
41
+
42
+ 1)
43
+ ExpectationNotMetError in 'An empty stack should raise a StackUnderflowError when you send it 'pop''
44
+ <Proc> should raise <StackUnderflowError> but raised #<NoMethodError: undefined method `pop' for #<Stack:0x36f980>>
45
+ ./stack_spec.rb:18:in `should raise a StackUnderflowError when you send it 'pop''
46
+
47
+ Finished in 0.000967 seconds
48
+
49
+ 2 contexts, 5 specifications, 1 failure
50
+ </pre>
51
+
52
+ The messages here are slightly different than what we've seen so far. Back when we specified sending 'top' to an empty stack, we had already specified sending 'top' to a stack with one element, so we didn't get the <code>NoMethodError</code> that we get here. And since we were expecting a <code>StackUnderflowError</code>, the message tells us that we were expecting one sort of error but got another. This is excellent feedback, as it not only tells us that our expectation was not met, it also tells us exactly what went wrong and indirectly what to do about it: add the pop method:
53
+
54
+ <ruby>
55
+ class Stack
56
+ ...
57
+ def pop
58
+ end
59
+ end
60
+ </ruby>
61
+
62
+ run the specs...
63
+
64
+ <pre>
65
+ $ spec stack_spec.rb -v
66
+
67
+ An empty stack
68
+ - should keep its mouth shut when you send it 'push'
69
+ - should raise a StackUnderflowError when you send it 'top'
70
+ - should raise a StackUnderflowError when you send it 'pop' (FAILED - 1)
71
+
72
+ A stack with one item
73
+ - should keep its mouth shut when you send it 'push'
74
+ - should return top when you send it 'top'
75
+
76
+ 1)
77
+ ExpectationNotMetError in 'An empty stack should raise a StackUnderflowError when you send it 'pop''
78
+ <Proc> should raise <StackUnderflowError> but raised nothing
79
+ ./stack_spec.rb:18:in `should raise a StackUnderflowError when you send it 'pop''
80
+
81
+ Finished in 0.000902 seconds
82
+
83
+ 2 contexts, 5 specifications, 1 failure
84
+ </pre>
85
+
86
+ ...and now the message tells us that nothing was raised. So back to stack.rb...
87
+
88
+ <ruby>
89
+ def pop
90
+ raise StackUnderflowError
91
+ end
92
+ </ruby>
93
+
94
+ ...run the specs...
95
+
96
+ <pre>
97
+ $ spec stack_spec.rb -v
98
+
99
+ An empty stack
100
+ - should keep its mouth shut when you send it 'push'
101
+ - should raise a StackUnderflowError when you send it 'top'
102
+ - should raise a StackUnderflowError when you send it 'pop'
103
+
104
+ A stack with one item
105
+ - should keep its mouth shut when you send it 'push'
106
+ - should return top when you send it 'top'
107
+
108
+ Finished in 0.000812 seconds
109
+
110
+ 2 contexts, 5 specifications, 0 failures
111
+ </pre>
112
+
113
+ ...and they pass. Notice that we did not include any conditional logic in the pop method. As things stand right now, any client that calls pop on any stack (empty or otherwise) will get a StackUnderflowError. Obviously, this is not what we want, but again, rather than going back to the code we're going to specify the behaviour that we're looking for. We do have one other context in place already, so let's specify calling 'pop' on a context with one element.
114
+
115
+ <ruby>
116
+ context "A stack with one item" do
117
+
118
+ setup do
119
+ @stack = Stack.new
120
+ @stack.push "one item"
121
+ end
122
+
123
+ specify "should keep its mouth shut when you send it 'push'" do
124
+ lambda { @stack.push Object.new }.should.not.raise Exception
125
+ end
126
+
127
+ specify "should return top when you send it 'top'" do
128
+ @stack.top.should.equal "one item"
129
+ end
130
+
131
+ specify "should return top when you send it 'pop'" do
132
+ @stack.pop.should.equal "one item"
133
+ end
134
+
135
+ end
136
+ </ruby>
137
+
138
+ Run the specs...
139
+
140
+ <pre>
141
+ $ spec stack_spec.rb -v
142
+
143
+ An empty stack
144
+ - should keep its mouth shut when you send it 'push'
145
+ - should raise a StackUnderflowError when you send it 'top'
146
+ - should raise a StackUnderflowError when you send it 'pop'
147
+
148
+ A stack with one item
149
+ - should keep its mouth shut when you send it 'push'
150
+ - should return top when you send it 'top'
151
+ - should return top when you send it 'pop' (FAILED - 1)
152
+
153
+ 1)
154
+ StackUnderflowError in 'A stack with one item should return top when you send it 'pop''
155
+ StackUnderflowError
156
+ ./stack.rb:16:in `pop'
157
+ ./stack_spec.rb:39:in `should return top when you send it 'pop''
158
+
159
+ Finished in 0.00102000000000002 seconds
160
+
161
+ 2 contexts, 6 specifications, 1 failure
162
+ </pre>
163
+
164
+ ...and we can see that we're getting the StackUnderflowError in a case where we don't want it. So NOW we can add the conditional logic to handle it...
165
+
166
+ <ruby>
167
+ class Stack
168
+
169
+ def push item
170
+ @item = item
171
+ end
172
+
173
+ def top
174
+ raise StackUnderflowError if @item.nil?
175
+ @item
176
+ end
177
+
178
+ def pop
179
+ raise StackUnderflowError if @item.nil?
180
+ @item
181
+ end
182
+
183
+ end
184
+ </ruby>
185
+
186
+ ...run the specs...
187
+
188
+ <pre>
189
+ $ spec stack_spec.rb -v
190
+
191
+ An empty stack
192
+ - should keep its mouth shut when you send it 'push'
193
+ - should raise a StackUnderflowError when you send it 'top'
194
+ - should raise a StackUnderflowError when you send it 'pop'
195
+
196
+ A stack with one item
197
+ - should keep its mouth shut when you send it 'push'
198
+ - should return top when you send it 'top'
199
+ - should return top when you send it 'pop'
200
+
201
+ Finished in 0.000936 seconds
202
+
203
+ 2 contexts, 6 specifications, 0 failures
204
+ </pre>
205
+
206
+ ...and everything passes. Everything seems fine, but there's one thing missing. When you send 'pop' to a non-empty stack, it's also supposed to remove that element from the stack. Here's a typical solution to this problem using TDD:
207
+
208
+ <ruby>
209
+ #typical state-based example
210
+ def test_should_remove_top_item_when_sent_pop
211
+ assert_equal(stack.size, 1)
212
+ stack.pop
213
+ assert_equal(stack.size, 0)
214
+ end
215
+ </ruby>
216
+
217
+ That is VERY tempting because it seems so simple. It tells the story we want to tell. But it does so at the expense of exposing internal state. "So what?" you may ask. "We're talking about something trivial here." you may say. "Exposing size is perfectly logical because the stack is a collection" you may add. While all those things are reasonable, this thinking almost always takes us down the slippery slope of exposing more and more state just because it makes the test easier to write.
218
+
219
+ Now don't get me wrong. Tests should be easy to write. Testable code is a primary goal of what we're doing here. Whether you're doing TDD or BDD, that's absolutely key. But all to often we make bad design decisions in the name of testability, when there are perfectly reasonable alternatives right at our fingertips.
220
+
221
+ So even if you buy these arguments, TDD would probably lead you to this instead:
222
+
223
+ <ruby>
224
+ #typical state-based example
225
+ def test_should_remove_top_item_when_sent_pop
226
+ assert(!stack.empty?)
227
+ stack.pop
228
+ assert(stack.empty?)
229
+ end
230
+ </ruby>
231
+
232
+ That's a little better. We're exposing state, but it's not something as specific as size. But it's still state. We can do better.
233
+
234
+ What we're looking for here is observable behaviour. How does a one-element stack behave after we send it 'pop'. We've stated that the stack should be empty at that point, right? So perhaps the observable behaviour is that it acts like an empty stack:
235
+
236
+ <ruby>
237
+ specify "should raise a StackUnderflowError the second time you sent it 'pop'" do
238
+ @stack.pop
239
+ lambda { @stack.pop }.should.raise StackUnderflowError
240
+ end
241
+ </ruby>
242
+
243
+ Run the specs (this time without the -v flag)...
244
+
245
+ <pre>
246
+ $ spec stack_spec.rb
247
+
248
+ ......F
249
+
250
+ 1)
251
+ ExpectationNotMetError in 'A stack with one item should raise a StackUnderflowError the second time you sent it 'pop''
252
+ <Proc> should raise <StackUnderflowError> but raised nothing
253
+ ./stack_spec.rb:44:in `should raise a StackUnderflowError the second time you sent it 'pop''
254
+
255
+ Finished in 0.000948 seconds
256
+
257
+ 2 contexts, 7 specifications, 1 failure
258
+ </pre>
259
+
260
+ ...implement just enough to meet this specification...
261
+
262
+ <ruby>
263
+ class Stack
264
+
265
+ ...
266
+
267
+ def pop
268
+ raise StackUnderflowError if @item.nil?
269
+ item = @item
270
+ @item = nil
271
+ item
272
+ end
273
+
274
+ end
275
+ </ruby>
276
+
277
+ <pre>
278
+ $ spec stack_spec.rb
279
+
280
+ .......
281
+
282
+ Finished in 0.000859 seconds
283
+
284
+ 2 contexts, 7 specifications, 0 failures
285
+ </pre>
286
+
287
+ ...and all specifications are met. Let's look back at our one-item stack spec so far:
288
+
289
+ <ruby>
290
+ context "A stack with one item" do
291
+
292
+ setup do
293
+ @stack = Stack.new
294
+ @stack.push "one item"
295
+ end
296
+
297
+ specify "should keep its mouth shut when you send it 'push'" do
298
+ lambda { @stack.push Object.new }.should.not.raise Exception
299
+ end
300
+
301
+ specify "should return top when you send it 'top'" do
302
+ @stack.top.should.equal "one item"
303
+ end
304
+
305
+ specify "should return top when you send it 'pop'" do
306
+ @stack.pop.should.equal "one item"
307
+ end
308
+
309
+ specify "should raise a StackUnderflowError the second time you sent it 'pop'" do
310
+ @stack.pop
311
+ lambda { @stack.pop }.should.raise StackUnderflowError
312
+ end
313
+
314
+ end
315
+ </ruby>
316
+
317
+ See some imbalance? We've specified what happens when you send 'pop' repeatedly, but not what happens when you send 'top' repeatedly. 'top' should keep returning the same value, right?
318
+
319
+ <ruby>
320
+ context "A stack with one item" do
321
+
322
+ ...
323
+
324
+ specify "should return top repeatedly when you send it 'top'" do
325
+ @stack.top.should.equal "one item"
326
+ @stack.top.should.equal "one item"
327
+ @stack.top.should.equal "one item"
328
+ end
329
+
330
+ ...
331
+
332
+ end
333
+ </ruby>
334
+
335
+ Run the specs (with the -v flag)...
336
+
337
+ <pre>
338
+ $ spec stack_spec.rb -v
339
+
340
+ An empty stack
341
+ - should keep its mouth shut when you send it 'push'
342
+ - should raise a StackUnderflowError when you send it 'top'
343
+ - should raise a StackUnderflowError when you send it 'pop'
344
+
345
+ A stack with one item
346
+ - should keep its mouth shut when you send it 'push'
347
+ - should return top when you send it 'top'
348
+ - should return top repeatedly when you send it 'top'
349
+ - should return top when you send it 'pop'
350
+ - should raise a StackUnderflowError the second time you sent it 'pop'
351
+
352
+ Finished in 0.001746 seconds
353
+
354
+ 2 contexts, 8 specifications, 0 failures
355
+ </pre>
356
+
357
+ So in this case we did not need any additional implementation, but look at how well rounded the specification becomes.
358
+
359
+ <a href="stack_04.html">Previous</a>