Google the phrase “should I test private methods,” and you’ll get a whole host of opinions that boil down to “no.” Fortunately, I’m here to say it’s okay to test private methods.
Table of Contents
What’s the Big Deal?
At the moment, I’m training to teach a software course at my university, and I was working on a utility class in Java which had a ton of private helper methods. In my particular case, there wasn’t actually any exposed public methods beyond
main, and I find it challenging to write tests that interact with input streams. As a result, I wanted to write some JUnit tests to prove the functionality of the private methods.
However, when I turned to Google, I found that most of the experts say not to test private methods:
- Why is white box testing discouraged in OOP?
- Should Private Methods Be Tested?
- How do you unit test private methods?
- Do you unit test private methods?
Instead, they argue that we should test our public methods which call our private methods. In the following subsections, I’ll try to break down their argument.
Private Methods Are Implementation Details
A common argument against testing private methods is that private methods are implementation details:
A private method is an implementation detail that should be hidden to the users of the class. Testing private methods breaks encapsulation.jop, 2008
In other words, how a solution is implemented is irrelevant from a testing point of view. Ultimately, we want to test our solution based on its expected behavior to the user.
Private Method Tests Are Brittle
Since private methods are implementation details, we’re free to change those details with little or no cost to us. However, if we choose to test our private methods, we run the risk of breaking our tests. As a result, our tests become brittle meaning they break easily. In fact, I think a Stack Overflow user said it best:
The problem here is that those “future code changes” invariably mean refactoring the inner workings of some class. This happens so often that writing tests creates a barrier to refactoring.Outlaw Programmer, 2008
In other words, brittle tests can hinder refactoring which provides a barrier to code improvement.
Private Method Test Failures May Not Matter
One of the more interesting arguments I’ve seen goes something like the following:
If you can’t break the public method does it really matter what the private methods are doing?Rig, 2012
In other words, we may be able to break our private methods, but the exposed methods might be under different constraints that cause the error in the private method to never manifest.
A Case for Testing Private Methods
In general, I agree with all the arguments against testing private methods. In fact, if I hadn’t run into my own needs for testing private methods, I might have been on that side of the fence. As always, however, the issue is a bit more nuanced.
Public Methods Depend on Implementation Details
When we make the argument that we shouldn’t care about implementation details, we run the risk of missing edge cases where our public methods break down. In other words, knowing how our system is designed under the hood is critical to ensuring that it works correctly. How would we prove it works otherwise?
As a sort of silly example, imagine a Fibonacci sequence method which outputs the term in the sequence based on some index. If we test this method, how do we know how many inputs to try to verify that the method works? With black box testing, we’d have to try them all. With white box testing (which depends on implementation details), we’d just have to hit all the branches.
Of course, I don’t think anyone is making the argument that public methods shouldn’t be white box tested, but that does move me into my second point: public method tests are just as brittle as private method tests, and they’re often bloated.
Public Method Tests Are Brittle and Often Bloated
Since private method tests depend on implementation details, it’s possible that tests will break as requirements change. That said, I’m not sure public methods are in the clear in that regard either.
For instance, sometimes methods can affect the state of an object. We typically call these instance methods because they interact directly with an instance of an object. In order to test an instance method, we usually have to setup the state of that object so we can monitor its behavior when we call that method on it.
Since we’re stuck using public methods to setup our object during testing, we can run into a scenario where tests depend on the behavior of multiple methods—not necessarily the method under test. If we had access to private methods (setters for example), we would be able to set the state of the object without becoming dependent on other public methods that may or may not work.
To make matters worse, white box testing becomes a nightmare. Suddenly, we have to feed all sorts of data into our public API under the hope that we can get proper code coverage. It would be much easier to test the private methods directly and throw those tests away when those private methods are no longer needed.
In terms of readability alone, imagine trying to name 50+ unique tests for a single method. After several rounds of refactoring, you wouldn’t even know which tests would be worth deleting. Private method tests keep the separation of responsibility clear.
Finally, imagine deprecating a public method held together by 50+ tests. Not only do all those tests go to waste, but the sunk cost fallacy basically guarantees that we will refuse to deprecate a public method due to the amount of testing behind it. The momentum of the accumulated test cases alone will stop us from making our code better.
Private Method Test Failures Matter
Ultimately, we come to the final argument: if the public methods work, who cares what the private methods are doing? In other words, as long as the API works, who cares whether or not some internal feature fails some test. At least, I feel like that’s argument being made here, right?
To me, private method test failures should matter because that error may just manifest itself down the line. After all, coding is a dynamic process. In other words, an underlying issue may not manifest itself today, but it just may 3 versions down the line. As a result, actively ignoring a private method that may have a bug is a ticking time bomb.
In addition, I’m also not a fan of the sentiment made by this argument. To be honest, I’d be really worried if the same kind of argument were made in other engineering disciplines. For instance, I would hope that airplane manufacturers would thoroughly test their equipment even if they had triple redundancy to cover for failures.
That said, I do find the original argument to be the most compelling. We can debate the merit of testing private methods all day, but a lot of software just isn’t mission critical. In today’s world, software moves quickly, and public method testing is probably enough. Hell, I’d prefer that over telemetry.
It’s Okay to Test Private Methods
When I set out to write this piece, it was in response to the overwhelming amount of literature online that states that testing private methods is a bad idea. To be honest, I thought that was a little odd. After all, I’ve been in situations where a public method is built on layers of private methods, so testing the public interface becomes a really inefficient way of isolating bugs. In other words, how do we know how to write just the right test to exercise all the branches on some underlying private method?
At any rate, whether or not it’s actually practical to test private methods is a completely different question, but I wouldn’t go as far as to say that private method testing is good or bad. Like many debates in Computer Science, the issue is more nuanced.
Of course, in the process of writing this article, I was also working on an app in Kotlin, and I found it was much more practical to only test the public API. After all, the underlying private methods were all very small and easy to reason about. However, I can’t say the same for every project I’ve written, so I pass the choice off to you: do what makes sense and nothing more.
Right now, it makes sense to become a premium member of The Renegade Coder! With a premium membership, you’ll get full access to the blog, so you can get to know me a little better. If you need more time to figure things out, check out some of the following articles:
While you’re here, why not share how you feel about private method testing? Do you strictly avoid it, or are their situations where you think it makes sense?
Kicking off a new series of reverse engineering content inspired by VirtualFlatCAD. Today, we're trying to roll our own uppercase function.
When it comes to capitalizing strings in Python, you have a few options. Use the tools Python provides or roll your own.