Talk Review: Fast Rails Tests
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:
Test First
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.
Test Driven
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)
Class Approach
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
rspec spec/lib/calculate_total_price_spec.rb
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.
Module Approach
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