Gems I've Known and Loved #1: Expectations 7

Posted by james
on Sunday, June 08

Recently, I realized that something was wrong with all of the conventional testing and specing libraries.

One of the things I loved about RSpec, when I read thin's specs, for the first time, was how easy it was to read the assertion code:

some_var.should == 5

But, when I tried using RSpec on a project, it continually frustrated me that I had to describe each test twice (by naming it, and writing pseudo-english code):

it "should augment the count by one" do
  some_var += 1
  some_var.should == 5
end

So, I went back to the familiar Shoulda. But, then, a couple of weeks ago, I came to a realization: Shoulda has exactly the same problem — it's just hidden under the awesome set of macros. Hell, even test/unit has this problem. Test names are comments. And, frankly, many, if not most of the tests that I write (like the example above) just don't need commenting — they're simple enough that a comment is unnecessary verbosity. A comment may even be distracting the reader from actual test code. Then, a couple of days later, Jay Fields blogged about exactly that. That's what made me take a look at his testing framework: expectations.

Naming Tests with Code

When I was working with RSpec, I started to wish that I could do something like this, to avoid the naming penalty:

before do
  some_var += 1
end

some_var.should == 5
some_var.should be_an_instance_of?(Fixnum)

Shoulda can do something like that, but it doesn't work in all situations, because it gets called at the class scope, so you have to go guessing about what the name of the instance variable will be. Besides, since we're sticking to one assertion per test, couldn't the assertion be the name of the test?

some_var.should == 5 do
  some_var += 1
end

Oops, we just derived the syntax for expectations.

Assertions are King

Except that expectations takes it a few steps further. It provides a ton of niceties for describing your tests in sensible language, and great mocking facilities. Our last example would look like this:

expect 5 do
  some_var  = 4
  some_var += 1
end

If we wanted to test for Fixnum, like in our second assertion, above, there's a nice shorthand for that.

expect Fixnum do
  some_var  = 4
  some_var += 1
end

As I write this, I can hear somebody out there screaming: "That's not very DRY!!". Even though I prefer the duplication in this particular example for readability reasons, let's DRY it up, just for fun.

setup = lambda do 
  some_var  = 4
  some_var += 1
end

expect(Fixnum, &setup)
expect(5, &setup)

Looks a lot like what I was wishing for with RSpec, doesn't it?

Next, let's look at a mocking example. One of the things that makes mocking a little bit weird with conventional testing and specing frameworks is that assertions come last, but mocks come first. So, when you're trying to follow the one assertion per test pattern, you end up with two different flows: setup, assert, for assertions, and: mock, setup, with mocks. Since assertions always come first with expectations, there's only one possible flow, making tests more readable.

expect mock.to.receive(:some_method).with(1) do |my_mock|
  my_mock.some_method(1)
end

Finally, spec junkies can even write expectations using BDD-style language.

expect klass.new.to.have.finished do |process|
  process.finished = true
end

Caveats

Expectations is pretty new, so I haven't yet seen any niceties for testing rails. It would be great to be able to do something like:

expect controller.to_render('index') do |c|
  get :index
end

As a big fan of Shoulda, I'd love to see some of the same types of macros for expectations, too (not in the core framework, but as an add-on).

Get It

$ sudo gem install expectations

Check out the RDoc for more examples, and a full set of documentation.

Comments

Leave a response

  1. Nicolás SanguinettiJune 09, 2008 @ 02:02 AM

    Uh, actually, with rspec you can do

    it { some_var.should == 5 }

    Which will translate (IIRC) to "it should equal 5" when printing the specdoc.

    The one thing that I don't like about rspec is how awfully big custom matchers are (compared to, for example, shoulda -- I haven't tried expectations yet, there's something I don't like but I'm not sure what it is...), but if you have matchers that describe your domain well, then you can do stuff like

    it { @user.should have_many(:photos) } it { @user.should validatepresenceof(:name) }

    And everything will be good and clean :)

  2. Adam WigginsJune 09, 2008 @ 03:35 AM

    Spec descriptions are comments, yes - comments that are always right, because they have a matching block of code which can be executed and thereby verified for correctness. And just like comments, they should not describe what the code does, since that's evident from the code. Instead, they should explain why. What's the underlying assumption? Is it some quirk of the problem domain? Some gotcha to be avoided? For example:

    it "expire shopping cart sessions after 30 days so that the table doesn't get huge" do @session.created_on = 3.months.ago @session.should be_expired end

    it "don't expire sessions created yesterday, users often return to checkout the next day" do @session.created_on = 1.day.ago @session.shouldnot beexpired end

    See: http://adam.blog.heroku.com/past/2008/2/23/testassumptionsnot_methods/

  3. James GolickJune 09, 2008 @ 09:30 AM

    @Nicolás: I didn't know about that. It is certainly nicer than all the long names.

    While this is something of a nitpick, I prefer the structure of expectations, which says, expect this from the code in this block, instead of do something, and then expect the following conditions to be present. I find the former to be a more natural way of looking at test code.

    Also, custom matchers like the ones that you described frequently test more than one thing, which sweeps the one assertion per test pattern under the rug.

    @Adam: Sure, in certain situations a comment is merited. But, if you're following the one assertion per test pattern, there are plenty of places where comments are unnecessary. I certainly don't comment every line of code that I write, so why should I comment every test? The nice thing about expectations is that it allows me to comment where I want, and omit them everywhere else.

  4. KarmenJune 09, 2008 @ 10:10 AM

    You might be interested in Rspec's simple matchers: http://blog.davidchelimsky.net/articles/2007/09/08/simple-matchers-made-simple

  5. Nicolás SanguinettiJune 10, 2008 @ 08:32 AM

    About the custom matchers, it depends. We use the ones I offered as example and only test that the association is present or the validation takes place. They only do one assertion.

    By the way, speaking of test names, did you read http://blog.jayfields.com/2008/05/testing-value-of-test-names.html ?

  6. Justin ReagorJune 25, 2008 @ 10:05 PM

    Isn't that the entire point of RSpec? So you have this commented documentation that describes what how your Ruby code behaves? Hey, I understand what you mean... Ruby is that beautiful most of the time. But I still think that PoE is needed to simply clarify exactly what your about to write. I'm pretty sure I wouldn't have learned all this Ruby if I didn't have those sitting in the Spec files.

    They also keep me on track. I've caught myself a few times looking back up at the top of each context block to make sure I'm describing what I'm supposed to. Without having to read through all the Ruby again.

  7. James DevilleJune 30, 2008 @ 12:14 AM

    The thing that kills me about Shoulda is that it seems founded on some weak assumptions about RSpec, and instead of contributing to the existing OSS project, they started up a new one. A lot of it really could have been nice additions to RSpec.

Comment






Clicky Web Analytics