Hello, RSpec fans!
Today I was playing with an idea of negative tests. They are surely a thing one may need while working on a long-term project. For example, you may need negative testing when you plan a future major change. You may want to plan this change as a test, but it should not fail today and it should be quite easy to rewrite it to non-failing test tomorrow.
Of course, you can just create a specification that tests that this planned functionality does not work today. And then, rewrite each test within it to opposite tomorrow.
But what if this could be done easier?
And it could! I’ve spent few minutes to create a quick-n-dirty specify_negatively implementation for rspec. I’m not sure whether it will be useful or even working properly in all cases, but it seems to pass on a simple test:
context "Test context" do
specify "test" do
(2+2).should == 4
end
specify_negatively "negative test" do
(2+2).should == 5
end
specify_negatively "bad negative test" do
(2+2).should == 4
end
end
Test context
- test
- negative test
- bad negative test (FAILED - 1)
1)
'Test context bad negative test' FAILED
This specification was expected to fail, but nothing failed
Finished in 0.041288 seconds
3 specifications, 1 failure
What do you think?





You can do this already:
whicih outputs
I realize the failure message in my last comment is lame. That is being fixed for the next rspec release.
David, it is exactly what I was trying to fight with:
If you have a more or less complex specification (at least few expectations), you need to rewrite each once you will decide to turn test to positive. In my case, you will just need to set change it from “specify_negative” to “specify”.
Or am I alone in my idea of testing long-term plans?
Yuri,
If I understand your approach correctly, you want to write all of the specs first, and specify that each one should fail until you are ready to make it pass, at which point you change it from specify_negatively to specify and you’ve got a failing spec.
Is that correct?
If so, that is really not aligned with the TDD cycle (which RSpec is intended to support) of writing a small amount of test code, then just enough code to pass the test, then refactor to remove duplication, then repeat.
The idea of listing all of the specs in RSpec so that you know what to do next is interesting though. In fact, I just this morning submitted an RFE to do just that:
http://rubyforge.org/tracker/index.php?func=detail&aid=8139&group_id=797&atid=3152
Take a peek at that and let me know what you think.
Cheers, David
My experience is that negative tests are not always the exact inverse of positive ones. For example, consider the case:
Clearly, the positive and negative tests are not mirror images of one another. Even thought this is a contrived example, I believe it illustrates my point. Did I miss your point?
David,
Your RFE looks interesting as well. In fact, I think both your approach and mine are viable. More, it seems that they could rock if combined. Some features are planned, but no picture on HOW they should work, and in this case your solution seems to fit better. In my case, in Railsware, we have a number of projects where we need to cover existing code with specification and we already know how it should work after next few steps (and it should not work this way right now). In this case my approach seems to be simple and effective.
So, combined, these approaches could deliver a really useful functionality to rspec.
What do you think?
s.ross, sure your example does not fits my approach; though specify_negatively is about another aspect of testing—I see it as a test against planned or gone functionality. Or it could be used to describe bugs in a positive manner and then declaring them as specify_negatively.
Yuri,
While I appreciate the direction you’re going I’m skeptical of its general use. Since the problem you’re solving is rails-specific at the moment, here is what I propose:
1 – raise an RFE in the rspec tracker 2 – publish your extension as a rails plugin compatible w/ rspec_on_rails (I highly recommend that you wait for the 0.8 release which is coming within the next few weeks) 3 – keep an eye on feedback from usage 4 – after some time (a couple of months) we revisit adding it to rspec
I can’t guarantee that I’ll want to add it later, but at least this will provide a means of sharing your idea w/ those who would like to use it.
WDYT?
David,
Well, I’m not sure why you say it is a rails-specific extension. I’d rather see it as a general-purpose extension.
Surely, I will submit an RFE in the rspec tracker and I’m actually going to create a rspec-specify-negatively gem on rubyforge (since I don’t really think it is a rails-specific extension) in order to get more feedback (and I’m going to try to use it extensively within a company).
Anyway, thank you for your attention and thank you for what you do for us, rspec users.
Hi Yuri,
I recognize that it’s a more general case than just rails. What I meant (and said poorly) is that you are using it right now in a rails-specific context, so why not take advantage of the rails plugin framework, which will let you easily publish this for other people to check out. Then, once we’ve seen some feedback in that environment we can revisit including it in RSpec.
If you do this, I still recommend that you wait for 0.8.
As to your thanks, you’re most welcome. I love using RSpec myself, and it is very rewarding to be involved in a project that so many seem to find useful.
Cheers, David
Maybe I’m missing something here, but would this work for you? (already supported)
lambda do (2+2).should == 5 end.should_raise
David,
Oh, I understand now. Well, I will either publish it is as a ruby gem (since it is a general purpose extension) or as a rails plugin.
Anyway, thank you again.
Aslak,
Well, your solution is pretty nice. The only issue with it that it seems to be less readable.
Thank you for your response!
In spec/spec_helper.rb (in rspec) you’ll find this method gets mixed in to Proc:
That improves the readability a bit:
lambda { (2+2).should == 5 }.should_failDavid, surely your solution is quite nice. If my solution sucks, then at least I’ve proved to myself that I can hack a complicated systems like rspec in few minutes :)
Anyway, I will try to use both solutions to understand which I like the most.
Thank you very much!
Maybe it’s better call “planned features” and connect with time somehow.
In instance: <table class="CodeRay"><tr> <td title="click to toggle" class="line_numbers" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"></td> <td class="code"></td> </tr></table>That way you may track TODOs by time inside your RSpec (no Trac tickets needed :) Of course, there could be “milestones” instead of raw dates. Nevertheless, idea is obvious.
Oleg,
I think that this could be implemented using options of specify_negatively. Now you can specify a reason: http://rashkovskii.com/articles/2007/2/3/specify_negatively-updated
Why not using the same schema for planned date/milestone?