By Corey Haines, Ruby on Rails globe trotter
Corey started his talk by jumping immediately to his core issue, using live demo from the command line to further his point, that can be summarized as: having tests is good, but when the tests take time to run, you run them less. And your tests are too slow! "Your" meaning everyone (else), as Corey soon demonstrates by first running a standard Rails testsuite, sitting down during the ten to twenty seconds that the suite takes, before launching his own testsuite, running in sub-second time. No review can do full justice to Corey's talk, as his talk was as entertaining as it was interesting - if you have the opportunity to see him, eeh, perform, I encourage you to do so.
Having shown his point, Corey then explained why the difference between one and ten seconds matter, evoking a quite orthodox TDD approach, running his tests with each line new line of code, in a "test first" approach: write the test for the new method, run it (it fails), add the method, doing nothing, run again (it fails again), then creating the concrete code, and running to confirm that it's working. While I recognize the difference (even for some dozens of second), I'm not sure this level of adherence to TDD is required (I consider myself to be a TDD practitioner, but not a test-first follower). Would you follow such an approach, you clearly need sub-second test.
At this point, Corey has shown many things... Except the reason behind the impressive difference between running his tests and the standard ones from rails. He made a last point before showing the real code, explaining that good and fast testing mean reducing dependencies - which is not really surprising (its why there is this unit in 'unit testing'). And, for Corey, the biggest dependency in the tests of a Rails application is... Rails itself. The framework has matured and evolved in a very nice way those last years - but it has also became larger, and it's loading slower.
His example revolved around a simple Order - Products model, with a method on Order computing the total price from the list of Items. When testing this kind of business logic, you normally does not need any DB or other Rails component: you should be testing simple Ruby code. But when you're using your Order class, it inherit from ActiveRecord::Base, and this means that it brings a lots of stuff that you do not need.
At this point, I was pretty much convinced by Corey demonstration, as the principle described are general sound design advice (remove dependencies, test a very small part of the code, unit testing is not integration testing, business logic test should not depend on the database).
But I was (and still am) less convinced by his solution of this problem, which is to extract the business logic code in an apart "helper like" class method. Being an opiniated domain driven proponent, I do not see with a good eye exporting all my business logic in separate, procedural methods. I like the demonstration of the problem, I like the result (very fast running test), but I am (constructively) critical of the problem description and the proposed solution.
I think that the problem here is not that we have business logic in the model - it's where it's supposed to be, but that in Rails, the model is mixed with the database access (ActiveRecord in this case). While this design choice has clearly tremendous advantages (first and foremost its simplicity and productivity), it is also the cause of the problem described here. Well, once more, design is all about choice, and I'm not contesting this one - just pointing that we have to live with its consequences, as shown by Corey's examples.
As for the solution, it is for me a very good step in decoupling the business logic from the ActiveRecord/Rails aspects, but as said, having lots of procedural code does not bode very well with me, especially in an Object Oriented language such as Ruby. A nice solution may be in Modules usage. In Corey example, I would put the "compute_price" method, along the other that could exists on the Order object inside an "BaseOrder" module that I could import in Order. This would give me the advantages of decoupling from ActiveRecord, without loosing much of the OO design : a real "domain model", distinct from the database, that I could use in tests or other situation requiring only the business logic. Corey solution, while resolving his pain point, does it by sacrificing good design, which unsettle me a bit. Last point, should (as Corey suggested) the compute_price method vary much (because of special promotions, reuse in other contexts, etc...), then some kind of Strategy Pattern may be a better solution yet.
I'm more than interested in anyone opinion of this topic, or to correct any flaw in my analyse, logic or proposed solution.
If you're interested in those questions, I recommend to read Steve Klabnik post about OO design in Rails.