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

Reply via email to