Is it just me or does anyone else miss being able to create something quickly. Once you start writing all the tests your time is now increased by a factor of 10. I agree with the logic in testing, but am I doing something wrong? Take the simple example of a controller test for an index action that shows all the forum posts for a user account. The controller code would be something like what is shown below, and on a bad day may take a minute to do (a really bad day) ========before_filter :login_required before_filter :find_account def index @posts = @account.posts end ======== Now the controller tests are as following, and with the make it fail, make the update, make it pass strategy would could take about 10 minutes. ======== describe PostsController, "index" do before :each do @posts = mock("posts") @account.stub!(:posts).and_return(@posts) end it "should call the find account method" do controller.should_receive(:find... do :get end it "should assign the @account model" do do :get assigns[:account] ... end it "should call the login required" do controller.should_receive.... do :get end it "should call on the account.posts" do @account.should_recei... get :index end it "should assign posts" do do_get assigns[:posts].... end end ======== Now this is a simple example, but that is a lot of extra tests for three lines of code. I don''t know if it is just me, but it takes away some of the fun in being able to see results right away. I understand that with more complex code it may be easy to make a change that could have unexpected effects elsewhere. In the above example, there is no logic that would ever get changed by accident on later updates at least not by anyone who knows what they are doing. Maybe I am way out in left field with the tests that I write, but the tests above are done using the fails, update, pass sequence. I just can''t see the reasoning for writing all these tests for something so simple. I guess my thing is that I like writing tests when they will eliminate a lot of manual testing ex automated scheduling class. This would require a lot of different tests to make sure that the scheduler works as it should. I also don''t have a problem with model testing seeing as there may be some logic within the model that is unique to it. I do find view testing to be a very unenjoyable, and I haven''t event looked at stories yet. I am not trying to say that testing isn''t good and that everyone is on the wrong track, but rather wondering if, as in the example, am testing more than I should be. Thanks -- Posted via http://www.ruby-forum.com/.
A couple comments inline... On Thu, Mar 13, 2008 at 5:54 PM, __iso __ <lists at ruby-forum.com> wrote:> it "should call the login required" do > controller.should_receive.... > do :get > endThis is testing too much. Just stub the call to logged_in? or whatever> it "should call on the account.posts" do > @account.should_recei... > get :index > endThere''s no need for this. If your mocks are set up properly, then the only way for assigns[:post] to == your mock list of posts is if @account receives #posts. So this example duplicates some of the stuff that''s implicitly tested in the next one, which is a much more useful example.> it "should assign posts" do > do_get > assigns[:posts].... > end > end ><snip>> > I am not trying to say that testing isn''t good and that everyone is on > the wrong track, but rather wondering if, as in the example, am testing > more than I should be.Personally, I write tests when I feel that they will help me design my code better, give me confidence that the code works as expected, or serve as useful documentation. If you aren''t benefiting from certain tests, then don''t write them (*gasp*) Remember, BDD isn''t some magical routine that, if followed, leads to perfect applications. It can be very helpful in achieving certain desirable ends, but it''s not a substitute for your own thinking. Pragmatism and creativity reign supreme. Lately I''ve started to write fewer interaction-based specs (like the example you gave) when dealing with controllers. I tend to just make the request and let it go all the way down to the database, to make sure that everything is hooked up properly. The catch is that your controllers have to be super skinny - you should be able to cover an action with one or two examples - and have the majority of permutations be handled by the model (and thus covered with model specs). Pat
Thanks for the info Pat. To be honest that is what I wanted to hear. There seems to be such an importance set on the lines of code to lines of testing ratio that it seems a person is violating some rule if the ratio isn''t high enough to the right. The thing that bugged me when writing out the tests for controllers is that there really was no logic in the controller code. It looked just like 90% of the controllers I have created, so it made no sense to why I had (or so I thought) to write so many tests. It seems that most tests are written in the fear that someone will later make a change and not realize what they have done unless a test now fails. For example in model testing I am sure most people create a tests for the relations to other models, but for what reason. You write the test to whether the relation exists, it fails, you make the relation, it passes. Why not just write the relation and be done with it, because there is paranoia that someone will later decide to delete the relation...I guess. The same is also true for testing if there the various validators exist for the various columns. As I mentioned before, I am totally in favour of writing tests where there is some fear that it may not work as I expected, for that tests rock. It is the tests that acts as a double check that I don''t care for so much. Maybe the book below that amazon displays on main page every time I visit the site should be something I check out soon. Thanks again Pat. http://amazon.ca/gp/product/0321146530/ref=s9_flash_asin_image_1_wish_c2?ie=UTF8&coliid=I2GQJNWRWB147V&colid=7OMS5OB238SP&pf_rd_m=A3DWYIK6Y9EEQB&pf_rd_s=center-2&pf_rd_r=1Z4Z31VY2YQ6P9PHQSK6&pf_rd_t=101&pf_rd_p=289767901&pf_rd_i=915398 -- Posted via http://www.ruby-forum.com/.
On the other hand, earlier today my pair and I had to override a find_by_param method. We quickly wrote it down: def find_by_param(val) find_by_id val end We were puzzled, then saw that it should be User.find_by_param. Who''da thunk. Pat On Thu, Mar 13, 2008 at 10:42 PM, __iso __ <lists at ruby-forum.com> wrote:> Maybe the book below that amazon displays on main page every > time I visit the site should be something I check out soon. > http://amazon.ca/gp/product/0321146530/ref=s9_flash_asin_image_1_wish_c2?ie=UTF8&coliid=I2GQJNWRWB147V&colid=7OMS5OB238SP&pf_rd_m=A3DWYIK6Y9EEQB&pf_rd_s=center-2&pf_rd_r=1Z4Z31VY2YQ6P9PHQSK6&pf_rd_t=101&pf_rd_p=289767901&pf_rd_i=915398Yes, you should
> Is it just me or does anyone else miss being able to create something > quickly.When I write customer paid for software I strive to apply the best practices to ensure they have high quality, low bug software which is well-factored with the goal that it is easier to maintain and extend over its lifetime, assuming there will be developers added to the project over its lifetime that were not there for its original development efforts. When I write software for myself I tend to flip flop. Sometimes I am writing things to learn or play and I don''t test. Sometimes I want to work on my ability to write well designed testable software, so I do test. Also, don''t forget that sometimes when producing software you need to SPIKE some things out, which means you code upfront to investigate (kind of like prototyping) and then when you figure out what you want to do, or how-to do it you scratch the prototype and reimplement w/tests.> Once you start writing all the tests your time is now > increased by a factor of 10.Here''s another perspective to look at it from. When you write testable and tested software you spend less time coming back to it to track down and fix erroneous bugs. There is a much higher risk you will have to track down and fix erroneous bugs when you don''t test (or don''t test well there is a difference). There are also the things to account for like when you design for testability and the fact that you usually end up with simpler components which are easier to add to, extend or change. So as your program grows and changes from your customer''s perspective you are able to maintain fluid momentum for changing the code as well. When you don''t test there is usually a lot of momentum in the first few weeks, but then development slows down and even comes to a halt because the codebase is not well factored, and bugs are much more easily introduced with these changes and you spend more time tracking down bugs, fixing bugs and trying to understand the codebase. So I don''t agree with the factor of 10 because when you don''t test you spend a lot more time over the life of the project tracking down bugs, fixing bugs, and a much more difficult time changing the codebase to grow with its users needs and your customers desires. All of the time spent doing these things should be added to the time it took to originally develop a feature upfront since it really wasn''t done. People also have different levels of software development skills (design, testing, object decomposition, etc) and they can produce things at different speeds so a factor of 10 for you may be a factor or 0 for someone else.> I agree with the logic in testing, but am > I doing something wrong? > > Take the simple example of a controller test for an index action that > shows all the forum posts for a user account. > > The controller code would be something like what is shown below, and on > a bad day may take a minute to do (a really bad day) > ========> before_filter :login_required > before_filter :find_account > > def index > @posts = @account.posts > end > ========> > Now the controller tests are as following, and with the make it fail, > make the update, make it pass strategy would could take about 10 > minutes. > ========> > describe PostsController, "index" do > > before :each do > @posts = mock("posts") > @account.stub!(:posts).and_return(@posts) > end > > it "should call the find account method" do > controller.should_receive(:find... > do :get > end > > it "should assign the @account model" do > do :get > assigns[:account] ... > end > > it "should call the login required" do > controller.should_receive.... > do :get > end > > it "should call on the account.posts" do > @account.should_recei... > get :index > end > > it "should assign posts" do > do_get > assigns[:posts].... > end > end > > ========> > Now this is a simple example, but that is a lot of extra tests for three > lines of code. I don''t know if it is just me, but it takes away some of > the fun in being able to see results right away.First thing: do not stub or mock expect the object under test. When you''re testing a controller do not "stub!" the controller and do not "should_receive" the controller. This removes the value you get for testing the controller itself. There are exceptions to this rule, and this is largely because people violate the use of mixins. That is for another conversation though. I know that Pat said that the "controller.should_receive(:login_required)" is too much testing, but I''m going to be more forward. It is not too much testing, it is testing the wrong thing.> I understand that with more complex code it may be easy to make a change > that could have unexpected effects elsewhere. In the above example, > there is no logic that would ever get changed by accident on later > updates at least not by anyone who knows what they are doing. > > Maybe I am way out in left field with the tests that I write, but the > tests above are done using the fails, update, pass sequence. I just > can''t see the reasoning for writing all these tests for something so > simple. >So when the application gradually becomes more complex do you think you''ll eventually get to a point and say, "well I need to test this now?". Unfortunately even if you do do that when you get to point there''s a really good chance you didn''t write the code for testability, so the barrier to test is much higher, and it''s easier for people to give in and not test at this point because it''s now to difficult to test, and they can''t spend a long time fixing bad code they''ve written.> I guess my thing is that I like writing tests when they will eliminate a > lot of manual testing ex automated scheduling class. This would require > a lot of different tests to make sure that the scheduler works as it > should. I also don''t have a problem with model testing seeing as there > may be some logic within the model that is unique to it. I do find view > testing to be a very unenjoyable, and I haven''t event looked at stories > yet.Testing is not about developer enjoyment. It''s about producing high quality low bug software in a way that allows you to sustain a consistent development pace over the lifetime of the project. Although I do get enjoyment knowing I''m producing high quality software.> I am not trying to say that testing isn''t good and that everyone is on > the wrong track, but rather wondering if, as in the example, am testing > more than I should be.I think you''re experiencing growing pains. Thank you for posting your concerns and questions. I used to have some of the same questions you''re having. Hopefully my response has been helpful to raise some counter arguments to your concerns, Zach Dennis http://www.continuousthinking.com
Zach Dennis wrote:> When I write software for myself I tend to flip flop. Sometimes I am > writing things to learn or play and I don''t test.I did that on a recent small project and I was amazed at how fast I was able to get things done.> Here''s another perspective to look at it from. When you write testable > and tested software you spend less time coming back to it to track > down and fix erroneous bugs. There is a much higher risk you will have > to track down and fix erroneous bugs when you don''t test (or don''t > test well there is a difference).One of the issues that I have had a couple times is that since I am writing the tests and the corresponding code in a serial fashion, sometimes my mind is off tangent and I write a test that is incorrect, I then write the corresponding code to allow the incorrect test to pass. And voila I have given myself confidence that everything is in working order since I tested it, but it is wrong. I believe, that one of the reasons for this is that I am writing too many tests, and because of that the tests start to lose their meaning. The tests become a series of tasks that I hastily try to place a check next to so I can feel I am working efficiently and move on to the next one. I think cutting down on the number of tests I write will allow me to better ensure the tests I have are required and correct. I am beginning to think that if a person checks the code to test code ratio more than once a week they may be testing for the wrong reason.> First thing: do not stub or mock expect the object under test. > > When you''re testing a controller do not "stub!" the controller and do > not > "should_receive" the controller. This removes the value you get for > testing > the controller itself. There are exceptions to this rule, and this is > largely > because people violate the use of mixins. That is for another > conversation though.What would you recommend instead? Since the before_filter contains a login_required check I have to stub something to allow the test to be completed.> So when the application gradually becomes more complex do you think > you''ll eventually > get to a point and say, "well I need to test this now?". Unfortunately > even if you do > do that when you get to point there''s a really good chance you didn''t > write the code > for testability, so the barrier to test is much higher, and it''s > easier for people to > give in and not test at this point because it''s now to difficult to > test, and they can''t > spend a long time fixing bad code they''ve written.Good point. I guess my confusion is to whether testing should exist where there is really no logic. Take an update controller method: @user = User.find(params[:id]) if @user.update_attributes(params[:user) flash[:success] = "..." redirect_to admin_users_url else render :action => :index end Pretty simple stuff. Is it worth 40 lines of test code? That is what I am wondering, because I don''t think so.> Testing is not about developer enjoyment. It''s about producing high > quality > low bug software in a way that allows you to sustain a consistent > development > pace over the lifetime of the project.I find that the issue with writing more tests than are required is that things become more brittle. One small change results in 10 failed tests and 20 minutes of work to clean up, and that is not enjoyable. I think that writing tests should be enjoyable since that is what it takes for me to keep doing them.> I think you''re experiencing growing pains.Adolescence all over again :) Thank you for posting your> concerns and questions. I used to have some of the same questions > you''re having. Hopefully > my response has been helpful to raise some counter arguments to your > concerns,Input from experienced people always allows me to look at things from a different angle. Thanks Dennis. -- Posted via http://www.ruby-forum.com/.
I really agree with the topic starter. I''ve always done the rspec testing with all layers in isolation, a bit like the first example. Now for my latest project I went a bit for a more TDD approach, sending a request to a controller and testing the full stack, including the dynamic bits of the layout. It really helped me getting a quick start. Now I don''t know how this will work out on the long term, I''ll probably write some additional tests on the model level to assure workings with different variables. I found out that I was wasting way too much time trying to practise and especially trying the come up with the best theory behind BDD, while I just should have get started and make things work. On Fri, Mar 14, 2008 at 7:08 PM, __iso __ <lists at ruby-forum.com> wrote:> Zach Dennis wrote: > > When I write software for myself I tend to flip flop. Sometimes I am > > writing things to learn or play and I don''t test. > > I did that on a recent small project and I was amazed at how fast I was > able to get things done. > > > Here''s another perspective to look at it from. When you write testable > > and tested software you spend less time coming back to it to track > > down and fix erroneous bugs. There is a much higher risk you will have > > to track down and fix erroneous bugs when you don''t test (or don''t > > test well there is a difference). > > One of the issues that I have had a couple times is that since I am > writing the tests and the corresponding code in a serial fashion, > sometimes my mind is off tangent and I write a test that is incorrect, I > then write the corresponding code to allow the incorrect test to pass. > And voila I have given myself confidence that everything is in working > order since I tested it, but it is wrong. > > I believe, that one of the reasons for this is that I am writing too > many tests, and because of that the tests start to lose their meaning. > The tests become a series of tasks that I hastily try to place a check > next to so I can feel I am working efficiently and move on to the next > one. I think cutting down on the number of tests I write will allow me > to better ensure the tests I have are required and correct. > > I am beginning to think that if a person checks the code to test code > ratio more than once a week they may be testing for the wrong reason. > > > > First thing: do not stub or mock expect the object under test. > > > > When you''re testing a controller do not "stub!" the controller and do > > not > > "should_receive" the controller. This removes the value you get for > > testing > > the controller itself. There are exceptions to this rule, and this is > > largely > > because people violate the use of mixins. That is for another > > conversation though. > > What would you recommend instead? Since the before_filter contains a > login_required check I have to stub something to allow the test to be > completed. > > > > So when the application gradually becomes more complex do you think > > you''ll eventually > > get to a point and say, "well I need to test this now?". Unfortunately > > even if you do > > do that when you get to point there''s a really good chance you didn''t > > write the code > > for testability, so the barrier to test is much higher, and it''s > > easier for people to > > give in and not test at this point because it''s now to difficult to > > test, and they can''t > > spend a long time fixing bad code they''ve written. > > Good point. I guess my confusion is to whether testing should exist > where there is really no logic. Take an update controller method: > > @user = User.find(params[:id]) > if @user.update_attributes(params[:user) > flash[:success] = "..." > redirect_to admin_users_url > else > render :action => :index > end > > Pretty simple stuff. Is it worth 40 lines of test code? That is what I > am wondering, because I don''t think so. > > > Testing is not about developer enjoyment. It''s about producing high > > quality > > low bug software in a way that allows you to sustain a consistent > > development > > pace over the lifetime of the project. > > I find that the issue with writing more tests than are required is that > things become more brittle. One small change results in 10 failed tests > and 20 minutes of work to clean up, and that is not enjoyable. I think > that writing tests should be enjoyable since that is what it takes for > me to keep doing them. > > > I think you''re experiencing growing pains. > > Adolescence all over again :) > > Thank you for posting your > > concerns and questions. I used to have some of the same questions > > you''re having. Hopefully > > my response has been helpful to raise some counter arguments to your > > concerns, > > Input from experienced people always allows me to look at things from a > different angle. > > Thanks Dennis. > -- > Posted via http://www.ruby-forum.com/. > _______________________________________________ > 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/20080314/c5dfc76b/attachment.html
On Fri, Mar 14, 2008 at 2:08 PM, __iso __ <lists at ruby-forum.com> wrote:> Zach Dennis wrote: > > When I write software for myself I tend to flip flop. Sometimes I am > > writing things to learn or play and I don''t test. > > I did that on a recent small project and I was amazed at how fast I was > able to get things done. > > > > Here''s another perspective to look at it from. When you write testable > > and tested software you spend less time coming back to it to track > > down and fix erroneous bugs. There is a much higher risk you will have > > to track down and fix erroneous bugs when you don''t test (or don''t > > test well there is a difference). > > One of the issues that I have had a couple times is that since I am > writing the tests and the corresponding code in a serial fashion, > sometimes my mind is off tangent and I write a test that is incorrect, I > then write the corresponding code to allow the incorrect test to pass. > And voila I have given myself confidence that everything is in working > order since I tested it, but it is wrong. > > I believe, that one of the reasons for this is that I am writing too > many tests, and because of that the tests start to lose their meaning. > The tests become a series of tasks that I hastily try to place a check > next to so I can feel I am working efficiently and move on to the next > one. I think cutting down on the number of tests I write will allow me > to better ensure the tests I have are required and correct. > > I am beginning to think that if a person checks the code to test code > ratio more than once a week they may be testing for the wrong reason. > > > > > First thing: do not stub or mock expect the object under test. > > > > When you''re testing a controller do not "stub!" the controller and do > > not > > "should_receive" the controller. This removes the value you get for > > testing > > the controller itself. There are exceptions to this rule, and this is > > largely > > because people violate the use of mixins. That is for another > > conversation though. > > What would you recommend instead? Since the before_filter contains a > login_required check I have to stub something to allow the test to be > completed. >I typically test filters by themselves, and then stub out the filters when testing the action, so there is a clear separation between the two. For logins I use a helper to ensure that a controller/action is requiring login if necessary. describe SomeController, "#index" do it_requires_login :get, :index end Then when testing the action itself I do stub all before filters, because I have already tested them elsewhere. If the action requires login I have a login_with_user helper which sets the logged in user to some mock User and then returns it so I can use it in my test. LIke below: describe SomeController, ''#index" do def get_index get :index end before do stub_before_filters! @user = login_with_a_user end ... end> > > So when the application gradually becomes more complex do you think > > you''ll eventually > > get to a point and say, "well I need to test this now?". Unfortunately > > even if you do > > do that when you get to point there''s a really good chance you didn''t > > write the code > > for testability, so the barrier to test is much higher, and it''s > > easier for people to > > give in and not test at this point because it''s now to difficult to > > test, and they can''t > > spend a long time fixing bad code they''ve written. > > Good point. I guess my confusion is to whether testing should exist > where there is really no logic. Take an update controller method: > > @user = User.find(params[:id]) > if @user.update_attributes(params[:user) > flash[:success] = "..." > redirect_to admin_users_url > else > render :action => :index > end > > Pretty simple stuff. Is it worth 40 lines of test code? That is what I > am wondering, because I don''t think so.Can someone change this implementation and still have your tests pass, but have the implementation be broken? If they can then yes it is worth the 40 lines. Pat mentioned that he uses very skinny controllers and does integration tests which go through the whole stack to make sure things work. So if he doesn''t test the controller#action''s because they are too simple he at least will have a failing integration test if someone breaks the implementation. The problem I have with what Pat is doing is that it takes a lot of discipline from the developer to not let the controller grow into a cesspool of logic and interaction that shouldn''t be there, but you put it there because you aren''t "testing" it directly. From what I''ve seen most Rails apps have important interaction in the controller actions and logic in the before filters. I would not personally take this approach on customer paid for software. Pat may be more disciplined then me though. =)> > Testing is not about developer enjoyment. It''s about producing high > > quality > > low bug software in a way that allows you to sustain a consistent > > development > > pace over the lifetime of the project. > > I find that the issue with writing more tests than are required is that > things become more brittle. One small change results in 10 failed tests > and 20 minutes of work to clean up, and that is not enjoyable.If a change to one object results in multiple failing integration tests, then that is awesome because you know exactly what you broke with that change. If that change breaks a bunch of model, controller, etc specs then how there is an issue in how you''re testing.> I think > that writing tests should be enjoyable since that is what it takes for > me to keep doing them.Testing is a skill to be learned. I get a lot of enjoyment out of producing high quality software for my customers. Testing reinforces that I do just that. I also get enjoyment out of finding better ways to write software which makes them more easily testable. If you''re looking for the instantaneous gratification that you get with hacking something together real quick then you''re not going to find that with testing. If that''s your motive for writing tests then you''re writing tests for the wrong reasons.> > > I think you''re experiencing growing pains. > > Adolescence all over again :) > > > Thank you for posting your > > concerns and questions. I used to have some of the same questions > > you''re having. Hopefully > > my response has been helpful to raise some counter arguments to your > > concerns, > > Input from experienced people always allows me to look at things from a > different angle. > > Thanks Dennis. > >Dennis my last name. Zach is my first name ;) -- Zach Dennis http://www.continuousthinking.com
Zach Dennis wrote:> Can someone change this implementation and still have your tests pass, > but have the implementation be broken? If they can then yes it is > worth the 40 lines.It is partly this paranoia of someone changing the code that makes me question testing of this magnitude. Other than attaching files if a file was uploaded I have yet to have any real logic in my controllers, so I can''t imagine why I would change anything, but maybe that is how it all starts :)> The problem I have with what Pat is doing is that it takes a lot of > discipline from the developer to not let the controller grow into a > cesspool of logic and interaction that shouldn''t be there, but you put > it there because you aren''t "testing" it directly. From what I''ve seen > most Rails apps have important interaction in the controller actions > and logic in the before filters. I would not personally take this > approach on customer paid for software. Pat may be more disciplined > then me though. =)I guess that is where I am blind, as I don''t have any apps that have reached the point of "additional functionality" yet.> If a change to one object results in multiple failing integration > tests, then that is awesome because you know exactly what you broke > with that change. If that change breaks a bunch of model, controller, > etc specs then how there is an issue in how you''re testing.After writing a lot of tests I began to think that taking more of a black box approach, ie test what goes in and what comes out, is the best way in that it allows me to make changes inside the box, without having to pick up the pieces of all the broken tests.> Dennis my last name. Zach is my first name ;):) Sorry about that, I figured that out right after I posted. Somehow I saw Dennis and that is what registered upstairs. -- Posted via http://www.ruby-forum.com/.
On Fri, Mar 14, 2008 at 6:08 PM, Zach Dennis <zach.dennis at gmail.com> wrote:> Pat mentioned that he uses very skinny controllers and does > integration tests which go through the whole stack to make sure things > work. So if he doesn''t test the controller#action''s because they are > too simple he at least will have a failing integration test if someone > breaks the implementation. > > The problem I have with what Pat is doing is that it takes a lot of > discipline from the developer to not let the controller grow into a > cesspool of logic and interaction that shouldn''t be there, but you put > it there because you aren''t "testing" it directly. From what I''ve seen > most Rails apps have important interaction in the controller actions > and logic in the before filters. I would not personally take this > approach on customer paid for software. Pat may be more disciplined > then me though. =)The way to do this is to not put any business logic in the action, and the way to do THAT is to metaprogram the action away. Our controllers look like this: class PostsController < ApplicationController actions :show, :new, :create end with hooks for stuff like on_successful_create if the default behavior isn''t sufficient. It feels pretty radical at first, but it just takes a bit of creativity to make it work. I find it to be a very elegant approach. Pat
> > Lately I''ve started to write fewer interaction-based specs (like the > example you gave) when dealing with controllers. I tend to just make > the request and let it go all the way down to the database, to make > sure that everything is hooked up properly. The catch is that your > controllers have to be super skinny - you should be able to cover an > action with one or two examples - and have the majority of > permutations be handled by the model (and thus covered with model > specs).> The way to do this is to not put any business logic in the action, and > the way to do THAT is to metaprogram the action away. > > Our controllers look like this: > > class PostsController < ApplicationController > actions :show, :new, :create > end > > with hooks for stuff like on_successful_create if the default behavior > isn''t sufficient. > >I''ve been doing this for several months, ever since I started to use the make_resourceful plugin. There are several plugins that have this similar goal. The only downside I have run into with this approach is that people newer to rails and MVC in general have a very hard time figuring out what is going on. It is a layer of abstraction that is not for the faint of heart or inexperienced. Other than that I love the approach.> It feels pretty radical at first, but it just takes a bit of > creativity to make it work. I find it to be a very elegant approach. > >I am interested why having this approach has motivated you to take more of a state based approach on testing controllers. Assuming that you have other integration tests (stories) that test the full stack then having the controller specs hit the DB doesn''t seem to buy you much and will slow your tests down quite a bit as compared to the interaction-based approach. I know you know this already so I am trying to understand how this newer way of writing controllers is more suited for allowing the specs to hit the db. Skinny controllers have been common practice for quite a while so I don''t see how the layer of abstraction of declaring actions like above really changes things on the testing level. Do you feel that interaction-based specs on the controllers are too brittle.. or verbose.. or what? Thanks for sharing :) -Ben
Zach Dennis wrote:> Can someone change this implementation and still have your tests pass, > but have the implementation be broken? If they can then yes it is > worth the 40 lines. > > Pat mentioned that he uses very skinny controllers and does > integration tests which go through the whole stack to make sure things > work. So if he doesn''t test the controller#action''s because they are > too simple he at least will have a failing integration test if someone > breaks the implementation. > > The problem I have with what Pat is doing is that it takes a lot of > discipline from the developer to not let the controller grow into a > cesspool of logic and interaction that shouldn''t be there, but you put > it there because you aren''t "testing" it directly. From what I''ve seen > most Rails apps have important interaction in the controller actions > and logic in the before filters. I would not personally take this > approach on customer paid for software. Pat may be more disciplined > then me though. =) > >I totally agree with this point. Using interaction-based testing really helps in forcing the logic down into the model. I''m curious about your comment: "From what I''ve seen most Rails apps have important interaction in the controller actions and logic in the before filters". What are you exactly referring to? I generally use before filters for authentication... How have you seen them abused? -Ben
On Sat, Mar 15, 2008 at 5:43 PM, Ben Mabey <ben at benmabey.com> wrote:> Zach Dennis wrote: > > Can someone change this implementation and still have your tests pass, > > but have the implementation be broken? If they can then yes it is > > worth the 40 lines. > > > > Pat mentioned that he uses very skinny controllers and does > > integration tests which go through the whole stack to make sure things > > work. So if he doesn''t test the controller#action''s because they are > > too simple he at least will have a failing integration test if someone > > breaks the implementation. > > > > The problem I have with what Pat is doing is that it takes a lot of > > discipline from the developer to not let the controller grow into a > > cesspool of logic and interaction that shouldn''t be there, but you put > > it there because you aren''t "testing" it directly. From what I''ve seen > > most Rails apps have important interaction in the controller actions > > and logic in the before filters. I would not personally take this > > approach on customer paid for software. Pat may be more disciplined > > then me though. =) > > > > > > I totally agree with this point. Using interaction-based testing really > helps in forcing the logic down into the model. > > I''m curious about your comment: "From what I''ve seen most Rails apps > > have important interaction in the controller actions > and logic in the before filters". What are you exactly referring to? I > generally use before filters for authentication... How have you seen > them abused?I''ve seen before filters be abused. Authentication is typical an application level responsibility and it makes sense for the controller to protect its resources. No qualms there. I have inherited code bases which abused before filters for things like checking permissions for business requirements. For example: class ProjectsController < ApplicationController before_filter :login_required # i''m ok with this before_filter :ensure_project_manager, :only => [:new, :create] before_filter :ensure_project_write_access, :only => [:edit, :update] # ... I''ve seen this filter list go on and on and on, based on any possible persmission I would rather introduce a Manager/Service object which is responsible for accessing a project, and allow that do handle the responsibility for knowing what permissions allow someone to create or edit a project. I''ve been experimenting with using an approach similar in spirit to what Pat is doing, but slightly different: class ApplicationController < ActionController::Base rescue_from AccessDenied, ResourceNotFoundError do |exception| redirect_to "/access_denied.html" end # ... end class ProjectsController < ApplicationController before_filter :login_required def new @project = ProjectManager.new_project end def create ProjectManager.create_project params[:project] do |project_creation| project_creation.success do |project| flash[:notice] = "Project created successfully" @project = project end project_creation.failure do |project| flash[:notice] = "Project creation failed" @project = project render :action => "new" end end # ... end I tend to like this for projects I''ve worked on where it doesn''t make sense to shove all of the responsibility onto a model because it abstracts out the "Manager" who is in charge of ensuring permissions and doing odds and ends things involved in the creation of a project that the model should not be doing (like upload an associated picture of the project manager or ensure memberships status). In the above approach the application controller is setup to rescue from certain application exceptions, and the controllers just do their thing, and the managers which enforce the business logic will throw the exceptions it a business rule has been violated. -- Zach Dennis http://www.continuousthinking.com
On Sat, Mar 15, 2008 at 2:38 PM, Ben Mabey <ben at benmabey.com> wrote:> I am interested why having this approach has motivated you to take more > of a state based approach on testing controllers. > Assuming that you have other integration tests (stories) that test the > full stack then having the controller specs hit the DB doesn''t seem to > buy you much and will slow your tests down quite a bit as compared to > the interaction-based approach. I know you know this already so I am > trying to understand how this newer way of writing controllers is more > suited for allowing the specs to hit the db. Skinny controllers have > been common practice for quite a while so I don''t see how the layer of > abstraction of declaring actions like above really changes things on the > testing level. Do you feel that interaction-based specs on the > controllers are too brittle.. or verbose.. or what?At work where we''re doing this, we don''t use stories. So our controller specs act as the integration tests. I think if we were using stories, we likely wouldn''t have controller tests at all. Not too sure about that though. Another major reason for not using interaction-based tests is that all of the controllers are identical, so any specs we write are just testing framework code. No sense in that. Pat
On Sat, Mar 15, 2008 at 4:56 PM, Pat Maddox <pergesu at gmail.com> wrote:> > At work where we''re doing this, we don''t use stories. So our > controller specs act as the integration tests. I think if we were > using stories, we likely wouldn''t have controller tests at all. Not > too sure about that though.This should be "I''m not 100% sure about that though"
Zach Dennis wrote:> On Sat, Mar 15, 2008 at 5:43 PM, Ben Mabey <ben at benmabey.com> wrote: > >> Zach Dennis wrote: >> > Can someone change this implementation and still have your tests pass, >> > but have the implementation be broken? If they can then yes it is >> > worth the 40 lines. >> > >> > Pat mentioned that he uses very skinny controllers and does >> > integration tests which go through the whole stack to make sure things >> > work. So if he doesn''t test the controller#action''s because they are >> > too simple he at least will have a failing integration test if someone >> > breaks the implementation. >> > >> > The problem I have with what Pat is doing is that it takes a lot of >> > discipline from the developer to not let the controller grow into a >> > cesspool of logic and interaction that shouldn''t be there, but you put >> > it there because you aren''t "testing" it directly. From what I''ve seen >> > most Rails apps have important interaction in the controller actions >> > and logic in the before filters. I would not personally take this >> > approach on customer paid for software. Pat may be more disciplined >> > then me though. =) >> > >> > >> >> I totally agree with this point. Using interaction-based testing really >> helps in forcing the logic down into the model. >> >> I''m curious about your comment: "From what I''ve seen most Rails apps >> >> have important interaction in the controller actions >> and logic in the before filters". What are you exactly referring to? I >> generally use before filters for authentication... How have you seen >> them abused? >> > > I''ve seen before filters be abused. Authentication is typical an > application level responsibility and it makes sense for the controller > to protect its resources. No qualms there. I have inherited code bases > which abused before filters for things like checking permissions for > business requirements. For example: > > class ProjectsController < ApplicationController > before_filter :login_required # i''m ok with this > before_filter :ensure_project_manager, :only => [:new, :create] > before_filter :ensure_project_write_access, :only => [:edit, :update] > # ... I''ve seen this filter list go on and on and on, based on > any possible persmission >Ahh, yes... I have seen this before. In fact people have told me that they don''t test there controllers because of such permutations that take too long to test fully (because they have to test it on EVERY controller with those set of filters). Thats when I tell them that if things are too hard to test that is probably a good sign the are doing something wrong. :) In the past I have always pushed this in the model. I have ofter though just let the controller call one simple predicate method on the model in a filter. So perhaps I am guilty of abusing filters as well...> I would rather introduce a Manager/Service object which is responsible > for accessing a project, and allow that do handle the responsibility > for knowing what permissions allow someone to create or edit a > project. > >Makes sense- I really like that idea. Having a service handle all of this makes a lot of sense the more I think about it. As I have developed rails apps I always keep my controllers extremely thin but certain models become too large, IMO. I''ve actually been reading DDD and have been trying to think of ways of creating more service objects to remove the extra responsibility I have added in my models. Thanks for pointing this one out to me.> I''ve been experimenting with using an approach similar in spirit to > what Pat is doing, but slightly different: > > class ApplicationController < ActionController::Base > rescue_from AccessDenied, ResourceNotFoundError do |exception| > redirect_to "/access_denied.html" > end > # ... > end > > class ProjectsController < ApplicationController > before_filter :login_required > > def new > @project = ProjectManager.new_project > end > > def create > ProjectManager.create_project params[:project] do |project_creation| > project_creation.success do |project| > flash[:notice] = "Project created successfully" > @project = project > end > > project_creation.failure do |project| > flash[:notice] = "Project creation failed" > @project = project > render :action => "new" > end > end > # ... > end > > I tend to like this for projects I''ve worked on where it doesn''t make > sense to shove all of the responsibility onto a model because it > abstracts out the "Manager" who is in charge of ensuring permissions > and doing odds and ends things involved in the creation of a project > that the model should not be doing (like upload an associated picture > of the project manager or ensure memberships status). >Very nice... I will definitely steal that slick use of blocks ;) My one question about this implementation is how does the ProjectManager get access to the session or at least gets the information about the current user? Without that information you can''t have any permissions logic in the manager...> In the above approach the application controller is setup to rescue > from certain application exceptions, and the controllers just do their > thing, and the managers which enforce the business logic will throw > the exceptions it a business rule has been violated. > >I have used a similar approach in apps in the past (by throwing an AccessDenied exception), but I have somewhat gone away from that. My hesitation was caused by me wondering if a violation of these permissions really deems an exception. Is having a user trying to get into an area where they don''t have the needed rights *exceptional*? So seeing you use this same pattern makes me think that it probably is a good use of exceptions. Having the logic in a manger I think the only way to handle incorrect permissions cleanly would be an exception so I guess the argument is somehwat moot in this context. Thank so much for taking the time to answer my question. I always learn a great deal from this list so thank you for contributing so much to it. -Ben