23

I know you're not supposed to test private methods, and if it looks like you need to, there might be a class in there waiting to come out.

But, I don't want to have a gazillion classes just so that I can test their public interfaces and I find that for many classes if I just test the public methods I end up having to mock a lot of dependencies and the unit tests are enormous and hard to follow.

I much prefer mocking the private methods when testing the public ones, and mocking external dependencies when testing the private ones.

Am I crazy?

7 Answers7

29

You're partially right - you shouldn't directly test private methods. The private methods on a class should be invoked by one or more of the public methods (perhaps indirectly - a private method called by a public method may invoke other private methods). Therefore, when testing your public methods, you will test your private methods as well. If you have private methods that remain untested, either your test cases are insufficient or the private methods are unused and can be removed.

If you are taking a white-box testing approach, you should consider the implementation details of your private methods when constructing unit tests around your public methods. If you are taking a black-box approach, you shouldn't be testing against any implementation details in either the public or private methods but against the expected behavior.

Personally, I prefer a white-box approach to unit tests. I can craft tests to put the methods and classes under test into different states that cause interesting behavior in my public and private methods and then assert that the results are what I expect.

So - don't mock your private methods. Use them to understand what you need to test in order to provide good coverage of the functionality that you provide. This is especially true at the unit test level.

Thomas Owens
  • 85,641
  • 18
  • 207
  • 307
5

UnitTests test the public observable behavior, not code, where "public" means: return values and communication with dependencies.

A "unit" is any code, that solves the same problem (or more precisely: has the same reason to change). That might be a single method or a bunch of classes.

The main reason why you don't want to test private methods is: they are implementation details and you may want to change them during refactoring (improve your code by applying OO-principles without changing the functionality). This is exactly when you don't want your unittests to change so that they can guarantee the behavior of your CuT did not change during the refactoring.

But, I don't want to have a gazillion classes just so that I can test their public interfaces and I find that for many classes if I just test the public methods I end up having to mock a lot of dependencies and the unit tests are enormous and hard to follow.

I usually experience the opposite: The smaller the classes (the less responsibility they have) the less dependencies they have, and the easier are unittests both, to write and to read.

Ideally you apply the same level of abstraction pattern to your classes. This means your classes either provide some business logic (preferably as "pure functions" working on their parameters only without maintaining an own state) (x)or call methods on other objects, not both at the same time.

This way unittesting the business behavior is a piece of cake and the "delegation" objects usually are too simple to fail (no branching, no state change) so that no unittesting is needed and their testing can be left to integration or module tests

4

I think this is a very poor idea.

The problem with creating unit tests of private members, is that it fits poorly with the product lifecycle.

The reason you've CHOSEN to make those methods private is that they aren't central to what your classes are trying to do - just helpers in how you currently implement that functionality. As you refactor, those private details are likely candidates to change and will cause friction then with refactoring.

Also, a key difference between public and private members, is that you should be thinking out carefully your public API, and documenting it well, and checking it well (assertions etc). But with a private API, it would be pointless to think out as carefully (a wasted effort since its use is so localized). Putting private methods into unit tests amounts to creating external dependencies on those methods. Meaning the API needs to be stable and well documented (since somebody has to figure out why those unit tests failed if/when they do).

I suggest you:

  • RETHINK whether those methods should be private
  • Write onetime tests (just to make sure its correct now, make it public temporarily so you can test, and then delete the test)
  • Built conditional testing into your implementation using #ifdef, and assertions (tests are only done in debug builds).

A appreciate your inclination to test this functionality and that its hard to get it tested through your current public API. But I personally value modularity more than test coverage.

Lewis Pringle
  • 2,975
  • 1
  • 11
  • 15
4

Before answering such a question, you need to decide what you actually want to achieve.

You write code. You hope it fulfils its contract (in other words, it does what it is supposed to do. Writing down what it is supposed to do is a giant step forward for some people).

To be reasonably convinced that the code does what it is supposed to do, you either stare at it long enough, or you write test code that tests enough cases to convince you "if the code passes all these tests then it is correct".

Often you are only interested in the publicly defined interface of some code. If I use your library, I don't care how you made it work correctly, only that it does work correctly. I verify that your library is correct by performing unit tests.

But you are creating the library. Getting it to work correctly can be difficult to achieve. Let's say I only care about the library doing operation X correctly, so I have a unit test for X. You, the developer responsible to create the library, implement X by combining steps A, B and C, which are each totally nontrivial. To get your library working you add tests to verify that A, B and C each work correctly. You want these tests. Saying "you shouldn't have unit tests for private methods" is quite pointless. You want tests for these private methods. Maybe someone tells you that unit testing private methods is wrong. But that only means you might not call them "unit tests" but "private tests" or whatever you like to call them.

The Swift language solves the problem that you don't want to expose A, B, C as public methods just because you want to test it by giving functions an attribute "testable". The compiler allows private testable methods to be called from unit tests, but not from non-test code.

gnasher729
  • 49,096
1

Unit testing has some value when you are the only programmer working on the code, especially if the application is very large or very complex. Where unit testing becomes essential is when you have a larger number of programmers working on the same codebase. The concept of unit testing was introduced to address some of the difficulties of working in these larger teams.

The reason that unit tests help larger teams is all about contracts. If my code is making calls to code written by someone else, I am making assumptions about what the other person’s code is going to do in various situations. Provided those assumptions are still true, my code will still work, but how do I know which assumptions that are valid and how do I know when those assumptions have changed?

This is where unit tests come in. The author of a class creates unit tests to document the expected behaviour of their class. The unit test defines all of the valid ways of using the class, and running the unit test validates these use cases work as expected. Another programmer that wants to make use of your class can read your unit tests to understand the behaviour they can expect for your class, and use this as a basis for their assumptions about how your class works.

In this way the public method signatures of the class and the unit tests together form a contract between the class author and the other programmers that make use of this class in their code.

In this scenario what happens if you include testing of private methods? Clearly it makes no sense.

If you are the only programmer working on your code and you want to use unit testing as a way of debugging your code then I don’t see any harm in that, it’s just a tool, and you can use it any way that works for you, but it was not the reason why unit testing was introduced, and does not provide the major benefits of unit testing.

0

Yes, you are crazy.... LIKE A FOX!

There are a couple of ways to test private methods, some of which are language dependent.

  • reflection! rules are made to be broken!
  • make them protected, inherit and override
  • friend/InternalsVisibleTo classes

On the whole though if you want to test private methods, you probably want to move them to public methods on a dependency and test/inject that.

Ewan
  • 83,178
0

Stay pragmatic. Testing special cases in private methods by way of setting up the state of the instance and the parameters to a public method such that those cases happen there, is often far too complicated.

I add an extra internal accessor (together with the flag InternalsVisibleTo the test assembly), clearly named DoSomethingForTesting(parameters), in order to test those "private" methods.

Of course, the implementation may change somewhen, and those tests including the test accessors become obsolete. That's still better than untested cases or unreadable tests.