Today I watched this amazing talk by Corey Haines called “Fast Rails Test” and here I’m going to talk about some of the main points of this talk.
It’s interesting to see how people is using rails, first they began to understand rails and its test directory, that made testing part of the default development cycle.
After a while, people began to talk about Fat Models, Skinny Controllers for extracting the business logic into models, and a lot of other concepts like REST, Factories, Mocks and Stubs, etc. as part of the way you develop rails applications and how to test them.
The problem was you ended up with having a huge test suite that takes forever to run. Then Corey speaks this is a problem and the differences between Test First and Test Driven in development and how you react to pain in each one:
If test are slow I’ll modify my test in order to avoid that pain, that’s why specjour, guard and spork appeared, but in reality this are band-aids for the problem.
If test are slow, it is because the application is too complex. If you have a small test that takes forever to run is because is coupled a lot with a lot of code, in the case of rails, the conclusion is rails itself is a 3rd party dependency and is not really needed to test your core business logic.
That said, the proposed approach is to use plain ruby classes or modules to implement your business logic, and test them in isolation of rails.
As Gary Bernhardt said in his Sucks/Rocks series: “Rails is not your application”, in that sense, it makes perfect sense to test your core App separately.
An Example of Business Core Code Outside Rails
This is a classic code that could be found in a rails App
class ShoppingCart < ActiveRecord::Base has_many :products def total_price products.map(&:price).inject(:+) end end
The problem here, is that you have to load all rails to test the total price method. Now, Corey proposes two different approaches to test this outside rails which I will show with their test code (Taking Corey’s code)
I like to put external rails classes under the lib directory, that’s a personal preference. So this would be the test code:
# spec/lib/calculate_total_price_spec.rb describe CalculateTotalPrice do it "returns 0 when there are no products" do expect(CalculateTotalPrice.of()).to eq(0) end it "returns the sum of prices of the products" do products = [stub(price: 5), stub(price: 10)] expect(CalculateTotalPrice.of(products)).to eq(10) end end
You run fast the spec with
And the implementation of the class
# lib/calculate_total_price.rb class CalculateTotalPrice def self.of(elements) elements.map(&:price).inject(:+) end end
Note that the implementation is almost the same, and because ruby is dynamically typed, this class will work with any class that has a price method and return an object that respond to “+”. Finally the active record class uses it like:
class ShoppingCart < ActiveRecord::Base has_many :products def total_price CalculateTotalPrice.of(products) end end
The thing is that for testing the logic of total price, now we don’t need rails at all making our tests amazingly fast. The example is very simple but it can be used for more Apps too.
That example you’ll find in the video of the talk ;), but the idea is almost the same as the class approach.
In conclusion, taking care of the quickness of our test suite in terms of design, recognizing third-party libraries and improving our design by doing so, make us better coders, because tests tell us what to do to complete our logic, you will run them more often, so listen to your test!.
I’ll leave a link to another talk mentioned by Corey, that’s awesome too and I’ll be summarizing (more concisely) called The Deep Synergy Between Testability and Good Design