I wrote about unit testing private methods on my blog and in the Unit Testing book.

The gist of it is that unit testing private methods:

  • Leads to coupling your tests to implementation details

  • Which leads to false alarms

  • Which in turn increases the noise component of the test’s signal-to-noise ratio (see below)

  • Which in turn reduces the test’s accuracy and value

[Enable Images]

Instead of testing private methods directly, you should test them indirectly, as part of the overarching observable behavior (using the class’s public API).

At this point, the common question is this:

What if those private methods contain important logic that does require unit testing?

And what if that logic is too complex, and testing it as part of the class’s observable behavior doesn’t provide sufficient coverage?

How to test it then?

In the book, I argue that such complexity in private methods is a sign of a missing abstraction which should be extracted into a separate class.

For example, the following Order class has a complex private method that is used by a much simpler public method:

public class Order
{
    private Customer _customer;
    private List<Product> _products;

    public string GenerateDescription()
    {
        return $"Customer name: {_customer.Name}, " +
            $"total number of products: {_products.Count}, " +
            $"total price: {GetPrice()}";
    }

    private decimal GetPrice()
    {
        decimal basePrice = /* Calculate based on _products */;
        decimal discounts = /* Calculate based on _customer */;
        decimal taxes = /* Calculate based on _products */;
        return basePrice - discounts + taxes;
    }
}

The private GetPrice() method in this example is the missing abstraction.

Instead of exposing it to a unit test, you should make this abstraction explicit by introducing a PriceCalculator class.

Alright, believe it or not, but all that was just a preamble and not the main point of this email :)

The main point is that I received an interesting comment on this guideline that I thought I’d share with you. Here it is:

Some may counter-argue that extracting a private method to a public class is not a solution as you make the logic public anyway. Therefore, exposing a private method isn’t any different from extracting a class.

What is your opinion on that?

That’s a great question.

Indeed, what’s the difference between making a private method public and extracting a (public) class out of that method?

Isn’t it just sugar-coating the same bad solution?

And my answer is (surprise-surprise!): no, it’s not.

The question assumes that unit tests are the first-class citizen, and the production code is not. Meaning that, the only reason why you would possibly extract a private method into a class is to enable unit testing.

But that’s not the only reason.

To see why, bring this argument to an extreme. Let’s say that you don’t have any unit tests in your project. Does it mean that you can keep all production code in one giant class? You can still sub-divide it using private methods, so why not?

Of course, that would be a horrible solution because abstractions (represented as separate classes) are useful regardless of whether you need to test those abstractions. They help you structure your project and keep its complexity manageable.

And so the necessity to unit test a private method is only a hint of a missing (or misplaced) abstraction. That necessity, in and of itself, is not a requirement to create such an abstraction.

In other words:

Code design always comes first. Unit tests merely help you reveal issues with that design.

Even without unit tests, it would still be a good idea to turn a complex private method into a class. Unit tests just point that idea out.

--Vlad


Enjoy this message? Here are more things you might like:

Consulting and training — I offer consulting and group training for organizations on Unit Testing and Domain-Driven Design. Reply to this email with the word "consult" to discuss.

Unit Testing Principles, Patterns and Practices — A book for people who already have some experience with unit testing and want to bring their skills to the next level.
Learn more »

My Pluralsight courses — The topics include Unit Testing, Domain-Driven Design, and more.
Learn more »