JUnit: Code Coverage
Let’s talk about unit testing, mocks, stubs, etc, all the good stuff, recently truth be told, I’ve begun to love writing tests. One of the more important points behind writing unit tests is to not only ensure that the application has as little bugs as possible, but I find that tests are a pretty good source of documentation. In my experience, if I want to really understand some process within an application, typically I find it effective to either write tests for the given class(es) or to refactor it, then write tests for the refactored code.
But of course, that’s just me, that’s not by any means to say that this is the best method to learn how a complex process works, obviously. But my personal experience of what helps me learn complex business logic is a bit off topic. Back to unit testing!
Working on projects that were written by developers that have left the team many moons ago, it’s quite comical to see the occurrence of false positives. An example being how many times I’ve seen POJO’s being included in the jacoco report(s), I mean providing that it’s a true POJO where there’s 0 business logic, at most some set of properties & maybe some getters & setters. I mean you really don’t wanna be wanting your time writing tests for these kinda classes, to be blunt, that’s just silly. The only other time(s) I would say it’s okay to exclude from unit tests & associated reports would be classes that should really be covered at a point in the testing process, at least that’s my opinion anyway. An example being classes that require a connection to a live database or some class that requires on some other service in order for it to work, etc.
In my experience, I’ve found that the higher the code coverage, the better from all perspectives, bearing in mind what I’ve mentioned above about POJO’s & classes that interact with external services. Provided you can ensure that the application works, getting as close to 100% as possible shouldn’t be an afterthought. I’ve found that with clean, simple & good application architecture, this alone can ensure that writing tests for your code will be extremely easy, whether you’re writing some factory or strategy class that interacts with 20+ classes, etc, it’s all doable. An example being how I spent several days working on a new microservice, building it from the ground up, I’ve taken the lead with this project & due to how simple the application architecture is, I was able to achieve 100% code coverage in just 2.5 hours of writing the unit tests.
Other than POJO’s such as entity classes or DTO’s, I only excluded one class from jacoco, that being the class that interacts with another microservice to verify that a given JWT is valid & whatnot. I decided to exclude this for several reasons, one being how other devs in my team may not have that application up & running on their machine(s) when they run these tests, not to mention how I still believe that this goes beyond the scope of just unit testing. I was able to cover ~75% of an enter package just by writing a test for a single class, I was able to achieve this kind of witchcraft due to the fact that I’ve created a nice factory pattern that converts entity classes to the relevant DTO, some very specific formatting going here & there, etc, hence the need for these classes. I decided to make a number of these classes package private, the only public class being the factory & keep in mind it’s not like I’ve implemented a tonne of logic into this factory class, it’s ~110 lines of code & that’s including the javadoc comments.
I think the above example alone illustrates how using simple, clean & clever application architecture makes a huge difference, I didn’t have to write say over 10 other test classes just to cater for ~75% of this package.
Conclusion
In addition to helping you ensure that you’re implementing a clean & simple application, unit testing can also act as a good source of documentation for other developers. Not to mention how the higher the test coverage, the more faith other departments like the security team will have in your application. It goes without saying that the PM/PO will also be happy to see the test coverage is as high as possible, provided you’ve covered all branches & instructions, then realistically your application should be pretty solid.
I’m also a firm believe that you should throw exceptions when data presented to the application is in some state that the application just can’t handle. Which means that I think that you should also write tests to cater for these exceptions being thrown, whether you’re just returning a HTTP 500 response code to the user or if you handle a specific exception in a specific manner, it’s somewhat irrelevant, as long as you cover these states with your tests, it should be all good.
Awesome post! Keep up the great work! 🙂