I do not like testing private functionality for a couple of reasons. They are as follows (these are the main points for the TLDR people):
- Typically when you're tempted to test a class's private method,
it's a design smell.
- You can test them through the public
interface (which is how you want to test them, because that's how
the client will call/use them). You can get a false sense of security by
seeing the green light on all the passing tests for your private
methods. It is much better/safer to test edge cases on your private functions through your public interface.
- You risk severe test duplication (tests that look/feel
very similar) by testing private methods. This has major
consequences when requirements change, as many more tests than
necessary will break. It can also put you in a position where it is
hard to refactor because of your test suite...which is the ultimate
irony, because the test suite is there to help you safely redesign
and refactor!
I'll explain each of these with a concrete example. It turns out that 2) and 3) are somewhat intricately connected, so their example is similar, although I consider them separate reasons why you shouldn't test private methods.
There are times testing private methods is appropriate, it's just important to be aware of the downsides listed above. I'm going to go over it in more detail later.
I also go over why TDD is not a valid excuse for testing private methods at the very end.
Refactoring your way out of a bad design
One of the most common (anti)paterns that I see is what Michael Feathers calls an "Iceberg" class (if you don't know who Michael Feathers is, go buy/read his book "Working Effectively with Legacy Code". He is a person worth knowing about if you are a professional software engineer/developer). There are other (anti)patterns that cause this issue to crop up, but this is by far the most common one I've stumbled across. "Iceberg" classes have one public method, and the rest are private (which is why it's tempting to test the private methods). It's called an "Iceberg" class because there is usually a lone public method poking up, but the rest of the functionality is hidden underwater in the form of private methods. It might look something like this:
For example, you might want to test GetNextToken()
by calling it on a string successively and seeing that it returns the expected result. A function like this does warrant a test: that behavior isn't trivial, especially if your tokenizing rules are complex. Let's pretend it's not all that complex, and we just want to rope in tokens delimited by space. So you write a test, maybe it looks something like this (some language agnostic psuedo-code, hopefully the idea is clear):
TEST_THAT(RuleEvaluator, canParseSpaceDelimtedTokens)
{
input_string = "1 2 test bar"
re = RuleEvaluator(input_string);
ASSERT re.GetNextToken() IS "1";
ASSERT re.GetNextToken() IS "2";
ASSERT re.GetNextToken() IS "test";
ASSERT re.GetNextToken() IS "bar";
ASSERT re.HasMoreTokens() IS FALSE;
}
Well, that actually looks pretty nice. We'd want to make sure we maintain this behavior as we make changes. But GetNextToken()
is a private function! So we can't test it like this, because it wont even compile (assuming we are using some language that actually enforces public/private, unlike some scripting languages like Python). But what about changing the RuleEvaluator
class to follow the Single Responsibility Principle (Single Responsibility Principle)? For instance, we seem to have a parser, tokenizer, and evaluator jammed into one class. Wouldn't it be better to just separate those responsibilities? On top of that, if you create a Tokenizer
class, then it's public methods would be HasMoreTokens()
and GetNextTokens()
. The RuleEvaluator
class could have a Tokenizer
object as a member. Now, we can keep the same test as above, except we are testing the Tokenizer
class instead of the RuleEvaluator
class.
Here's what it might look like in UML:
Note that this new design increases modularity, so you could potentially re-use these classes in other parts of your system (before you couldn't, private methods aren't reusable by definition). This is main advantage of breaking the RuleEvaluator down, along with increased understandability/locality.
The test would look extremely similar, except it would actually compile this time since the GetNextToken()
method is now public on the Tokenizer
class:
TEST_THAT(Tokenizer, canParseSpaceDelimtedTokens)
{
input_string = "1 2 test bar"
tokenizer = Tokenizer(input_string);
ASSERT tokenizer.GetNextToken() IS "1";
ASSERT tokenizer.GetNextToken() IS "2";
ASSERT tokenizer.GetNextToken() IS "test";
ASSERT tokenizer.GetNextToken() IS "bar";
ASSERT tokenizer.HasMoreTokens() IS FALSE;
}
Testing private components through a public interface and avoiding test duplication
Even if you don't think you can break your problem down into fewer modular components (which you can 95% of the time if you just try to do it), you can simply test the private functions through a public interface. Many times private members aren't worth testing because they will be tested through the public interface. A lot of times what I see is tests that look very similar, but test two different functions/methods. What ends up happening is that when requirements change (and they always do), you now have 2 broken tests instead of 1. And if you really tested all your private methods, you might have more like 10 broken tests instead of 1. In short, testing private functions (by using FRIEND_TEST
or making them public or using reflection) that could otherwise be tested through a public interface can cause test duplication. You really don't want this, because nothing hurts more than your test suite slowing you down. It's supposed to decrease development time and decrease maintenance costs! If you test private methods that are otherwise tested through a public interface, the test suite may very well do the opposite, and actively increase maintenance costs and increase development time. When you make a private function public, or if you use something like FRIEND_TEST
and/or reflection, you'll usually end up regretting it in the long run.
Consider the following possible implementation of the Tokenizer
class:
Let's say that SplitUpByDelimiter()
is responsible for returning an array such that each element in the array is a token. Furthermore, let's just say that GetNextToken()
is simply an iterator over this vector. So your public test might look this:
TEST_THAT(Tokenizer, canParseSpaceDelimtedTokens)
{
input_string = "1 2 test bar"
tokenizer = Tokenizer(input_string);
ASSERT tokenizer.GetNextToken() IS "1";
ASSERT tokenizer.GetNextToken() IS "2";
ASSERT tokenizer.GetNextToken() IS "test";
ASSERT tokenizer.GetNextToken() IS "bar";
ASSERT tokenizer.HasMoreTokens() IS false;
}
Let's pretend that we have what Michael Feather's calls a groping tool. This is a tool that lets you touch other people's private parts. An example is FRIEND_TEST
from googletest, or reflection if the language supports it.
TEST_THAT(TokenizerTest, canGenerateSpaceDelimtedTokens)
{
input_string = "1 2 test bar"
tokenizer = Tokenizer(input_string);
result_array = tokenizer.SplitUpByDelimiter(" ");
ASSERT result.size() IS 4;
ASSERT result[0] IS "1";
ASSERT result[1] IS "2";
ASSERT result[2] IS "test";
ASSERT result[3] IS "bar";
}
Well, now let's say the requirements change, and the tokenizing becomes much more complex. You decide that a simple string delimiter won't suffice, and you need a Delimiter
class to handle the job. Naturally, you're going to expect one test to break, but that pain increases when you test private functions.
When can testing private methods be appropriate?
There is no "one size fits all" in software. Sometimes it's okay (and actually ideal) to "break the rules". I strongly advocate not testing private functionality when you can. There are two main situations when I think it's okay:
I've worked extensively with legacy systems (which is why I'm such a big Michael Feathers fan), and I can safely say that sometimes it is simply safest to just test the private functionality. It can be especially helpful for getting "characterization tests" into the baseline.
You're in a rush, and have to do the fastest thing possible for here and now. In the long run, you don't want to test private methods. But I will say that it usually takes some time to refactor to address design issues. And sometimes you have to ship in a week. That's okay: do the quick and dirty and test the private methods using a groping tool if that's what you think is the fastest and most reliable way to get the job done. But understand that what you did was suboptimal in the long run, and please consider coming back to it (or, if it was forgotten about but you see it later, fix it).
There are probably other situations where it's okay. If you think it's okay, and you have a good justification, then do it. No one is stopping you. Just be aware of the potential costs.
The TDD Excuse
As an aside, I really don't like people using TDD as an excuse for testing private methods. I practice TDD, and I do not think TDD forces you to do this. You can write your test (for your public interface) first, and then write code to satisfy that interface. Sometimes I write a test for a public interface, and I'll satisfy it by writing one or two smaller private methods as well (but I don't test the private methods directly, but I know they work or my public test would be failing). If I need to test edge cases of that private method, I'll write a whole bunch of tests that will hit them through my public interface. If you can't figure out how to hit the edge cases, this is a strong sign you need to refactor into small components each with their own public methods. It's a sign your private functions are doing too much, and outside the scope of the class.
Also, sometimes I find I write a test that is too big of a bite to chew at the moment, and so I think "eh I'll come back to that test later when I have more of an API to work with" (I'll comment it out and keep it in the back of my mind). This is where a lot of devs I've met will then start writing tests for their private functionality, using TDD as the scapegoat. They say "oh, well I need some other test, but in order to write that test, I'll need these private methods. Therefore, since I can't write any production code without writing a test, I need to write a test for a private method." But what they really need to be doing is refactoring into smaller and reusable components instead of adding/testing a bunch of private methods to their current class.
Note:
I answered a similar question about testing private methods using GoogleTest a little while ago. I've mostly modified that answer to be more language agnostic here.
P.S. Here's the relevant lecture about iceberg classes and groping tools by Michael Feathers: https://www.youtube.com/watch?v=4cVZvoFGJTU