Update 15. January 2018: Jacoco 0.8.0 has been released. No need to build it from the SNAPSHOT version anymore.
Introduction
Test Coverage is a code metric that indicates how many lines of code, as a percent of the total, your tests execute. It can’t tell you anything about the quality of your tests, but it nevertheless is one of the most important metrics in use. Jacoco is one of the most prominent test coverage tools for Java.
Lombok is a Java library that generates common boilerplate code like getter/setter methods, hashCode, and builder classes during the compilation phase. This improves development speed significantly.
The Problem
Lombok causes problems when your project requires a minimum test coverage rate that is also checked by a CI System such as Jenkins or Travis. Jacoco can’t distinguish between Lombok’s generated code and the normal source code. As a result, the reported coverage rate drops unrealistically low. You the developer are left with two options:
- Write unit tests for generated code or
- Decrease the required coverage rate.
Neither option makes sense and neither is desirable.
The Solution
Luckily, beginning with version 0.8.0, Jacoco can detect, identify, and ignore Lombok-generated code. The only thing you as the developer have to do is to create a file named lombok.config
in your directory’s root and set the following flag:
lombok.addLombokGeneratedAnnotation = true
This adds the annotation lombok.@Generated
to the relevant methods, classes and fields. Jacoco is aware of this annotation and will ignore that annotated code.
Please keep in mind that you require at least version 0.8.0 of Jacoco and v1.16.14 of Lombok.
Showcase
Let’s suppose we have a Person
class that contains fields for first- and lastname. We are using @Data
which will generate the getter/setters, hashCode, toString and equals methods. We also use @Builder
which generates - as the name says - a builder pattern for instantiating an object.
import lombok.Builder; import lombok.Data; @Data @Builder public class Person { private String firstname; private String lastname; }
Then we have a PersonPrinter
class that contains logic for printing a Person. We annotate it with @Log
to instantiate a static logger and again @Data
:
import lombok.Data; import lombok.extern.java.Log; @Log @Data public class PersonPrinter { private Person person; private String separator = " "; private String noLastnameLog = "That person has no name"; public PersonPrinter(Person person) { this.person = person; } public String toString() { if ("".equals(person.getLastname())) { log.info(noLastnameLog); return ""; } return String.format(person.getFirstname() + this.separator + person.getLastname()); } }
The only logic which should be tested is PersonPrinter. The following two test cases should actually give us 100% test coverage:
public class PersonPrinterTest { @Test public void testDefault() { Person harrison = Person.builder() .firstname("John").lastname("Harrison").build(); assertEquals("John Harrison", new PersonPrinter(harrison).toString()); } @Test public void testNoLastname() { Person anonymous = Person.builder() .firstname("anonymous").lastname("").build(); assertEquals("", new PersonPrinter(anonymous).toString()); } }
Unfortunately, this is not the case since Jacoco also counts the generated code from Lombok:
By adding the flag lombok.addLombokGeneratedAnnotation = true
before cleaning and running the tests again, we see that Jacoco has completely ignored the class Person and shows us 100% test coverage:
As always the source code is available on GitHub. It contains support for both Maven and Gradle.
Any equivalent Gradle plugin info?
I have just updated the project on GitHub. It supports now both maven and gradle.
Thanks Rainer!
What kind of nuances go into making this work for a multi-module project [gradle]? I’ve been running into issues related to “Classes in bundle “” do not match with execution data” which I believe might be related to using a different Java version but I’m not sure.
Please let me know if you intend to update your project to handle multi module project builds (not all subprojects may have tests).
Again, great page! I’m definitely pointing people here for this issue. Thanks a lot!
Hi Yash,
if I understand you correctly, you have a “simple” multi-module gradle project, where you get a “classes in bundle – do not match with execution data” error when running Jacoco?
Have you tried to use the same Java version? If not, please try that and if it is still not working I will look into it.
Hi,
Do you know how to update jacoco version in Sonar cube?
Unfortunately that’s not possible yet.
Jacoco’s code removal is done in the report generation part, which comes after creating the raw output in form of an executable (jacoco.exec). Sonarqube and similar take that raw output and create their own reports. So we will have to wait until Jacoco 0.7.10 is released and Sonarqube makes the required adaptions.
You can find more on: https://github.com/jacoco/jacoco/pull/513#issuecomment-293176354
Hi, thanks for your answer. I upvoted the feature.
I have to see if Sonar have planned something.
Do you have a solution for immutables too?
https://immutables.github.io/immutable.html#copy-methods
Hi, this feature is very Lombok specific. I am currently investigating options for a more general approach. Will inform you, when I have something.
The Immutables library adds the ‘javax.annotation.Generated’ annotation to the generated code if that is helpful. It appears the Lombok specific solution was being achieved via an annotation so hopefully something similar can be done here. I am also interested in some sort of generalized support for this. Thanks!
lombok.@Generated , can you please share a use case how to use this in actual code base
Hi, I’ve shared an example project on https://github.com/rainerhahnekamp/jacocolombok. Is this enough or do you need more?
Thanks a lot man, you’ve saved me a lot of time 🙂