Beginning with RSpec
My colleage Bill Tozier, who’s working with me on the project to move XProgramming to this site, is an aficionado of RSpec. I have been unable to determine why, not least because he seems to have to look everything up and is surely using a mixture of old and new syntax. No disrespect, I look everything up too, but when I’m doing TDD, learning the syntax of the testing language is the last thing on my mind.
When Chet and I teach developer courses, with practices like TDD, the participants have all kinds of objections to doing TDD in their context, blah blah. They want to decide that TDD isn’t for them, owing to their circumstances. My view is that the most important circumstance is that they don’t really know what TDD is and how to do it, so that they are singularly unqualified to decide whether it would be helpful.
I am in no position to command anyone to do anything, even my cats. Especially my cats. So my advice is always this:
Try it ‘til you’re good.
Then decide.
If you’re interested, try doing TDD until you’re pretty sure I’d recognize what you’re doing as really being TDD. Then take a look at how it’s working for you, and then decide what place it has in your life.
So I’ve been whining, in real life and on twitter, about RSpec and my difficulties in getting going with it. Realizing what I was doing, I then tweeted this:
ok, i shall take my own advice and get good at rspec before i condemn it. then i will condemn it. what’s the right reading for modern rspec?
It seems only fair that I do this, since I ask it of others. And of course the part about “then I will condemn it” is just my sense of humor. I generally don’t condemn things unless they really need it. Surely RSpec won’t need it.
First attempt …
We have this “regarding” plugin that I’ve written about before. It’s a Liquid plugin that lets you say regarding some_category
and then inside the regarding block, category
and category_name
are defined and you can loop over the category displaying index information.
The plugin needed refactoring and some extensions. It was built with no tests, so I decided to write some in RSpec. They looked roughly like this:
require 'rspec'
require 'rubygems'
require 'liquid'
require_relative '../_plugins/regardingCategoryBlock.rb'
#### none of this runs, it is left over from a failed exersize. But I think I know what I did.
# should have made a copy of the site pages and put THAT in my destroyed member variable.
# without that, each call to this destroyed more pages ...
describe 'hookup' do
context 'no idea' do
it "can do arithmetic" do
expect(2+2). to eq(4)
end
end
end
describe 'regarding methods' do
context 'a regarding block' do
block = RegardingCategoryBlock.new("xxxtestingxxx", "some category", [])
p_nil = { 'categories' => nil}
p_private = {'categories'=> ['private']}
p_kate = {'categories'=> ['Kate Oneal']}
p_tdd = {'categories' => ['tdd']}
p_estimation = {'categories' => ['estimation']}
p_beyond = {'categories' => ['Beyond Agile']}
it 'removes pages with nil categories and leaves the rest' do
block.regarding_pages = [p_nil, p_private, p_kate ]
result = block.restrict_to_pages_with_categories
expect(result.size).to be(2)
expect(result).to match_array([p_kate, p_private])
end
it 'handles nil automatically with restrict done' do
block.regarding_pages = [p_nil, p_kate, p_private]
result = block.restrict_to_pages_with_categories
result = block.remove_all_these_categories(['private'])
expect(result).to match_array([p_kate])
end
it 'can remove a category NO NILS' do
block.regarding_pages = [p_kate, p_private]
result = block.remove_all_these_categories(['private'])
expect(result).to match_array([p_kate])
# should we split this into two specs?
block.regarding_pages = [p_kate, p_private]
result = block.remove_all_these_categories(['Kate Oneal'])
expect(result).to match_array([p_private])
end
it 'can remove multiple categories NO NILS' do
block.regarding_pages = [ p_kate, p_private, p_tdd, p_estimation]
result = block.remove_all_these_categories(['private','estimation'])
expect(result).to match_array([p_tdd, p_kate])
end
it 'can include multiple categories and remove some, NO NILS' do
block.regarding_pages = [ p_kate, p_private, p_tdd, p_estimation]
result = block.fetch_categories(['tdd','estimation'])
expect(result).to match_array([p_tdd, p_estimation])
end
it 'gets everything but private given *' do
block.regarding_pages = [ p_nil, p_kate, p_private, p_tdd, p_estimation]
result = block.regarding('*')
expect(result).to match_array([ p_kate, p_tdd, p_estimation])
end
it 'gets what is asked for from a single item' do
block.regarding_pages = [ p_nil, p_kate, p_private, p_tdd, p_estimation]
result = block.regarding('Kate Oneal')
expect(result).to match_array([ p_kate ])
end
it 'gets all items from a list' do
block.regarding_pages = [ p_nil, p_kate, p_private, p_tdd, p_estimation, p_beyond]
result = block.regarding('Kate Oneal, Beyond Agile')
expect(result).to match_array([ p_beyond, p_kate ])
end
end
end
So this isn’t too bad as far as I know. If it could be better, feel free to drop me an email about it.
Unfortunately, the implementation worked fine under RSpec, but exploded bizarrely when actually running under Jekyll. Turns out to have been a dopey error: I was editing a page collection in place, removing items not needed and so on, and that collection was the one built into Jekyll, so I was removing pages from under its feet. I should have made a shallow copy. Or not edited in place, as was suggested by Clifford Heath, who tweeted “Hooray for mutable state. Not.”
Still, the specs seemed not too bad to me, and I actually found writing the “it” description to be helpful in focusing on exactly what I was trying to do. And who couldn’t like that match_array
matcher, which was just what I needed for checking that the right values, and no others, were in the array.
Still, the experience was tainted by the horrible results. I knew quite well that the problem wasn’t in RSpec and it took me all day to figure out what it was.
Meanwhile …
Myron Marston, lead maintainer of RSpec, wrote me a nice note. He was a tiny bit upset because he felt that I had called his baby ugly. We had a nice email exchange, and he sent me a very early draft of his upcoming book on RSpec, with Ian Dees, which will be coming from Pragmatic Bookshelf.
And a few people have tweeted me various links to useful RSpec sites. It could just be me, but none of them quite started me where I needed to start. (My claim, competent programmer, knows TDD, knows zip zero nada about RSpec.)
Then I found a video from Jason Arhart that was just about what I needed. I think it may have a bug in it – or a version difference – because his example won’t compile and run on my system, but it got me close. Based on that, I now have this spec and class:
require 'rspec'
require 'stack'
describe "Stack" do
subject(:stack) { Stack.new(initial_values) }
let(:initial_values) { [3] }
it "pushes" do
stack.push(42)
expect(stack.size).to eq 2
end
it "pops" do
stack.push(42).push(17)
expect(stack.pop).to eq 17
expect(stack.pop).to eq 42
expect(stack.pop).to eq 3
end
end
class Stack
def initialize(contents=[])
@contents = contents
end
def push(value)
@contents.push(value)
end
def pop
@contents.pop
end
def size
@contents.count
end
end
Silly app, but it’s not supposed to be a sophisticated program, it’s a simple example of how to do the thing. And it works.
Now I know I’m not supposed to have multiple expectations for best style, but to me, that multi-one expresses the stack’s behavior best. I should also mention that I initialized the stack with 3 just to practice with the subject
and let
facility.
Now Jason’s example had context
where I have it
, but when I do that it seems not to work. It can’t find stack
any more, making me think that the subject
has to be inside the context. Maybe I didn’t copy the example correctly, I’m not sure.
Anyway, this is working and it looks pretty reasonable to me. Again, if it should be written differently, or you have a preferred way, do let me know.
Unmentioned Travails
In the course of making these little guys work, I had to do a number of things that weren’t difficult, but each one took away some gumption. They include, but are not limited to:
- Make sure RSpec is installed;
- Learn that specs are looked for in the Spec folder;
- Find the RubyTest plugin for Sublime;
- Convince RubyTest to run, by adding obscure config values;
- Get everyone looking at the right folders.
Yeah, I know, I should have used bundler or fumbler or ratsync or something …
I’m sure there were more. These are examples of the “little things” that every programmer goes through whenever he or she tries to install some new tool. The on-line documentation never seems to quite match reality, and your configuration is always enough different that you fear messing up something else by following the instructions.
These things are always easy if you already know how, or are sitting beside someone who knows how. I suppose it’s amazing that all these gems from all over work at all, especially when they work together, but it’s still a lot like getting your car in kit form. I have no solution but these things do slow down uptake of good ideas – at least my uptake.
So Far, So Good
OK, I’m getting the hang of all this. I have no doubt that the RSpec is easier to read than Test::Unit or the like. I’m not yet convinced that it is easier to write, but my guess is that I can follow this working pattern for a while, learning new matchers and combinations and such, and work my way forward. Which is how we do these things, inch by inch.
I’m getting the sense that having to think about the specs and how to write them is helping me design, but it’s too soon to be sure. It might just be that thinking a bit more would be a good idea. (Thinking less seems unlikely to be right.) And writing the comment in the it
definitely helps me focus on what I’m going to do next.
Anyway, that’s my report. “So far, so good”, and I’ll keep trying. For a while, I’ll write all my tests er specs in RSpec. Stay tuned. Thanks!
P.S. To the first person who can tell me why Jekyll has stopped regenerating on a save, and how to fix it, I’ll send you an autographed copy of one of the soon to be famous pictures from The Nature of Software Development. That should teach you to stay out of trouble!
P.P.S. I have directory_watcher 1.4.1 installed, so that’s not it. And Sublime sees folder and file changes just fine, so I’m pretty sure it’s not the system.