Unit tests and private functions: A real life dilemma

Always Test!

There is no argument whatsoever that unit tests are essential to every software nowadays. Beyond their initial use they serve as regression tests during code changes. They are also used in build environments and CI/CD to ensure merges do not negatively affect your code. And sometimes they are considered as an alternative to QA on server side code. TL;DR "It depends"

This is where I currently live, server side code, and unit tests are an essential part of every feature in every sprint.  I will not go into the textbook "what are unit tests" Q and A. I assume dear reader that you know it by heart. However in case you don't know this Wikipedia entry should get you started.

Opinions on unit tests in OO languages

In general unit tests are used to verify the correct behavior of a module, hence in C# only the public methods of a class should be tested.

Some consider testing protected or private methods unacceptable. And then there is this quote from a StackOverflow post "There is nothing called as standard or best practice, probably they are just popular opinions”.

However some times the public methods just represent wider flows. In my case, at my current project, they look more like orchestrators of mini flows. So how would you test the real workhorses, the smaller methods (protected or private) of your code? Well there are a few options. And it really depends on how your code is structured.

My functions are just that. Mine, private*

To make things easy consider an abstract class:


As you can see the class above has one public method orchestrating some operations into an externally visible behavior. So you can test this public method and indeed observe that the external behavior is fine. However how could you test the methods comprising that external visible behavior? In this particular case, assuming the protected methods are virtual, things should look somewhat like the class below:


This way you can instantiate your test class, call some initializing method and take perfectly good advantage of all the class members initialization as well. Perfect isn't it? Well... consider the following class:


This may very well be a class you wrote or inherited at a client's project. This may also be their coding style or convention. And now you have to test it. But wait! What do we have here? Two private static methods and one plain private. The static ones hint at self contained methods. It may be that except for some "smart" defaults in the class members, these methods are totally self contained and thus totally testable.
And then there is one private non static one. In case it is not self contained, I would examine if some refactoring could make it testable and self contained and make it a static one.

So how do we test these private methods? Consider the code below:


By using PrivateType we now have access to our private functions and may test them accordingly.
We "instantiate" our class via PrivateType, get a pointer to our method and invoke it by passing it an Object array of parameters to work with.

Insights

So should we make all our protected methods private and rewrite our tests? Or on new classes make all methods private and self contained? Remember the two words we started with? It depends.
It always depends on what you do or what the requirements of your code are. My opinion is that if your class will not be inherited (for now) or will be sealed then you may very well need to use PrivateType . You can also combine the lot in the same test class. If your code has proper internal separation of concerns and your methods abide to SRP then deciding how to test your classes should be a piece of cake. If not you may find yourself refactoring to, seemingly, accommodate tests. When in fact the tests show you how your code should be internally structured to begin with.
In my case the dilemma was to expose the private methods as protected (but not public, not just for the tests). I decided against it since the classes have nothing virtual about them. They will most probably not be inherited in the foreseeable future. 

*Please note that the methods in the code listings are void for simplicity's sake. It is up to you reader to deduce the implications and actual implementations.

Comments

Popular posts from this blog

NUnit Console Runner and NLog extension logging for your tests

Tests code coverage in Visual Studio Code with C# and .Net Core

Applying an internal forums system in the company culture - thoughts