Skip to content

Instantly share code, notes, and snippets.

@gma
Last active September 13, 2022 17:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gma/893459 to your computer and use it in GitHub Desktop.
Save gma/893459 to your computer and use it in GitHub Desktop.
What's so bad about RSpec?
require 'rspec'
describe 'Rspec' do
it 'should not allow you to make shit up' do
[1, 2, 3].should have(4).potatoes
end
end
$ rspec ~/insane-magic.spec
F

Failures:

  1) Rspec should not allow you to make shit up
     Failure/Error: [1, 2, 3].should have(4).potatoes
       expected 4 potatoes, got 3
     # /Users/graham/insane-magic.spec:5

Finished in 0.00042 seconds
1 example, 1 failure

Do you see the potatoes method call? Why isn't Ruby complaining that it doesn't exist? Where the hell did it come from? Is it part of rspec? It must be, surely. That's the only library I've required!

[Goes off to grep the rspec gems for "potatoes"... doesn't find it]

What's happening here? RSpec is purposefully ignoring .potatoes, which is a method call on the object returned by #have. And what is that object, exactly?

So RSpec (and this "BDD" business) is good for people new to testing you say? Really?

The "call any method you like and we'll silently ignore it" behaviour is not syntactic sugar, it's pain and confusion for people who are new to programming. It's a bloody daft idea to put it forward as good practice in a widely popular library.

Some bad ideas catch on just as easily as good ideas, and this approach is its own special breed of footgun.

To be clear, I like describe and it. But you can stop there; I feel RSpec's features have a negative impact on productivity from there on in.

And for comparison, the Contest gem adds support for these blocks to test/unit in 100 lines of code.

@gma
Copy link
Author

gma commented Mar 29, 2011

Now that a newbie Ruby programmer has got over the fact that grep has failed them, could they start to wonder how some objects seem to have a should method defined on them? And have, where did that spring up from? They're not mentioned in their Ruby book. Perhaps they conclude that their Ruby book covers 1.8.7 and they're using 1.9; perhaps should is a new method on Array in Ruby 1.9? Yeah that's probably it...

This kind of thought process goes on all the time when somebody is learning a new language. This "DSL" (there's another frigging annoying fashion based Ruby meme) drives my sympathetic side nuts.

@jgwhite
Copy link

jgwhite commented Mar 29, 2011

Brings this great post to mind: http://coderoom.wordpress.com/2010/03/19/5-stages-of-programmer-incompetence/

Exploration and play at the meta level are really enticing aspects of Ruby. They make it a great language for beginners. On the flipside, it is very easy to get addicted to this sort of sugar and wind up never really implementing anything besides seemed-clever-at-the-time syntax.

@rgarner
Copy link

rgarner commented Oct 26, 2011

Grep failure on new dev confusion applies to every library that uses method_missing. ActiveRecord is right up there. During my Ruby learning curve, this confusion you state applied (and still applies, until you learn the idioms/fashions (delete according to mood)) to way more than RSpec.

@gma
Copy link
Author

gma commented Oct 26, 2011

Well quite – method_missing should be reserved for special occasions.

The ActiveRecord API that springs to my mind does something useful that you can't already do with Ruby's standard library.

The difference with RSpec is that everything it does can be done with the Ruby standard library.

@rgarner
Copy link

rgarner commented Oct 26, 2011

I suspect many more libraries are susceptible to the reductio ad absurdum attack, though :)

RSpec makes it easier to do stupid stuff, for sure. I also think that when used well, the resulting code makes far more immediate sense with less cognitive overhead at 17.30 on a Friday afternoon when you're sleepy after a nice lunchtime pie. It might be the case that it's just what I got used to, having skipped Test::Unit and coming to it straight from C#/MSpec.

[1, 2, 3].should have(3).numbers makes sense to me - it requires zero parsing beyond what I already do for English, whereas assert_equal(3, [1,2,3].length) always leaves me having to expend at least a small amount of energy to get to a mental model of "[1,2,3] should have 3 numbers". I'd prefer not to have the parsing overhead, especially if I'm taking in a dozen failures at a time.

Of course, this is all rationalisation from me having a blind spot remembering the sequence of actual, expected in an assert_equal and finding remembering that have(n) returns a matcher and should is mixed into everything is much easier than forcing myself to internalise a different and unnatural order for subject -> predicate -> object. Just wired that way, I suppose.

But I also like stuff like

    subject { MyObject.new }
    its(:name) { should be_nil }
    its(:count) { should eql(0) }

and too often I see an equivalent Test::Unit suite of

    def test_attributes_should_initialize
        object = MyObject.new
        assert_nil object.name
        assert_equal 0, count
    end

In a CI environment, when that test fails, do you know which assertion failed? Do you care? Does it help to know? Maybe not in this contrived example, but how about if there were a dozen asserts? Does Test::Unit have an equivalent of the RSpec version where the code under test is called once and multiple identifiable assertions are made (is it Contest)? If so, why doesn't it get used more? If it exists, I'll try Test::Unit on my next project and report back.

Wow. Sorry that was so long. I probably could just have said "it's a matter of taste" ;)

EDIT: please allow me to admit that a lot of this response didn't have anything to with the have(n) matcher and was mostly for @techbelly

@dchelimsky
Copy link

I think it's more than just taste. I appreciate the confusion that @gma is complaining about, and would be more than happy to look at alternatives. For example, instead of a method call, we could change that API to [1,2,3].should have(2, :items). Reads just as well, but now you can look at the rdoc to understand it. The current rdoc does explain methods like potatoes, but I agree that when trying to understand a method call, the instinct would be to grep for its definition rather than look at the rdoc for a different method.

@dchelimsky
Copy link

FYI: rspec/rspec-expectations#93. Feel free to add commentary there. I proposed one alternative, and would love to hear other proposals.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment