In Progress
Unit 1, Lesson 1
In Progress

# Where do Private Methods Come From?

A question that I hear a lot is: “Do you test private methods? And if so, how?” In this, the first of a series, we’ll talk about where private methods come from, and what that has to do with testing them (or not).

Video transcript & code

Over the years I’ve done a lot of training and coaching on unit testing and test-driven design. And there’s this question that comes up a lot:

Do you test private methods? And if so, how?

This is a question that generates a lot of debate in Ruby-land. Some programmers are adamant about only testing public interfaces. Others break privacy in order to test the logic in private methods. Still others cite the difficulty of testing as one reason for never making methods private.

Me, I use private methods, and I don’t write tests for them. But the reason I don’t test them flows from how my private methods come into existence in the first place.

To illustrate what I mean, let’s take a look at some example code.

Here are the tests for an extremely rudimentary reverse-Polish notation calculator.

We have a test for the addition functionality,

where we create a calculator instance,

prepare an expression that pushes the numbers 3 and 2 onto the stack and then applies the addition operator,

Then asserts that the result is 5.

Similarly, there’s a subtraction test

that pushes a 10 and a 7 onto the stack and then applies the subtraction operator

and asserts that the result is 3.

Let’s take a quick look at the implementation so far.

The `Calc` class has a `stack` attribute,

which it initializes to an empty array.

When it is told to calculate an expression,

It splits it into tokens on whitespace

And then switches on the contents of the token.

Numbers are parsed into integers and pushed onto the `stack`.

If it encounters a `+` operator

It pops the left and right operand off the stack,

then pushes the result of adding those two operands back onto the stack.

The subtraction case works quite similarly.

At the end, the `calculate` method returns the last value on the stack.

``````require "minitest/autorun"

class TestCalc < MiniTest::Test
calc = Calc.new
expr = "3 2 +"
assert_equal 5, calc.calculate(expr)
end

def test_subtract
calc = Calc.new
expr = "10 7 -"
assert_equal 3, calc.calculate(expr)
end
end

class Calc

def initialize
@stack = []
end

def calculate(expression)
expression.split.each do |token|
case token
when /^\d+\$/
stack.push(Integer(token))
when "+"
right = stack.pop
left = stack.pop
stack.push(left + right)
when "-"
right = stack.pop
left = stack.pop
stack.push(left - right)
else
fail "Unexpected token: '#{token}'"
end
end
stack.last
end
end

``````

These tests are not what I’d call comprehensive.

But they do exercise almost all of the implementation code. And they only test public methods.

In fact, the current implementation code only has public methods.

Looking at the implementation code, we can observe that the addition and subtraction cases are almost completely identical. Before we add any new operators,

let’s refactor this code to eliminate some logic duplication.

we’ll have just once case branch for both binary operators

As before, it will pop left an right operands off the stack

but then, we use another method to look up an operator implementation.

We apply that implementation by calling it.

And we save the result to the stack.

Now let’s create that `lookup_binary_operator` method.

We’ll add a private section to the class

And we’ll add the method.

Inside it, we create a table mapping operators to lambdas that implement those operators.

And at the end we use `fetch` to look up and return the given operator.

``````class Calc

def initialize
@stack = []
end

def calculate(expression)
expression.split.each do |token|
case token
when /^\d+\$/
stack.push(Integer(token))
when "+", "-"
right = stack.pop
left = stack.pop
op = lookup_binary_operator(token)
result = op.call(left, right)
stack.push(result)
else
fail "Unexpected token: '#{token}'"
end
end
stack.last
end

private

def lookup_binary_operator(token)
{
"+" => ->(left, right) { left + right },
"-" => ->(left, right) { left - right },
}.fetch(token)
end
end

``````

When we run the tests, they still pass.

Is this new private method tested? Yes! Our refactoring moved and reorganized logic that was already tested from a public method into a private one. So its behavior is tested, by definition! We can prove it by bringing in `simplecov` and taking a look at the code coverage report.

What we’ve done here is extract the logic for picking out an operator implementation into a new method. And that’s where almost all of my private methods come from: extraction.

Now I know what you’re thinking: this is all too easy. Sure, private methods are well-tested if they are just extracted out of tested public methods. But what about when we need to add to the logic that exists in already-extracted private methods?

Indeed, that’s where things get sticky, and we’ll talk about it in an upcoming episode. Happy hacking!