Motley: To get high code coverage, we should be testing both public and private methods
Maven: Only test public methods. Testing private methods gets in the way of refactoring
[Context: Maven checks in on Motley's unit testing practices and notices something odd]
Maven: Hey Mot - how is the unit testing thing going?
Motley: Pretty good, I must say. I'm leveraging Test-Driven Development (TDD) as much as possible and it has really improved my code coverage. Check out all these tests!
Maven: Wow - that is pretty good! In fact, that's more tests than I was expecting. Are you literally testing everything?
Motley: You betcha. I have test cases around every method I write.
Maven: Even the private methods?
Motley: Duh. Of course! I gotta get that 100% code coverage, man! That was your idea don't forget.
Maven: Okay, I am willing to bet you are doing TDD without the refactoring part of the process. Am I right?
Motley: Well, um, I guess. I'm trying to do a bit but it's such a pain in the butt. But wait, how can you tell just by looking at my tests???
Maven: Well, if you are testing private methods that typically leads to a slightly larger number of tests, because a good practice is one test (or more) per method that you are testing. If you just test public methods, you can typically get the same code coverage with a slightly lower number of tests.
Motley: So what? So I have a few more tests. It doesn't harm anything.
Maven: Ah, but it does. Testing private methods makes it really hard to refactor. You see, refactoring does not usually affect a public interface but instead the implementation behind that interface, i.e. the private methods. With TDD, you need to be free to refactor, or you defeat one of its primary purposes - to drive a better design. If you need to update tests every time you make a refactoring change to your implementation because you are testing private methods directly, it really gets in the way and you are not going to do it. I am willing to bet that's why you are skipping an extremely important part of the TDD process.
Motley: The private methods do get in the way of refactoring, but code coverage is everything!
Maven: With TDD you don't write a line of code without having a test in place first. When you do refactoring, you rerun your tests to ensure you haven't broken anything. You should theoretically end up with the same coverage even just testing via public methods. If you can't hit your private methods through your public methods, why are they even there? Besides, the code coverage number in itself is not super interesting. What is more interesting is the analysis of why coverage numbers are low. This can lead you to more tests that you may otherwise miss. Don't go overboard trying to get to 100% - it's not worth it. Some C# language constructs, like foreach, have extra compiler-generated code in the intermediate language, which makes it real tough to get 100%. It's not worth the effort. Oh, and by the way, when I say "public", I typically also mean protected methods (public for subclasses) and even internal methods (public within the assembly).
Motley: So you're saying I should just test public methods and code coverage will just come if I use TDD. You're also saying that getting to that magical 100% code coverage number is often not worth the extra effort, and although code coverage is a good measure of your tests, the analysis is more important.
Maven: You should write a book, Mot. I could not have put it better myself.
Motley: I'm a quick learner, as you can see from my stellar deliverables. I can even teach you a few things, like how to be likeable in a social atmosphere.
Maven: What are you saying?!?
Maven's Pointer: You may ask, "How do I test private methods anyway? They are private!". In .NET, there are several ways to test private methods should you have a rare case where you definitely need to test a private:
- Leverage a test framework that uses reflection to call methods. The System.Reflection APIs can access private members of a class
- Use Visual Studio 2005 (and later) for unit testing. It will generate a test context object when you use the wizard to create a test that lets you access privates.
- Create a friend assembly using the InternalsVisibleTo attribute that lets you access internal methods at least.
- Create/Run a tool that flips some bits in the .NET assembly metadata to change the scope of classes and methods all to public (you are not testing the original compiled assembly in this case though).
- Create/Run a tool that generates a proxy for your class and accesses members via reflection. Your tests then use the proxy instead of the original class.
- Use preprocessor definitions to change the scope of the private methods to public in a special build.
Maven's Resources: How to Test Private and Protected Methods in .NET provides similar arguments for/against testing private methods, and talks about a subset of the above list for testing private methods.