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?

1
  • A thorough unit test should implicitly cover all private members of a given class, because while you can't call them directly, their behaviour will still have an effect on the output. If they don't then why are they there in the first place? Remember in unit testing what you care about is the result, not how the result was arrived at.
    – GordonM
    Commented Jul 30, 2018 at 12:05

7 Answers 7

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.

6
  • 1
    "don't mock your private methods" yeah, i can understand testing, when you are mocking them you may have crossed over the fine fine line to crazy
    – Ewan
    Commented Jul 25, 2018 at 15:31
  • Yeah but like I said, my problem with white box testing is that usually mocking all dependencies for public and private methods makes for really big test methods. Any ideas how to address that? Commented Jul 25, 2018 at 15:52
  • 9
    @FranSevillano If you have to stub or mock that much, I'd look at your overall design. Something feels off.
    – Thomas Owens
    Commented Jul 25, 2018 at 15:55
  • A code coverage tool helps with this.
    – Andy
    Commented Jul 25, 2018 at 21:22
  • 5
    @FranSevillano: There's not many good reasons for a class that does one thing to have tons of dependencies. If you have tons of dependencies, you probably have a God class. Commented Jul 26, 2018 at 0:29
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.

7
  • 8
    I disagree. IMO, this is 100% correct for integration tests. But with unit tests things are different; The goal of unit testing is to pinpoint where a bug is, narrow enough so you can fix it quickly. I find myself often in this situation: i have very few public methods because thats really the core value of my classes (as you said one should do). However, i also avoid writing 400 line methods, neither public nor private. So my few public methods can only achieve their goal with the help of tens of private methods. Thats too much code to "quickly fix it". I have to start the debugger etc..
    – marstato
    Commented Jul 25, 2018 at 14:42
  • 6
    @marstato: suggestion: start writing tests first and change your mind about unittests: they don't find bugs, but verify that the code works as the developer intended. Commented Jul 25, 2018 at 20:01
  • @marstato Thank you! Yes. The tests always pass the first time, when you check stuff in (or you wouldn't have checked it in!). They are useful, as you evolve the code, giving you a heads-up when you break something, and if you have GOOD regression tests, then they give you COMFORT / GUIDANCE about where to look for problems (not in the stuff that is stable, and well regression tested). Commented Jul 25, 2018 at 20:24
  • @marstato "The goal of unit testing is to pinpoint where a bug is" - That's exactly the misunderstanding that leads to the OP's question. The goal of unit testing is to verify the intended (and preferably documented) behavior of an API. Commented Jul 25, 2018 at 20:42
  • 4
    @marstato The name "integration test" comes from testing that multiple components work together (i.e. they integrate properly). Unit testing is testing a single component does what it is supposed to do in isolation, which basically means that its public API works as documented/required. Neither of those terms says anything about whether you include tests of internal implementation logic as part of ensuring that the integration works, or that the single unit works.
    – Ben
    Commented Jul 26, 2018 at 2:30
4

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
  • "UnitTests test the public observable behavior, not code," But you can think of private methods as being "public' to the public methods within the same class/module. When you test these privates, you're testing their observable behaviour from the point of view of their public counterparts. Commented Sep 28, 2023 at 10:26
  • @MehdiCharife: This is a very strange definition of "public". The Point here is that private methods may change (changed name/parameters/return type) ore be removed completely (inlined) without changing the codes behavior. In that case the Tests should not need to be changed. Commented Sep 28, 2023 at 13:29
  • But public methods can also change or get removed without changing the behaviour of other methods that call them (either in the same class or outside of it) yet we still test them. A public method might be just an implementation of a virtual one. Commented Sep 28, 2023 at 14:14
  • @MehdiCharife No. public methods are the official contract of the unit (except they are made public by accident by a careless programmer). When they change (accept renaming) then because new behavior needs this change. Implementations of virtual methods of a parent class should be protected (or whatever the languages equivalent is), not public. Commented Sep 28, 2023 at 17:02
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.

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.

2
  • I struggle with this in that I mock out the dependencies such that if any dependency changes it's behavior, my test won't fail. Is this failure supposed to occur in an integration test not the unit test?
    – Todd
    Commented Jul 30, 2018 at 14:38
  • The mock is supposed to behave identically to the real implementation, but have no dependencies. If the real implementation changes so that they are now different this would show up as an integration test failure. Personally I consider the development of mocks and real implementation to be done to be one task. I do not build mocks as part of writing unit tests. In this way when I change my classes behaviour and change the mock to match, running the unit tests will identify all the other classes that were broken by this change.
    – bikeman868
    Commented Jul 30, 2018 at 17:54
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.

3
  • I don't know, in my eyes you explained that it's not a good idea but continued to answer the question
    – Liath
    Commented Jul 25, 2018 at 15:06
  • I thought I had all the bases covered :(
    – Ewan
    Commented Jul 25, 2018 at 15:07
  • 3
    I upvoted, because the idea of exposing your private helper methods to the public API just to test them is crazy, and I've always thought that. Commented Jul 25, 2018 at 15:08
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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.