I took the ideas presented here and built a gem called Mustard. Check it out!
There are several expectation/assertion interfaces available for writing tests/specs. Here are some issues I have with them.
- The order of
assert_equals
feels backwards - Oh wait, that should be
assert_equal
(that too) - I like to write the subject first
- Adds 30+ methods to Object
- Any matchers you want to add are another method on Object
- Still too few matchers, so it's ugly:
foo.must_be :<, 10
- too.many.method.calls.to.simulate.english
- The space makes for some awkward syntaxes that cause warnings (when used with
==
,>
, etc.) eq()
and such aren't bad, but may need parenthesis- Pollutes the spec context with methods for each matcher
I haven't used the RSpec expect()
interface enough to know cons.
# should returns an object which can have matchers called on it
5.should.eq 5
5.should.equal 5
# should_not can be used to do a reverse match
5.should_not.equal 7
# some comparison matchers
5.should.be_less_than 8
5.should.be_greater_than 3
5.should.be_lt 8
5.should.be_gt 3
5.should.be_gte 5
true.should.be_true
false.should.be_false
# call the method and see if it is true
[].should.be :empty?
5.should.be :between?, 3, 7
# add matchers
Should.matcher :be_empty do # any args will be passed into block
empty? # runs in the context of the object and returns true/false
end
[].should.be_empty
[].should_not.be_empty # message: Expected [] to not be empty
# add matcher through class
Should.matcher :be_empty, BeEmptyMatcher
class BeEmptyMatcher
# methods in here for defining behavior and messages.
end
I like this because it does not pollute the Object space much (just should
and should_not
). Also every method called after it is simply a matcher. No crazy method chains.
What do you think? If there's enough interest I'll write this up as a plugin for both RSpec-Core and MiniTest.
@ryanb be does essentially the same as it does in other frameworks, it provides syntax sugar. In Bacon, be has a few extra useful side effects:
The advantage of the code being so short, is that it's very readable.
If you want rspec style matchers, just include methods into the
Should
class and you're away. It's no more complex than that.I find it somewhat confusing that you claim you cannot understand what some of bacons methods are doing, when the rspec equivalents are doing much much more in order to achieve their logic. The matcher proxies are MUCH more complicated, I really cannot believe anyone would describe them as being simpler than the bacon implementation.
In bacon almost all assertions you write, in normal usage (e.g. your original examples) execute in this class:
https://github.com/chneukirchen/bacon/blob/master/lib/bacon.rb#L290-L351
That's all of it. ALL OF IT. There is no more.
By contrast, in rspec, the same stuff goes through:
https://github.com/rspec/rspec-core/blob/master/lib/rspec/core/subject.rb#L52-L79
https://github.com/rspec/rspec-expectations/blob/master/lib/rspec/expectations/handler.rb#L15-L61
https://github.com/rspec/rspec-expectations/blob/master/lib/rspec/matchers.rb#L227-L231
https://github.com/rspec/rspec-expectations/blob/master/lib/rspec/matchers/built_in/be.rb#L48-L92
And that's NOT all of it. That's just the parts that you really care about.
There's just no way your comments are based on reading the source here. RSpecs be implementation is very very similar, just much more complicated and indirect.
Now I use (regularly) RSpec, Bacon, MiniTest and Test::Unit. None of them are perfect (there are tradeoffs, I would not "blame" authors for any shortcomings in general use a testing system), but I really can't let the claims stand that minitest or bacon are in any way "harder to understand" than the other two. If you've ever had to debug the libraries themselves, or you've spent some time reading their internals (not just implementing a few matchers here and there), then there is no way that you can describe RSpec or Test::Unit as being "simpler" or "easier to understand", it's just not real.
Onto matchers a little more, if you study what is available, and the way that bacon actually works, you'll realize that you can also implement local scope matchers, that won't even cross-pollute your tests:
Alternatively, you can just implement a regular method on Should:
If you want be_ matchers, you can do that too:
See how easy that is, to be different? I cannot stress enough how this is a product of simplicity. And that simplicity of implementation actually goes further...
Proof? Ok, lets implement generic
be_
(that's space be_) matchers then:Does that feel closer to what you want?
Ah yes, should_not, okay:
Lets review:
So I've implemented what you discussed, and your desires.
I dare you 😜 to implement it in a gist comment, in 10 minutes, for RSpec.
Have fun!