Note: the question "should I test private methods or only public ones?" is a great reference to what I'm asking.
My question is: what is the most practical TDD process for building out a single, bulletproof reliable public method with complex private methods?
I best learn through examples, so here goes:
Chapter 1) Test coverage
Say I have a ruby class that only does one thing, it gives me bacon.
It would probably look something like this:
class Servant
def gimme_bacon
# a bunch of complicated private methods go here
end
private
# all of the private methods required to make the bacon
end
Now I can call servant = Servant.new
; servant.gimme_bacon
. Awesome, that's all I care about. All I want is my bacon.
But say my servant kind of sucks. That's because he doesn't have any of his private methods yet so gimme_bacon
just returns nil
. Alright, no problem, I'm a developer, I will give the Servant class all of the right private methods so he can finally gimme_bacon
.
In my pursuit for a reliable servant, I want to TDD all of his methods. But wait, all I care about is that he is going to gimme_bacon
. I really don't care about all of the steps he has to take as long as I get my bacon at the end of the day. After all, gimme_bacon
is the only public method.
So, I write my test like this:
RSpec.describe Servant do
let(:servant) { Servant.new }
it "should give me bacon when I tell it to!" do
expect(servant.gimme_bacon).to_not be_nil
end
end
Nice. I only tested the public method. Perfect, 100% test coverage. I move on to further develop the gimme_bacon
capability with full confidence that it is being tested.
Chapter 2) Writing moar private methods
After some development (unfortunately, not TDD because I'm adding private methods) I might have something like this (in pseudo code):
class Servant
attr_reader :bacon
def initialize(whats_in_the_fridge)
@bacon = whats_in_the_fridge[:bacon]
end
def gimme_bacon(specifications)
write_down_specifications(specifications)
google_awesome_recipes
go_grocery_shopping if bacon.nil?
cook_bacon
serve
end
private
attr_reader :specifications, :grocery_list
def write_down_specifications(specifications)
@specifications = specifications
end
def google_awesome_recipes
specifications.each do |x|
search_result = google_it(x)
add_to_grocery_list if looks_yummy?(search_result)
end
end
def google_it(item)
HTTParty.get "http://google.com/#q=#{item}"
end
def looks_yummy?(search_result)
search_result.match(/yummy/)
end
def add_to_grocery_list
@grocery_list ||= []
search_result.each do |tasty_item|
@grocery_list << tasty_item
end
end
def go_grocery_shopping
grocery_list.each { |item| buy_item(item) }
end
def buy_item
1_000_000 - item.cost
end
def cook_bacon
puts "#{bacon} slices #{bacon_size_in_inches} inch thick on skillet"
bacon.cooked = true
end
def bacon_size_in_inches
case specifications
when "chunky" then 2
when "kinda chunky" then 1
when "tiny" then 0.1
else
raise "wtf"
end
end
def serve
bacon + plate
end
def plate
"---"
end
end
Conclusion:
In hindsight, that's a lot of private methods.
There could be multiple points of failure because I didn't really TDD any of them. The above is a simple example, but what if the servant had to make decisions, say on which grocery store to go to depending on my specifications? What if the internet was down and he couldn't google, etc.
Yes, you could say that I should perhaps make a subclass, but I'm not so sure. All I want is one class that has one public method.
For future reference, what could I have done better in my TDD process?