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.