On 19 Jul 2010, at 11:38, Wincent Colaiuta wrote: > El 19/07/2010, a las 10:58, Matt Wynne escribió: > >> On 18 Jul 2010, at 00:10, David Chelimsky wrote: >> >>> On Jul 17, 2010, at 1:18 PM, Costa Shapiro wrote: >>> >>>> Hello, >>>> >>>> I've been thinking of how to express my idea in code, but since I've never >>>> been involved in RSpec development, I'd better have some feedback here >>>> first. >>>> The feature suggestion below applies to any controller-like code under >>>> spec, i.e. a stateless module producing output or just altering its data >>>> store (it doesn't necessarily have to be a C of the MVC, but I suppose >>>> merb/rails developers will particularly appreciate it). >>>> >>>> Here is a skimmed sample to illustrate the pain: >>>> >>>> describe BookController do >>>> >>>> context "registering a book" do >>>> >>>> specify "from a new author on a new subject" do >>>> auth = mock(:name => 'John Doe') >>>> Author.should_receive(:find_ >>>> by_name).and_return(nil) >>>> Author.should_receive(:new).and_return(auth) >>>> auth.should_receive(:save).and_return(true) >>>> >>>> subj = mock(:short => 'Mockery') >>>> Subject.should_receive(:find_by_short).and_return(nil) >>>> Subject.should_receive(:new).and_return(subj) >>>> subj.should_receive(:save).and_return(true) >>>> >>>> title = 'Specs on Steroids' >>>> >>>> book = mock >>>> Book.should_receive(:new).and_return(book) >>>> book.should_receive(:save).and_return(true) >>>> >>>> post :register :author => auth.name, :title => title, :subject => >>>> subj.short >>>> response.should be_success >>>> end >>>> >>>> specify "from a known author on a new subject" do >>>> auth = mock(:name => 'Joe Dohn') >>>> Author.should_receive(:find_by_name).and_return(auth) >>>> >>>> subj = mock(:short => 'Mockery') >>>> Subject.should_receive(:find_by_short).and_return(nil) >>>> Subject.should_receive(:new).and_return(subj) >>>> subj.should_receive(:save).and_return(true) >>>> >>>> title = 'Specs on Steroids II' >>>> >>>> book = mock >>>> Book.should_receive(:new).and_return(book) >>>> book.should_receive(:save).and_return(true) >>>> >>>> post :register :author => auth.name, :title => title, :subject => >>>> subj.short >>>> response.should be_success >>>> end >>>> >>>> specify "from a known author on a known subject" do >>>> auth = mock(:name => 'Joe Dohn') >>>> Author.should_receive(:find_by_name).and_return(auth) >>>> >>>> subj = mock(:short => 'Forgery') >>>> Subject.should_receive(:find_by_short).and_return(subj) >>>> >>>> #... >>>> end >>>> >>>> specify "from a new author on a known subject" do >>>> #... >>>> end >>>> end >>>> end >>>> >>>> >>>> And this is what I have in mind for doing exactly the same job: >>>> >>>> describe BookController do >>>> >>>> context "registering a book" do >>>> >>>> before :any, "from a new author", :author do >>>> @auth = mock(:name => 'John Doe') >>>> Author.should_receive(:find_by_name).and_return(nil) >>>> Author.should_receive(:new).and_return(@auth) >>>> @auth.should_receive(:save).and_return(true) >>>> end >>>> >>>> before :any, "from a known author", :author do >>>> @auth = mock(:name => 'Joe Dohn') >>>> Author.should_receive(:find_by_name).and_return(@auth) >>>> end >>>> >>>> before :any, "on a new subject", :subject do >>>> @subj = mock(:short => 'Mockery') >>>> Subject.should_receive(:find_by_short).and_return(nil) >>>> Subject.should_receive(:new).and_return(@subj) >>>> @subj.should_receive(:save).and_return(true) >>>> end >>>> >>>> before :any, "on a known subject", :subject do >>>> @subj = mock(:name => 'Joe Dohn') >>>> Subject.should_receive(:find_by_name).and_return(@subj) >>>> end >>>> >>>> it "should succeed", :with => [:author, :subject] do >>>> title = 'Specs on Steroids X' >>>> >>>> post :register :author => @auth.name, :title => title, :subject => >>>> @subj.short >>>> response.should be_success >>>> end >>>> end >>>> end >>>> >>>> A run of such specs will effectively multiply the tests — automatically — >>>> choosing before and after blocks as specified. >>>> I'm sorry, I haven't thought the DSL through, but I hope the main idea can >>>> be seen: contexts do not have to be hierarchical. >>>> In my opinion, adding some sort of context selection+combination >>>> capabilities (AOP-style) will contribute greatly to the expressiveness of >>>> the spec language. >>> >>> I think the idea of mixing/matching sub-contexts is very interesting, but >>> it definitely needs from fleshing out. It would have to be easy to >>> read/understand in the spec file as well as the output. >>> >>> Also, this only works if every combination should behave the same way. I >>> think we'd need a means of saying "given these combinations of data, expect >>> these outcomes". >>> >>> Anybody else have thoughts on this? >> >> It's a nice idea. >> >> I'm not sure whether I'd use it though. I think this idea comes from the >> desire to write specs that are *complete*, which I can perfectly understand >> but I don't think I subscribe to anymore. I prefer to really craft the >> examples so there's 'just enough' tests but no more than that. I'd be >> worried this might offer a temptation to think less about why you're writing >> each example, and I'd be worried how that would help me to do TDD. >> >> It should be possible to do something like this using macros now, right? Can >> I suggest that the OP has a go at refactoring his code using macros and we >> can see how it looks? > > I know that the posted code may be a contrived toy example for the purposes > of illustration, but when I see a spec like that alarm bells start to ring. > So much mocking, so many assertions in each example block etc. And it's not > at all clear what the pertinent behavior is that you want to test here, > because each example looks exactly like a one-to-one rewrite of the original > implementation that uses mocks instead of real objects. > > And when the alarm bells start to ring, before I think about changing my > testing framework to make things easier, I look at the code under test to see > if it could be changed to be more testable. > > So we basically have a controller action that accepts three parameters > (author, title, and subject), and it has a conditional code path for two of > those: > > if thing.exists > great > else > create it > end > > And in your spec you're wanting to test for all the different permutations of > new author/existing author and new subject/existing subject. > > First thing you could do to eliminate a lot of mocking is use something like > "find_or_create_by_name" and "find_or_create_by_short". Then you only have to > mock three calls (one for each parameter) and forget about the permutations > entirely. > > This is an example of pushing logic down into the model in order to make > controllers simpler and more testable. If "find_or_create_by_*" doesn't do > what you need it to, then create a model method which does. > > You could go even further and create a "register" method on your Book class > which accepted the three parameters of author, title, and subject, and did > everything which you are currently doing in your controller in the model > layer instead. Then your controller spec becomes ridiculously simple, can be > tested with a single mock, and the rest of the logic now resides in a model, > which is easily testable. > > So whether or not the example was a toy example, the need for the any > automatic permutation and spec generation in RSpec has disappeared. Let's > imagine, however, that the need was still there. Would adding this kind of > code to RSpec itself be a good idea? > > I don't necessarily think so. Matt says you can probably do this right now by > using macros. I don't actually know what he means by that, but I do know that > there are cases where I sometimes want a bunch of nearly identical specs, and > I generate them in code using enumeration or some other means; ie. dumb > example: > > [:foo, :bar, :baz].do |thing| > describe "#{thing} dimensions" do > it 'has length' do > thing.to_s.length.should > 0 > end > end > end
That's the kind of thing I meant by macro, yes. Is that a well-understood use of the word? > > So like I said, if your tests are painful, I think the first port of call > should be to look at how the code under test could be change. Good code isn't > just code that works. It's also code that is readable, maintainable, any > among many other things, testable. Adding support to RSpec to make it easier > to test bad code doesn't seem the right thing to do. > > Maybe you have another example that could illustrate how what you propose > would be useful, but right now I don't really see the need for this kind of > thing. > > Cheers, > Wincent > > > > _______________________________________________ > rspec-users mailing list > rspec-users@rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users cheers, Matt http://blog.mattwynne.net +44(0)7974 430184 _______________________________________________ rspec-users mailing list rspec-users@rubyforge.org http://rubyforge.org/mailman/listinfo/rspec-users