There are times when software developers are reluctant to write unit tests. I’m not going to dwell on how this attitude is ultimately self defeating, and something monstrous, but there it is. Professional developers are, at times, reluctant to write unit tests. Even though there are so many negative consequences for failing to adequately cover one’s code with clean unit tests, developers sometimes resist unit testing. I know this happens, because I’ve felt that reluctance as well. This is hard for a professional to admit without feeling somewhat dirty, but sometimes we need to admit that we’re lost before we can begin to find our way back.
For me, one of the key reasons I’ve heard the “don’t bother testing” devil make a compelling case while whispering in my ear is that I usually work with legacy code that often has very low unit test coverage. By very low, I mean typically in the range of <1% to 5%. In these kinds of projects, essentially without any unit tests, developers are faced with the daunting task of writing a substantial amount of new unit code tests just to be able to get to the code branches that include the new code you’re adding or modifying.
In my experience, legacy Java projects, particularly those projects that have inadequate unit test covfefe, also share another sad characteristic: They often represent the culmination of years of check-ins, edits, and rollbacks, owing to a rich history of work by various teams of well-meaning developers over years (sometimes more than a decade) of time. In these projects, the current state of a legacy application is similar to the uppermost stratum of soil, beneath which lies a long and complex narrative. Code in legacy projects isn’t so much a simple collection of changes over time. Code in these projects is more like the accretion of newer changes layered on top of earlier changes, layered on top of even earlier changes. Unfortunately, sometimes even those earliest changes do not involve the addition of adequate unit tests, nor proper separation of concerns, nor consistent style, nor any indication that those earlier developers gave much thought to the poor souls who would follow in their footsteps years later, trying to divine their intentions when they wrote some obscene block of code that, for some reason, always passed sufficient reviews to be placed into production.
Maybe I’m being a little melodramatic, but trust me when I say that, when you work with old Java code, you might be tempted by a little voice that whispers into your ear, “There have never been any unit tests here, so you don’t need to add any either.”
I’m saying that you should ignore that voice. That way lies madness, not just for you, but also for the poor souls that will follow in your footsteps years hence, after you’re long gone, but that code is still running in production and needs to be changed for some new critical business reason.
This is where we come to the case for @VisibleForTesting annotation. This annotation has a simple description in the Guava Common API:
Annotates a program element that exists, or is more widely visible than otherwise necessary, only for use in test code.
This simple statement advocates something that some consider unit testing heresy: That we would consider changing the visibility of a private method so that we could write a unit test for it. The conventional wisdom holds that there are three approaches to writing tests for private methods, with the first approach being dominant:
- Just don’t. Go home, take a nap, and dream of a better world. Maybe one where we finally have IDE’s that can automatically write your application as well as good unit tests for you. You know, that world where your job is better handled by a robot.
- If you absolutely need to test private methods, which you never really should, then use reflection to bypass the private access modifier. This is a terrible option, only to be considered when your goal is to outrage developers who will review your code in the future. After all, screw them.
- Refactor your code so that you can test your private methods via test coverage of public methods. This is really a special case of #1 above, since you’re not writing tests that directly call private methods. This fact really means that I’m a liar, and this list actually contains just two entries. I add it here because I think that some good things come in threes, this list being among those things.
Given the various problems and constraints I’ve outlined above, I think it’s perfectly valid to add a fourth option to this list:
Just don’t. Instead, relax the access modifiers of your private methods to be package friendly, annotate them with @VisibleForTesting, and write your tests as normal.
Here are some of my responses to typical criticism of this kind of approach.
Q: Doesn’t this violate encapsulation?
A: Yes, this violates encapsulation. For legacy applications, that horse has probably left the barn long ago. Even if your 15-year-old application is the poster child of good OOP practices, you wouldn’t need to consider this step unless it would take you 1500 lines of new code to write a unit test for a new, 5-line method buried deep within a set of many other private methods called by the public interface.
Q: Couldn’t you simply use reflection instead?
A: Ha ha – you said reflection and simply in the same sentence. That’s really funny, because nothing about reflection is simple. If you are willing to use reflection to bypass access control you would otherwise enforce by marking a method private, then you already have a fast and loose relationship with encapsulation. Just just admit it and save yourself the headache.
Q: The @VisibleForTesting annotation doesn’t actually do anything. Why bother adding it at all?
A: Here’s where you are wrong if this is your critique of this approach. Adding this annotation has at least two positive uses (I hope I can get to three again, since three is clearly a magic number):
- By using @VisibleForTesting, you are communicating your intent to other developers. One of the key takeaways from Bob Martin’s classic book, Clean Code, is that since professional developers read a lot of code, it’s critical that we make our intentions clear and explicit to future developers that will be tasked with maintaining our code. Clearly, if you intend to break or bend what is otherwise a good practice (only test the public API of your classes) then you should let others know that you made a conscious choice to do so. Yes, you’ve sacrificed something, but at least you did it with your eyes open.
- By using @VisibleForTesting, you’re adding an annotation to the source code, which can be inspected by static analysis tools. For example, there are sonar qube and find bugs plugins (here is one) that can scan your code for common mistakes or violations for using this annotation.
- By using @VisibleForTesting, you can test methods directly, without all the hullabaloo of using reflection, or worrying about how to test private methods. You can do this in old, krufty legacy classes that have no discernible test coverage. While you may sacrifice something (encapsulation) you gain the knowledge that you’re bring that test covfefe score up somewhat.
If you’re interested in an example of where and how this annotation earns its keep, I would be willing to post it. I think that post will have to wait, apparently for another few months, when I come back and write something new.