Should I test private methods or only public ones?

2018-12-31 06:13发布

I have read this post about how to test private methods. I usually do not test them, because I always thought it's faster to test only public methods that will be called from outside the object. Do you test private methods? Should I always test them?

27条回答
姐姐魅力值爆表
2楼-- · 2018-12-31 06:58

Absolutely YES. That is the point of Unit testing, you test Units. Private method is a Unit. Without testing private methods TDD (Test Driven Development) would be impossible,

查看更多
刘海飞了
3楼-- · 2018-12-31 06:59

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):

  1. Typically when you're tempted to test a class's private method, it's a design smell.
  2. 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.
  3. 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 is one time I consider testing private methods to be appropriate, but 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:

Rule Evaluator

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:

Rule Evaluator Refactored

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:

enter image description here

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:

  1. 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.

  2. 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 you're 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

查看更多
泪湿衣
4楼-- · 2018-12-31 06:59

I am not an expert in this field, but unit testing should test behaviour, not implementation. Private methods are strictly part of the implementation, so IMHO should not be tested.

查看更多
人间绝色
5楼-- · 2018-12-31 07:00

I never understand the concept of Unit Test but now I know what it's the objective.

A Unit Test is not a complete test. So, it's not a replacement for QA and manual test. The concept of TDD in this aspect is wrong since you can't test everything, including private methods but also, methods that use resources (especially resources that we don't have control). TDD is basing all its quality is something that it could not be achieved.

A Unit test is more a pivot test You mark some arbitrary pivot and the result of pivot should stay the same.

查看更多
心情的温度
6楼-- · 2018-12-31 07:01

As quoted above, "If you don't test your private methods, how do you know they won't break?"

This is a major issue. One of the big points of unit tests is to know where, when, and how something broke ASAP. Thus decreasing a significant amount of development & QA effort. If all that is tested is the public, then you don't have honest coverage and delineation of the internals of the class.

I've found one of the best ways to do this is simply add the test reference to the project and put the tests in a class parallel to the private methods. Put in the appropriate build logic so that the tests don't build into the final project.

Then you have all the benefits of having these methods tested and you can find problems in seconds versus minutes or hours.

So in summary, yes, unit test your private methods.

查看更多
浅入江南
7楼-- · 2018-12-31 07:02

It's obviously language dependent. In the past with c++, I've declared the testing class to be a friend class. Unfortunately, this does require your production code to know about the testing class.

查看更多
登录 后发表回答