loading...
Cover image for It’s Okay to Test Private Methods

It’s Okay to Test Private Methods

renegadecoder94 profile image Jeremy Grifski Originally published at therenegadecoder.com on ・7 min read

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.

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:

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?

The post It’s Okay to Test Private Methods appeared first on The Renegade Coder.

Posted on by:

renegadecoder94 profile

Jeremy Grifski

@renegadecoder94

Engineering Education PhD student interested in challenging cultural issues in the tech community.

Discussion

markdown guide
 

It's not that you shouldn't test private methods, it's that you should write code in which private methods don't need to be tested.

 

Anything taken to extremes is unproductive. Including criticism of methodologies based on their philosophies being taken to extremes, usually out of context. Which I find happens with TDD a lot.

 

Of course! I totally agree here, but I also think that we speak too often in absolutes and ignore the nuance that day-to-day coding requires. Arguments like these are sometimes easier said than done.

 

As a matter of practice, I test drive the code that I write. I find it extremely helpful to have coverage around the (package) private methods because it allows me to break up the tests into more concise increments. I also find that when I need to expand the functionality that the process goes much quicker when I have tests around all methods that have business logic.

I do not have much concern around deleting previously written unit tests. If the tests are no longer needed then keeping them around provides no value, in fact it just creates extra noise.

 

I'm on board with all of this!

Like you, I have no problem with deleting code. I actually enjoy it in a weird way because I'm fairly minimalist in my daily life. That said, I could see why the accumulation of tests could prevent someone from scrapping old code.

 

Some good arguments have already been made here:
TDD your code. This will make sure your code does not need to test private methods.
In your example, you talked about a main method. That is only the input/output. There should be a class that is free of side effects that. An be tested. Then, there might be a parse and toString method that will be utilized by the main method. Both can also be tested. That why a lot of people value TDD: It leads to a clean and testable code.

 

Absolutely agree! But, I think TDD can sometimes ignore the fact that there is tons of code out there that doesn’t or can’t follow it.

In this particular case, we had a class that couldn’t be instantiated (no public constructor), so the only way to interact with it was through the main method. The only way (I can think of) to put the solution in a testable state would be to write some public wrapper function that accepted everything but the IO streams. Since that function was never going to be used outside of testing, I didn’t bother, but it would have gotten the job done.

 

Hmm, why not refactor the class then? If it’s untested legacy code, it is legitimate to test private methods and use things like PowerMock to mock static methods etc. But that should be the first step. Once the tests capture the behavior, you should refactor the class bit by bit and start writing new unit tests until you can throw away the old I/O based tests. I’ve done this once for a rather large code base. It was both tedious and soothing at the same time. After a lot of work, I had a tested code base, deleted 1/3 of the code and the application was 10x as fast. 😅

I am actually 100% on board with working toward eliminating private method tests and whatnot. That said, I guess I’m just critical of rules that we tend to blindly follow as a community.

 

I see the issue a bit differently. If you have many private methods or private methods that are complex enough to warrant being tested, it's a sign that a new module (or class, function, namespace, etc) is waiting to come to life.

That module would essentially be a private module, not a private method or a set of private methods. A new module is easier to test than a private method because the latter might require setting up unrelated stuff to put it under test, and also, some languages won't let you call private methods.

I usually listen to my unit tests: if the examples for a particular module are in great number, messy, or cumbersome, that's a sign a new module could be extracted. That's a useful code smell to be aware of.

All of that to say you are doing the right thing by testing complex private methods, and that's part of the mindset that makes for a good testing strategy, but I'd consider extracting the private methods into first class modules.

 

Yeah, I saw this argument a lot when I was researching the topic for this piece. But ultimately, I couldn’t find any battle tested rules on the subject—it’s more of a feeling. In other words, when is a good time to create that new module/class?

Regardless, I’m on board. It’s hard to test a class that has too much functionality, and overly complex methods are usually a code smell. I think these types of problems have to be tackled before worrying about private method testing.

 

If you have properly architectured your code you wont need to test private methods.

For example if I have a class which is responsible for creating a blog post and inside that class I am doing some validation via private methods (like title > 10 chars, thumbnail is set) & I want to see why my tests are failing to create a post. At this point I should be separating out the validation methods to their own class. Now I have an easily testable class that does not violate the single responsibility principle.

 

This is another argument I hear a lot, and I don’t think it’s always that simple. Busting out a new class every time you want to test something feels like an anti-pattern. At least from a Java perspective, that could generate a lot of files which forces you to maintain some sort of mental record of what they all do.

That said, I agree with the sentiment. I just don’t think it’s always the best course of action.

 

Test what you need to test and build it as it needs to be built. Having everything in private methods sounds like a Java problem.

 

To some extent, it definitely is a Java problem. For instance, nothing is really stopping you from testing private Python methods. Meanwhile, one of the only ways to test a Java private method is through reflection.

That said, private methods are also a consequence of philosophical ideas like encapsulation and abstraction. In other words, we like to hide behavior to protect our users from making mistakes. As a result, testing becomes a bit more of a roundabout process.