I had an interesting conversion recently that I thought I would share with you. The conversation is about bugs that would compensate each other and would ultimately lead to a correct behavior.

Let’s say we have a method like this:

public void PerformAction()

private void Step1()
    /* ... */

private void Step2()
    /* ... */

And let’s also say that both Step1 and Step2 contain bugs in their implementation, but the overall behavior of the PerformAction method is correct because, by pure chance, the bugs in the two steps cancel each other out.

How should you test this method? In the book (and in the previous emails), I argued that you should only test public methods. Private methods should be tested indirectly, via the public methods that use them.

In other words, PerformAction is the only method that should be unit tested here. Step1 and Step2 shouldn’t be covered at all.

But wouldn’t this guideline make you not notice the bugs in Step1 and Step2, since the behavior of PerformAction remains correct?

That’s a great question and to answer it, we need to once again step into the "observable behavior vs implementation details" territory.

In our example, the PerformAction method is the observable behavior and the two steps are implementation details. Therefore, the only thing that matters is the behavior of PerformAction.

The clients of PerformAction don’t care how exactly this method performs its function. The only thing they care about is the end result — its observable behavior. Unit tests should always mimic the clients of the code under test and thus shouldn’t care about the two private methods either.

So yes, this guideline does potentially lead to unnoticed bugs in private methods. But no, this is not a problem because those bugs will never manifest themselves.

I would even go as far as to argue that those are not bugs at all because it’s impossible for the clients of your application to stumble into them.

As the saying goes, If a tree falls in a forest and no one is around to hear it, does it really make a sound?

Now, if these weren’t private methods, then it would’ve been a different story. Now that these methods are part of the public API, they also become part of the observable behavior and thus should be tested and, consequently, fixed. But until then, the bugs in private methods aren’t a problem.

Of course, if you notice the bugs in the private Step1 and Step2, it’s best to fix them regardless of whether they are noticeable, in order to avoid confusion for future readers of these methods.

But that "fixing" would be a refactoring, and not a bug fix: you are changing the implementation details here; the observable behavior remains intact.



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

Workshops — I offer a 2-day workshop for organizations on Domain-Driven Design and Unit Testing. Reply to this email 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 »