Rodrigo Rosenfeld Rosas
2012-Apr-13 19:09 UTC
[rspec-users] Smart mocks for ActiveRecord to speed up tests
Hello old friends, I''m getting back to Rails and Ruby programming full time again (Yay!) I''ve stopped doing Rails programming to work with Grails in 2009 after 2 years working with Rails due to a better job offer. Since then I''ve changed my job once more, but still on Grails, but I found a critical bug in Grails more than a week ago that didn''t seem to get much attention and is affecting the production system I maintain: http://jira.grails.org/browse/GRAILS-8994 As I couldn''t work around that bug and I can''t see myself reading the Grails source code (a real mess!) I decided to take another approach that would allow me to get back to Rails programming. I created a Devise / Warden strategy to send the cookies to the current system and it will return me the current logged in user. So I can set up Nginx to proxy some requests affected by the Grails bug to this new Rails application. I''ve finished this action last Monday but when I was about to send all the hard work to the server lots of my directories were suddenly gone when I was using them. I still don''t know what happened but I''ve replaced my hard-drive just in case, but it means I had to do all over again :( Anyway, now I got it back to work but testing a single action (an update/insert one) will take about a full second to run using RSpec and FactoryGirl. I really don''t like Grails but they had a great feature for unit testing. They were able to mock their domain class (that is how they call their models) in such a way that they can perform most of the operations you can do on real objects. Of course it have another serious bug that will prevent me to use it in lots of cases: http://jira.grails.org/browse/GRAILS-8854 But the idea is great as it works seamlessly like in a memory database. So, I''d like to be able to keep my tests easy to read but avoid touching the database that much. But my controller spec rely on lots of the setting of ActiveRecord for my models, like maximum calculations in before callbacks, custom foreign keys and depends: :delete_all in some has_many associations. Also I do also need a user object that Devise will accept on its sign_in helper method. Is there any place I could read more about writing faster tests without getting crazy writing so many mocks manually. For example, mocking a Grails domain class in a unit test would be as easy as: @mock(User) class UserTests... Then I''m able to do most operations using the Grails domain class API using the User mock. Is there something like this for Rails and Rspec? Is there some way that I could set up FactoryGirl to use those mocks instead of hitting the database when I want to? Am I going in the wrong direction here? If so, what would be the right direction? Thanks in advance. Glad to be part of the Ruby, Rails and Rspec community full-time again :D Cheers, Rodrigo. -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20120413/22694155/attachment.html>
David Chelimsky
2012-Apr-14 13:11 UTC
[rspec-users] Smart mocks for ActiveRecord to speed up tests
On Friday, April 13, 2012 at 2:09 PM, Rodrigo Rosenfeld Rosas wrote:> Hello old friends, I''m getting back to Rails and Ruby programming full time again (Yay!) > > I''ve stopped doing Rails programming to work with Grails in 2009 after 2 years working with Rails due to a better job offer. > > Since then I''ve changed my job once more, but still on Grails, but I found a critical bug in Grails more than a week ago that didn''t seem to get much attention and is affecting the production system I maintain: > > http://jira.grails.org/browse/GRAILS-8994 > > As I couldn''t work around that bug and I can''t see myself reading the Grails source code (a real mess!) I decided to take another approach that would allow me to get back to Rails programming. > > I created a Devise / Warden strategy to send the cookies to the current system and it will return me the current logged in user. So I can set up Nginx to proxy some requests affected by the Grails bug to this new Rails application. > > I''ve finished this action last Monday but when I was about to send all the hard work to the server lots of my directories were suddenly gone when I was using them. I still don''t know what happened but I''ve replaced my hard-drive just in case, but it means I had to do all over again :( > > Anyway, now I got it back to work but testing a single action (an update/insert one) will take about a full second to run using RSpec and FactoryGirl. > > I really don''t like Grails but they had a great feature for unit testing. They were able to mock their domain class (that is how they call their models) in such a way that they can perform most of the operations you can do on real objects. > > Of course it have another serious bug that will prevent me to use it in lots of cases: > > http://jira.grails.org/browse/GRAILS-8854 > > But the idea is great as it works seamlessly like in a memory database. > > So, I''d like to be able to keep my tests easy to read but avoid touching the database that much. > > But my controller spec rely on lots of the setting of ActiveRecord for my models, like maximum calculations in before callbacks, custom foreign keys and depends: :delete_all in some has_many associations. Also I do also need a user object that Devise will accept on its sign_in helper method. > > Is there any place I could read more about writing faster tests without getting crazy writing so many mocks manually. For example, mocking a Grails domain class in a unit test would be as easy as: > > @mock(User) > class UserTests... > > Then I''m able to do most operations using the Grails domain class API using the User mock. > > Is there something like this for Rails and Rspec? Is there some way that I could set up FactoryGirl to use those mocks instead of hitting the database when I want to? > > Am I going in the wrong direction here? If so, what would be the right direction? > > Thanks in advance. > > Glad to be part of the Ruby, Rails and Rspec community full-time again :D > > Cheers, > Rodrigo.You can use mock_model or stub_model to create model instances, or you can use FactoryGirl.build instead of FactoryGirl.create. Only mock_model will actually avoid db calls entirely (the others will still query for column names even if you don''t save anything). Those techniques, however, don''t help you with finders. You still have to stub any finders with the object generated by any of the aforementioned techniques: thing = mock_model(Thing) Thing.stub(:domain_specific_finder => [thing]) It''s not pretty, but it''s the best way I know of to get what you''re after. I started a lib called stubble a few years back that attempted to address this more holistically (https://github.com/dchelimsky/stubble) but I never released it as a gem because there were just too many different ways to access model data via the ActiveRecord API and they don''t all flow through a single part of the codebase that we can override in an extension. HTH, David -- David Chelimsky Sent with Sparrow (http://www.sparrowmailapp.com/?sig) -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20120414/0f1eeb5b/attachment-0001.html>
Rodrigo Rosenfeld Rosas
2012-Apr-16 02:23 UTC
[rspec-users] Smart mocks for ActiveRecord to speed up tests
Em 14-04-2012 10:11, David Chelimsky escreveu:> On Friday, April 13, 2012 at 2:09 PM, Rodrigo Rosenfeld Rosas wrote: >> Hello old friends, I''m getting back to Rails and Ruby programming >> full time again (Yay!) >> >> I''ve stopped doing Rails programming to work with Grails in 2009 >> after 2 years working with Rails due to a better job offer. >> >> Since then I''ve changed my job once more, but still on Grails, but I >> found a critical bug in Grails more than a week ago that didn''t seem >> to get much attention and is affecting the production system I maintain: >> >> http://jira.grails.org/browse/GRAILS-8994 >> >> As I couldn''t work around that bug and I can''t see myself reading the >> Grails source code (a real mess!) I decided to take another approach >> that would allow me to get back to Rails programming. >> >> I created a Devise / Warden strategy to send the cookies to the >> current system and it will return me the current logged in user. So I >> can set up Nginx to proxy some requests affected by the Grails bug to >> this new Rails application. >> >> I''ve finished this action last Monday but when I was about to send >> all the hard work to the server lots of my directories were suddenly >> gone when I was using them. I still don''t know what happened but I''ve >> replaced my hard-drive just in case, but it means I had to do all >> over again :( >> >> Anyway, now I got it back to work but testing a single action (an >> update/insert one) will take about a full second to run using RSpec >> and FactoryGirl. >> >> I really don''t like Grails but they had a great feature for unit >> testing. They were able to mock their domain class (that is how they >> call their models) in such a way that they can perform most of the >> operations you can do on real objects. >> >> Of course it have another serious bug that will prevent me to use it >> in lots of cases: >> >> http://jira.grails.org/browse/GRAILS-8854 >> >> But the idea is great as it works seamlessly like in a memory database. >> >> So, I''d like to be able to keep my tests easy to read but avoid >> touching the database that much. >> >> But my controller spec rely on lots of the setting of ActiveRecord >> for my models, like maximum calculations in before callbacks, custom >> foreign keys and depends: :delete_all in some has_many associations. >> Also I do also need a user object that Devise will accept on its >> sign_in helper method. >> >> Is there any place I could read more about writing faster tests >> without getting crazy writing so many mocks manually. For example, >> mocking a Grails domain class in a unit test would be as easy as: >> >> @mock(User) >> class UserTests... >> >> Then I''m able to do most operations using the Grails domain class API >> using the User mock. >> >> Is there something like this for Rails and Rspec? Is there some way >> that I could set up FactoryGirl to use those mocks instead of hitting >> the database when I want to? >> >> Am I going in the wrong direction here? If so, what would be the >> right direction? >> >> Thanks in advance. >> >> Glad to be part of the Ruby, Rails and Rspec community full-time again :D >> >> Cheers, >> Rodrigo. > You can use mock_model or stub_model to create model instances, or you > can use FactoryGirl.build instead of FactoryGirl.create. Only > mock_model will actually avoid db calls entirely (the others will > still query for column names even if you don''t save anything). > > Those techniques, however, don''t help you with finders. You still have > to stub any finders with the object generated by any of the > aforementioned techniques: > > thing = mock_model(Thing) > Thing.stub(:domain_specific_finder => [thing])I''m not sure what you''re suggesting. For example, the first part of my controller action looks like this: instance = Model.where(id: params[:id]).first_or_initialize(parent_id: params[:parentId]) The spec starts with something like this: root = FactoryGirl.create :field, label: ''ROOT'' post ''action'', parentId: root.id In the before/after Model callbacks there will be other kind of finders for the same class. How would you suggest me to change this block to use FactoryGirl.build and mock Model? Also, I couldn''t find how to mock ''open'' from ''open-uri'', so I''m currently mocking my controller''s action: controller.should_receive(:clear_fields_cache).exactly(3).times # 1 creation and 2 updates The action is implemented this way: require ''open-uri'' ... private def clear_fields_cache open INTEGRATION_BASE_URL + ''clearModelCache'', ''Cookie'' => authentication_cookie_string # includes JSESSIONID and rememberMe end But maybe it would be a great idea to check for the specific URL and cookie handling and I could do so if I was able to mock the ''open'' method from ''open-uri''. Any ideas on how to do that?> It''s not pretty, but it''s the best way I know of to get what you''re > after. I started a lib called stubble a few years back that attempted > to address this more holistically > (https://github.com/dchelimsky/stubble) but I never released it as a > gem because there were just too many different ways to access model > data via the ActiveRecord API and they don''t all flow through a single > part of the codebase that we can override in an extension.I agree with you. I''ve taken a look at the library but as you can see I''m using the new ''where'' API and this probably will keep changing each new AR release... Also, in Grails case, the mocked domain classes are maintained alongside with the real classes, so there is little chance of them getting out of sync. But of course the mocks have some limitation, like not being able to deal with custom SQL fragments: http://grails.org/doc/latest/guide/testing.html#unitTestingDomains Other than that there are also some bugs when dealing with association ids. I never thought it would be easy to have something like this for ActiveRecord but once there is a long time since I last often worked with Rails, things might have changed ;) Thank you a lot for your input and glad to be talking to you again :) Cheers, Rodrigo. -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20120415/8ce84137/attachment-0001.html>
David Chelimsky
2012-Apr-16 02:42 UTC
[rspec-users] Smart mocks for ActiveRecord to speed up tests
On Sunday, April 15, 2012 at 9:23 PM, Rodrigo Rosenfeld Rosas wrote:> Em 14-04-2012 10:11, David Chelimsky escreveu: > > On Friday, April 13, 2012 at 2:09 PM, Rodrigo Rosenfeld Rosas wrote: > > > Hello old friends, I''m getting back to Rails and Ruby programming full time again (Yay!) > > > > > > I''ve stopped doing Rails programming to work with Grails in 2009 after 2 years working with Rails due to a better job offer. > > > > > > Since then I''ve changed my job once more, but still on Grails, but I found a critical bug in Grails more than a week ago that didn''t seem to get much attention and is affecting the production system I maintain: > > > > > > http://jira.grails.org/browse/GRAILS-8994 > > > > > > As I couldn''t work around that bug and I can''t see myself reading the Grails source code (a real mess!) I decided to take another approach that would allow me to get back to Rails programming. > > > > > > I created a Devise / Warden strategy to send the cookies to the current system and it will return me the current logged in user. So I can set up Nginx to proxy some requests affected by the Grails bug to this new Rails application. > > > > > > I''ve finished this action last Monday but when I was about to send all the hard work to the server lots of my directories were suddenly gone when I was using them. I still don''t know what happened but I''ve replaced my hard-drive just in case, but it means I had to do all over again :( > > > > > > Anyway, now I got it back to work but testing a single action (an update/insert one) will take about a full second to run using RSpec and FactoryGirl. > > > > > > I really don''t like Grails but they had a great feature for unit testing. They were able to mock their domain class (that is how they call their models) in such a way that they can perform most of the operations you can do on real objects. > > > > > > Of course it have another serious bug that will prevent me to use it in lots of cases: > > > > > > http://jira.grails.org/browse/GRAILS-8854 > > > > > > But the idea is great as it works seamlessly like in a memory database. > > > > > > So, I''d like to be able to keep my tests easy to read but avoid touching the database that much. > > > > > > But my controller spec rely on lots of the setting of ActiveRecord for my models, like maximum calculations in before callbacks, custom foreign keys and depends: :delete_all in some has_many associations. Also I do also need a user object that Devise will accept on its sign_in helper method. > > > > > > Is there any place I could read more about writing faster tests without getting crazy writing so many mocks manually. For example, mocking a Grails domain class in a unit test would be as easy as: > > > > > > @mock(User) > > > class UserTests... > > > > > > Then I''m able to do most operations using the Grails domain class API using the User mock. > > > > > > Is there something like this for Rails and Rspec? Is there some way that I could set up FactoryGirl to use those mocks instead of hitting the database when I want to? > > > > > > Am I going in the wrong direction here? If so, what would be the right direction? > > > > > > Thanks in advance. > > > > > > Glad to be part of the Ruby, Rails and Rspec community full-time again :D > > > > > > Cheers, > > > Rodrigo. > > You can use mock_model or stub_model to create model instances, or you can use FactoryGirl.build instead of FactoryGirl.create. Only mock_model will actually avoid db calls entirely (the others will still query for column names even if you don''t save anything). > > > > Those techniques, however, don''t help you with finders. You still have to stub any finders with the object generated by any of the aforementioned techniques: > > > > thing = mock_model(Thing) > > Thing.stub(:domain_specific_finder => [thing]) > > > > > I''m not sure what you''re suggesting. For example, the first part of my controller action looks like this: > > instance = Model.where(id: params[:id]).first_or_initialize(parent_id: params[:parentId])The controller is interacting with an ActiveRecord API on the model instead of a domain-specific-finder. I''d recommend wrapping this in a method on the model and then mocking _that_ method.> The spec starts with something like this: > > root = FactoryGirl.create :field, label: ''ROOT'' > post ''action'', parentId: root.id > > In the before/after Model callbacks there will be other kind of finders for the same class.Think I''d need to see more of the code to comment on this.> How would you suggest me to change this block to use FactoryGirl.build and mock Model?Use one or the other (they both serve a similar purpose).> Also, I couldn''t find how to mock ''open'' from ''open-uri'', so I''m currently mocking my controller''s action: > > controller.should_receive(:clear_fields_cache).exactly(3).times # 1 creation and 2 updates > > The action is implemented this way: > > require ''open-uri'' > ... > private > def clear_fields_cache > open INTEGRATION_BASE_URL + ''clearModelCache'', > ''Cookie'' => authentication_cookie_string # includes JSESSIONID and rememberMe > end > > But maybe it would be a great idea to check for the specific URL and cookie handling and I could do so if I was able to mock the ''open'' method from ''open-uri''. Any ideas on how to do that?I use https://github.com/chrisk/fakeweb for this sort of thing. It operates more like a stub than a message expectation.> > It''s not pretty, but it''s the best way I know of to get what you''re after. I started a lib called stubble a few years back that attempted to address this more holistically (https://github.com/dchelimsky/stubble) but I never released it as a gem because there were just too many different ways to access model data via the ActiveRecord API and they don''t all flow through a single part of the codebase that we can override in an extension. > > > > I agree with you. I''ve taken a look at the library but as you can see I''m using the new ''where'' API and this probably will keep changing each new AR release...This is exactly why I like to wrap finders in domain-specific methods on the model :)> Also, in Grails case, the mocked domain classes are maintained alongside with the real classes, so there is little chance of them getting out of sync. But of course the mocks have some limitation, like not being able to deal with custom SQL fragments: > > http://grails.org/doc/latest/guide/testing.html#unitTestingDomains > > Other than that there are also some bugs when dealing with association ids. > > I never thought it would be easy to have something like this for ActiveRecord but once there is a long time since I last often worked with Rails, things might have changed ;) > > Thank you a lot for your input and glad to be talking to you again :) > > Cheers, > Rodrigo. > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org (mailto: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/20120415/ba1d15d8/attachment-0001.html>
Matt Wynne
2012-Apr-16 09:00 UTC
[rspec-users] Smart mocks for ActiveRecord to speed up tests
On 16 Apr 2012, at 03:42, David Chelimsky wrote:> On Sunday, April 15, 2012 at 9:23 PM, Rodrigo Rosenfeld Rosas wrote: >> Em 14-04-2012 10:11, David Chelimsky escreveu: >>> >>> On Friday, April 13, 2012 at 2:09 PM, Rodrigo Rosenfeld Rosas wrote: >>>> Hello old friends, I''m getting back to Rails and Ruby programming full time again (Yay!) >>>> >>>> I''ve stopped doing Rails programming to work with Grails in 2009 after 2 years working with Rails due to a better job offer. >>>> >>>> Since then I''ve changed my job once more, but still on Grails, but I found a critical bug in Grails more than a week ago that didn''t seem to get much attention and is affecting the production system I maintain: >>>> >>>> http://jira.grails.org/browse/GRAILS-8994 >>>> >>>> As I couldn''t work around that bug and I can''t see myself reading the Grails source code (a real mess!) I decided to take another approach that would allow me to get back to Rails programming. >>>> >>>> I created a Devise / Warden strategy to send the cookies to the current system and it will return me the current logged in user. So I can set up Nginx to proxy some requests affected by the Grails bug to this new Rails application. >>>> >>>> I''ve finished this action last Monday but when I was about to send all the hard work to the server lots of my directories were suddenly gone when I was using them. I still don''t know what happened but I''ve replaced my hard-drive just in case, but it means I had to do all over again :( >>>> >>>> Anyway, now I got it back to work but testing a single action (an update/insert one) will take about a full second to run using RSpec and FactoryGirl. >>>> >>>> I really don''t like Grails but they had a great feature for unit testing. They were able to mock their domain class (that is how they call their models) in such a way that they can perform most of the operations you can do on real objects. >>>> >>>> Of course it have another serious bug that will prevent me to use it in lots of cases: >>>> >>>> http://jira.grails.org/browse/GRAILS-8854 >>>> >>>> But the idea is great as it works seamlessly like in a memory database. >>>> >>>> So, I''d like to be able to keep my tests easy to read but avoid touching the database that much. >>>> >>>> But my controller spec rely on lots of the setting of ActiveRecord for my models, like maximum calculations in before callbacks, custom foreign keys and depends: :delete_all in some has_many associations. Also I do also need a user object that Devise will accept on its sign_in helper method. >>>> >>>> Is there any place I could read more about writing faster tests without getting crazy writing so many mocks manually. For example, mocking a Grails domain class in a unit test would be as easy as: >>>> >>>> @mock(User) >>>> class UserTests... >>>> >>>> Then I''m able to do most operations using the Grails domain class API using the User mock. >>>> >>>> Is there something like this for Rails and Rspec? Is there some way that I could set up FactoryGirl to use those mocks instead of hitting the database when I want to? >>>> >>>> Am I going in the wrong direction here? If so, what would be the right direction? >>>> >>>> Thanks in advance. >>>> >>>> Glad to be part of the Ruby, Rails and Rspec community full-time again :D >>>> >>>> Cheers, >>>> Rodrigo. >>> You can use mock_model or stub_model to create model instances, or you can use FactoryGirl.build instead of FactoryGirl.create. Only mock_model will actually avoid db calls entirely (the others will still query for column names even if you don''t save anything). >>> >>> Those techniques, however, don''t help you with finders. You still have to stub any finders with the object generated by any of the aforementioned techniques: >>> >>> thing = mock_model(Thing) >>> Thing.stub(:domain_specific_finder => [thing]) >> >> I''m not sure what you''re suggesting. For example, the first part of my controller action looks like this: >> >> instance = Model.where(id: params[:id]).first_or_initialize(parent_id: params[:parentId]) > The controller is interacting with an ActiveRecord API on the model instead of a domain-specific-finder. I''d recommend wrapping this in a method on the model and then mocking _that_ method. >> The spec starts with something like this: >> >> root = FactoryGirl.create :field, label: ''ROOT'' >> post ''action'', parentId: root.id >> >> In the before/after Model callbacks there will be other kind of finders for the same class. > Think I''d need to see more of the code to comment on this. >> How would you suggest me to change this block to use FactoryGirl.build and mock Model? > Use one or the other (they both serve a similar purpose). >> Also, I couldn''t find how to mock ''open'' from ''open-uri'', so I''m currently mocking my controller''s action: >> >> controller.should_receive(:clear_fields_cache).exactly(3).times # 1 creation and 2 updates >> >> The action is implemented this way: >> >> require ''open-uri'' >> ... >> private >> def clear_fields_cache >> open INTEGRATION_BASE_URL + ''clearModelCache'', >> ''Cookie'' => authentication_cookie_string # includes JSESSIONID and rememberMe >> end >> >> But maybe it would be a great idea to check for the specific URL and cookie handling and I could do so if I was able to mock the ''open'' method from ''open-uri''. Any ideas on how to do that? > I use https://github.com/chrisk/fakeweb for this sort of thing. It operates more like a stub than a message expectation. >>> It''s not pretty, but it''s the best way I know of to get what you''re after. I started a lib called stubble a few years back that attempted to address this more holistically (https://github.com/dchelimsky/stubble) but I never released it as a gem because there were just too many different ways to access model data via the ActiveRecord API and they don''t all flow through a single part of the codebase that we can override in an extension. >> I agree with you. I''ve taken a look at the library but as you can see I''m using the new ''where'' API and this probably will keep changing each new AR release... > This is exactly why I like to wrap finders in domain-specific methods on the model :) >> Also, in Grails case, the mocked domain classes are maintained alongside with the real classes, so there is little chance of them getting out of sync. But of course the mocks have some limitation, like not being able to deal with custom SQL fragments: >> >> http://grails.org/doc/latest/guide/testing.html#unitTestingDomains >> >> Other than that there are also some bugs when dealing with association ids. >> >> I never thought it would be easy to have something like this for ActiveRecord but once there is a long time since I last often worked with Rails, things might have changed ;) >> >> Thank you a lot for your input and glad to be talking to you again :) >> >> Cheers, >> Rodrigo. >> _______________________________________________ >> rspec-users mailing list >> rspec-users at rubyforge.org >> http://rubyforge.org/mailman/listinfo/rspec-users > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-usersWhat David said. There''s a great rule of thumb from Steve Freeman and Nat Pryce, who invented mocking. They say: "Don''t mock types you don''t own" If your Thing inherits from ActiveRecord, you don''t own it. That''s why it''s hard to mock, because the ActiveRecord API is very generic and has nothing to do with your domain. That''s also why it''s a bad idea to add anything more than skeletal behaviour to it. Instead, I''d suggest you add behaviour to a PORO adapter or wrapper around the ActiveRecord model that makes sense in your domain, and can therefore be easily mocked. cheers, Matt -- Freelance programmer & coach Author, http://pragprog.com/book/hwcuc/the-cucumber-book Founder, http://www.relishapp.com/ Twitter, https://twitter.com/mattwynne -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20120416/71c275d1/attachment.html>
Rodrigo Rosenfeld Rosas
2012-Apr-16 12:27 UTC
[rspec-users] Smart mocks for ActiveRecord to speed up tests
Thank you David and Matt for your comments. Indeed I had considered using FakeWeb but I thought that maybe there could be an easier way just to mock ''open'' directly since I guess it would be faster to run. With regards to the work around to overcome the issue with mocking AR so that I could speed up my tests I think that creating helpers in the model that will restrict the use of the AR API sounds like a good balance, but that wouldn''t make my specific tests faster. Here is why: my Model class shouldn''t be created or updated by any other code in my application. So I''d just have to add a new method to the Model class that would be specific to this action and I''d need to write a "unit" test for this new method. I quoted "unit" because it is actually an integration test as it will touch the database or otherwise I wouldn''t be able to upgrade Rails to a new version if I''m mocking some methods from AR and they change in a future version. So I''d need to touch the database anyway and my 9 specs would still take almost a full second to run while this specific controller spec takes about 0.7s (up to 2s depending on my system load). Well, actually, I guess RSpec has changed the way it measure spec runs since I last used it. I''m saying that because it will report me from 0.7 to 2s when I run "rspec path/to/my_controller_spec.rb", but if I run "rspec --profile" it will run the full suite about the same time and this specific controller action will take 0.38s. Anyway, I don''t see how I would create hundreds of specs and expect them to run in a few seconds and still have a reliable test suite. I mean, I want to feel safe if I''m upgrading to a newer Rails version even if it changed its AR API. The only real improvements I can see for my tests running fasts without changing anything in the specs is to use something like Hydra so that I could use the full power of my 6-core processor. Does RSpec has a built-in threaded option? I guess not because I don''t think it would be much effective on CRuby because it would probably lock on database access (or maybe not, I don''t know how the pg driver is implemented). For my particular application I suspect it is not cpu-heavy, but io-expensive instead. So maybe a multi-thread spec runner could improve its performance when running multiple specs. Best, Rodrigo.
Rodrigo Rosenfeld Rosas
2012-Apr-16 17:17 UTC
[rspec-users] Smart mocks for ActiveRecord to speed up tests
Em 16-04-2012 09:27, Rodrigo Rosenfeld Rosas escreveu:> Thank you David and Matt for your comments. > > Indeed I had considered using FakeWeb but I thought that maybe there > could be an easier way just to mock ''open'' directly since I guess it > would be faster to run. > > With regards to the work around to overcome the issue with mocking AR > so that I could speed up my tests I think that creating helpers in the > model that will restrict the use of the AR API sounds like a good > balance, but that wouldn''t make my specific tests faster. > > Here is why: my Model class shouldn''t be created or updated by any > other code in my application. So I''d just have to add a new method to > the Model class that would be specific to this action and I''d need to > write a "unit" test for this new method. I quoted "unit" because it is > actually an integration test as it will touch the database or > otherwise I wouldn''t be able to upgrade Rails to a new version if I''m > mocking some methods from AR and they change in a future version. > > So I''d need to touch the database anyway and my 9 specs would still > take almost a full second to run while this specific controller spec > takes about 0.7s (up to 2s depending on my system load). > > Well, actually, I guess RSpec has changed the way it measure spec runs > since I last used it. I''m saying that because it will report me from > 0.7 to 2s when I run "rspec path/to/my_controller_spec.rb", but if I > run "rspec --profile" it will run the full suite about the same time > and this specific controller action will take 0.38s. > > Anyway, I don''t see how I would create hundreds of specs and expect > them to run in a few seconds and still have a reliable test suite. I > mean, I want to feel safe if I''m upgrading to a newer Rails version > even if it changed its AR API. > > The only real improvements I can see for my tests running fasts > without changing anything in the specs is to use something like Hydra > so that I could use the full power of my 6-core processor. > > Does RSpec has a built-in threaded option? I guess not because I don''t > think it would be much effective on CRuby because it would probably > lock on database access (or maybe not, I don''t know how the pg driver > is implemented). For my particular application I suspect it is not > cpu-heavy, but io-expensive instead. So maybe a multi-thread spec > runner could improve its performance when running multiple specs. > > Best, > Rodrigo. >After some more investigation I could find out why it is taking so much time for the spec to run. Even for a simple model, calling a single "Model.create! name: ''Model name''" will take almost 100ms. So, here is usually what happens: root = FactoryGirl.create :model # 2 creates: # section = Section.create! name: ''Section'' # root = Model.create! section: section, label: ''Label'' post ''action'', parent_id: root.id # another Model creation in the controller alongside with 2 other creations of a has_many association So, without counting any assertions just for creating all records so far about 400ms were already spent. There will also be about 2 more updates and 2 "delete" statements in this spec and that will complete about 540ms of total spec run time. Another interesting fact is that for measuring this I''ve mocked the user and controller authentication related methods, but if I actually create the user the time for completing the spec will be about the same, even if in the logs it is reported to spend about 70ms for executing the insert in the user''s table. But I don''t think it should take so much time for creating/updating those records. See the output of the logging, for example: Completed 200 OK in 83ms (Views: 0.6ms | ActiveRecord: 14.9ms) This is consistent with the individual timing for each query/insert statement including validations. Even so, the first ''post'' method call in the spec takes about 100ms. So, my question is, if AR and Views are taking about 16ms what else could be taking about 70ms? This is also hard to profile since PostgreSQL will need more time for the first queries and will be much faster for subsequent ones... This means that running all specs will take much less time than if I run each spec separately and compare to the total time. But I don''t really think I''ll be able to test this action faster. I need to check for all created records including associations and fields set by the before_validation and removed by after_save so that I can get confidence in the code. And since all of this should only happen through this action, it wouldn''t matter if I moved lots of those checks to the "unit" test of my Model as the total time would remain the same. It would only make my specs harder to read with lots of mocks and stubbed methods in my controller spec. I mean, that is ok, I don''t aim in having single assertions per unit test. I just need to make sure things won''t break in a future refactoring or requirement changes. And of course I''d like my tests to run faster, but my current understanding is that I can''t get this test to run faster because it is testing a lot of code. Of course that an in-memory AR adapter could help me speeding up my tests but using mocks here won''t give me any confidence on my code base. Usually slow tests are not a problem when I''m working in some controller as I don''t need to run the full suite. But when I change some model or library it would be great to run the full suite and I''m a bit worried if that will become super slow some time in the future. But I think I''ll start to worry about this when this future becomes present because there is no easy replacement that will allow my specs to run faster. I''ll still be able to try Hydra before having to resort to mocks if things get really unsustainable. But thank you very much for your input on the subject. I was just worried if I was doing something fundamentally wrong. Cheers, Rodrigo.
Rodrigo Rosenfeld Rosas
2012-Apr-19 18:42 UTC
[rspec-users] Smart mocks for ActiveRecord to speed up tests
Just in case someone is curious, I''ve replaced ActiveRecord models with Sequel ones and my slowest example as reported by "rspec -d" got faster from about 0.5s to 0.2s. That is awesome! :) It seems AR is bloated... As if this wasn''t already amazing enough, my models seem much cleaner with Sequel than with AR. Cheers, Rodrigo. Em 16-04-2012 14:17, Rodrigo Rosenfeld Rosas escreveu:> Em 16-04-2012 09:27, Rodrigo Rosenfeld Rosas escreveu: >> Thank you David and Matt for your comments. >> >> Indeed I had considered using FakeWeb but I thought that maybe there >> could be an easier way just to mock ''open'' directly since I guess it >> would be faster to run. >> >> With regards to the work around to overcome the issue with mocking AR >> so that I could speed up my tests I think that creating helpers in >> the model that will restrict the use of the AR API sounds like a good >> balance, but that wouldn''t make my specific tests faster. >> >> Here is why: my Model class shouldn''t be created or updated by any >> other code in my application. So I''d just have to add a new method to >> the Model class that would be specific to this action and I''d need to >> write a "unit" test for this new method. I quoted "unit" because it >> is actually an integration test as it will touch the database or >> otherwise I wouldn''t be able to upgrade Rails to a new version if I''m >> mocking some methods from AR and they change in a future version. >> >> So I''d need to touch the database anyway and my 9 specs would still >> take almost a full second to run while this specific controller spec >> takes about 0.7s (up to 2s depending on my system load). >> >> Well, actually, I guess RSpec has changed the way it measure spec >> runs since I last used it. I''m saying that because it will report me >> from 0.7 to 2s when I run "rspec path/to/my_controller_spec.rb", but >> if I run "rspec --profile" it will run the full suite about the same >> time and this specific controller action will take 0.38s. >> >> Anyway, I don''t see how I would create hundreds of specs and expect >> them to run in a few seconds and still have a reliable test suite. I >> mean, I want to feel safe if I''m upgrading to a newer Rails version >> even if it changed its AR API. >> >> The only real improvements I can see for my tests running fasts >> without changing anything in the specs is to use something like Hydra >> so that I could use the full power of my 6-core processor. >> >> Does RSpec has a built-in threaded option? I guess not because I >> don''t think it would be much effective on CRuby because it would >> probably lock on database access (or maybe not, I don''t know how the >> pg driver is implemented). For my particular application I suspect it >> is not cpu-heavy, but io-expensive instead. So maybe a multi-thread >> spec runner could improve its performance when running multiple specs. >> >> Best, >> Rodrigo. >> > > After some more investigation I could find out why it is taking so > much time for the spec to run. Even for a simple model, calling a > single "Model.create! name: ''Model name''" will take almost 100ms. So, > here is usually what happens: > > root = FactoryGirl.create :model # 2 creates: > # section = Section.create! name: ''Section'' > # root = Model.create! section: section, label: ''Label'' > post ''action'', parent_id: root.id # another Model creation in the > controller alongside with 2 other creations of a has_many association > > So, without counting any assertions just for creating all records so > far about 400ms were already spent. There will also be about 2 more > updates and 2 "delete" statements in this spec and that will complete > about 540ms of total spec run time. > > Another interesting fact is that for measuring this I''ve mocked the > user and controller authentication related methods, but if I actually > create the user the time for completing the spec will be about the > same, even if in the logs it is reported to spend about 70ms for > executing the insert in the user''s table. > > But I don''t think it should take so much time for creating/updating > those records. See the output of the logging, for example: > > Completed 200 OK in 83ms (Views: 0.6ms | ActiveRecord: 14.9ms) > > This is consistent with the individual timing for each query/insert > statement including validations. Even so, the first ''post'' method call > in the spec takes about 100ms. > > So, my question is, if AR and Views are taking about 16ms what else > could be taking about 70ms? > > This is also hard to profile since PostgreSQL will need more time for > the first queries and will be much faster for subsequent ones... This > means that running all specs will take much less time than if I run > each spec separately and compare to the total time. > > But I don''t really think I''ll be able to test this action faster. I > need to check for all created records including associations and > fields set by the before_validation and removed by after_save so that > I can get confidence in the code. And since all of this should only > happen through this action, it wouldn''t matter if I moved lots of > those checks to the "unit" test of my Model as the total time would > remain the same. It would only make my specs harder to read with lots > of mocks and stubbed methods in my controller spec. > > I mean, that is ok, I don''t aim in having single assertions per unit > test. I just need to make sure things won''t break in a future > refactoring or requirement changes. And of course I''d like my tests to > run faster, but my current understanding is that I can''t get this test > to run faster because it is testing a lot of code. Of course that an > in-memory AR adapter could help me speeding up my tests but using > mocks here won''t give me any confidence on my code base. > > Usually slow tests are not a problem when I''m working in some > controller as I don''t need to run the full suite. But when I change > some model or library it would be great to run the full suite and I''m > a bit worried if that will become super slow some time in the future. > > But I think I''ll start to worry about this when this future becomes > present because there is no easy replacement that will allow my specs > to run faster. I''ll still be able to try Hydra before having to resort > to mocks if things get really unsustainable. > > But thank you very much for your input on the subject. I was just > worried if I was doing something fundamentally wrong. > > Cheers, > Rodrigo. > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >