Costa Shapiro
2010-Jul-17 18:18 UTC
[rspec-users] Data-wise context combination for controller speccing
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. Thank you for your attention, Costa. -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20100717/390ed5a8/attachment-0001.html>
David Chelimsky
2010-Jul-17 23:10 UTC
[rspec-users] Data-wise context combination for controller speccing
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? Cheers, David> Thank you for your attention, > Costa.-------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20100717/9f190fdf/attachment-0001.html>
Matt Wynne
2010-Jul-19 08:58 UTC
[rspec-users] Data-wise context combination for controller speccing
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?> > Cheers, > David > > >> Thank you for your attention, >> Costa. > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-userscheers, Matt http://blog.mattwynne.net +44(0)7974 430184 -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20100719/b5d6f188/attachment.html>
Wincent Colaiuta
2010-Jul-19 10:38 UTC
[rspec-users] Data-wise context combination for controller speccing
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 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
David Chelimsky
2010-Jul-19 10:55 UTC
[rspec-users] Data-wise context combination for controller speccing
On Jul 19, 2010, at 3:58 AM, Matt Wynne wrote:> > 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.Agreed this would be a potential pitfall of this approach. I''m also not convinced this is the right tool for this sort of thing. Seems like something best expressed in a table: data do author do new { ... } known { ... } end subject do new { ... } known { ... } end outcome do success { ... } end end scenarios <<-SCENARIOS | author | subject | outcome | | new | new | success | | known | new | success | | new | known | success | | known | known | success | SCENARIOS Something like this might work, but maybe it''s better suited for Cucumber, or FIT.> 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? > >> >> Cheers, >> David >> >> >>> Thank you for your attention, >>> Costa. >> >> _______________________________________________ >> rspec-users mailing list >> rspec-users at 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 at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users-------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20100719/e646bc0d/attachment-0001.html>
Matt Wynne
2010-Jul-19 12:54 UTC
[rspec-users] Data-wise context combination for controller speccing
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 > endThat''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 at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-userscheers, Matt http://blog.mattwynne.net +44(0)7974 430184
Phillip Koebbe
2010-Jul-19 14:20 UTC
[rspec-users] Data-wise context combination for controller speccing
On 2010-07-19 5:38 AM, 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 >Glad to see I''m not the only one that does this. :) Peace, Phillip
Wincent Colaiuta
2010-Jul-19 15:12 UTC
[rspec-users] Data-wise context combination for controller speccing
El 19/07/2010, a las 16:20, Phillip Koebbe escribi?:> On 2010-07-19 5:38 AM, Wincent Colaiuta wrote: >> 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 >> > > Glad to see I''m not the only one that does this. :)Yeah, I think it''s a legitimate technique, as long as you don''t get too carried away with it. I use it, for example, for some code quality specs: Wincent::SOURCE_FILES.each do |file| describe file do it ''contains no trailing whitespace'' do file.should_not have_trailing_whitespace end it ''contains no tabs'' do file.should_not contain_tabs end it ''has a newline at the end'' do file.should have_newline_at_end_of_file end end end Cheers, Wincent
Costa Shapiro
2010-Jul-21 19:58 UTC
[rspec-users] Data-wise context combination for controller speccing
Thank you very much for your valuable feedback. First, the example is a toy example of course, while the idea of mixing-matching contexts is not. Note that it would be a totally backward-compatible enhancement to rspec, and that a developer would still be in full control of what gets mixed-matched. Then, the subject might been confusing (not to mention the unsuccessful suggested syntax), as the idea is not necessarily coupled with data permutations. I accept the criticism about over-mocking, and even more than that, I find mocking AR extremely tedious, that''s why I''ve initially thought of coming up with something like http://github.com/costa/rspec-orm (there will be a separate mail on that, please don''t comment it here just yet). I hope I''ll bring some sort of POC -- which will be working for me at least -- soon. Any comments or suggestions are more than welcome. Cheers, Costa. On 19 July 2010 11:12, Wincent Colaiuta <win at wincent.com> wrote:> El 19/07/2010, a las 16:20, Phillip Koebbe escribi?: > > > On 2010-07-19 5:38 AM, Wincent Colaiuta wrote: > >> 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 > >> > > > > Glad to see I''m not the only one that does this. :) > > Yeah, I think it''s a legitimate technique, as long as you don''t get too > carried away with it. > > I use it, for example, for some code quality specs: > > Wincent::SOURCE_FILES.each do |file| > describe file do > it ''contains no trailing whitespace'' do > file.should_not have_trailing_whitespace > end > > it ''contains no tabs'' do > file.should_not contain_tabs > end > > it ''has a newline at the end'' do > file.should have_newline_at_end_of_file > end > end > end > > Cheers, > Wincent > > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >-------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20100721/6238cc0e/attachment.html>