Using Maven, JUnit and JaCoCo in a Java project
This article presents a simple way to setup a Java project using Maven, JUnit and JaCoCo. The goal is to have a project that can be built, tested and analyzed for code coverage using these tools.
Project structure
Maven requires a specific project structure. The code should be located in the
src/main/java
and the tests in the src/test/java
directories. The project
should also contain a pom.xml
file at the root of the project. Here is the
hierarchy of our very basic project:
hello.git/
├── pom.xml
├── README.md
└── src/
├── main/
│ └── java/
│ └── hello/
│ └── HelloWorld.java
└── test/
└── java/
└── hello/
└── HelloWorldTest.java
The code
We only have two files in our project. The first one is the HelloWorld.java
which encloses a simple class with a method that returns a string and a static
max()
function:
package hello;
public class HelloWorld
{
public String sayHello() {
return "Hello World!";
}
public static int max(int a, int b) {
return a > b ? a : b;
}
public static void main(String[] args) {
System.out.println(new HelloWorld().sayHello());
}
}
The second file is the HelloWorldTest.java
which contains two test using the
JUnit framework:
package hello;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class HelloWorldTest {
@Test
public void testHelloWorld() {
hello.HelloWorld helloWorld = new hello.HelloWorld();
assertEquals("Hello World!", helloWorld.sayHello());
}
@Test
public void testMax() {
assertEquals(5, HelloWorld.max(3, 5));
assertEquals(5, HelloWorld.max(5, 3));
assertEquals(5, HelloWorld.max(5, 5));
}
The POM file
The pom.xml
file is the Maven configuration file. It contains the project
description, the dependencies, the plugins and the build configuration. Here is
the content of our pom.xml
file:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Comment -->
<groupId>hello</groupId>
<artifactId>hello-world</artifactId>
<name>hello-world</name>
<version>1.0</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>17</maven.compiler.release>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.11.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<!-- Optionally: parameterized tests support -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
<addClasspath>true</addClasspath>
<mainClass>hello.HelloWorld</mainClass>
</manifest>
<manifestEntries>
<mode>development</mode>
<url>${project.url}</url>
<key>value</key>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.21.0</version>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<!-- attached to Maven test phase -->
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jxr-plugin</artifactId>
<version>3.3.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.9.0</version>
</plugin>
</plugins>
</reporting>
</project>
We already integrated the JaCoCo plugin in the pom.xml
file. It will be
executed during the test
phase of the Maven build lifecycle. The JaCoCo plugin
will generate a report in the target/site/jacoco
directory.
Using Maven
Maven is a build automation tool that can be used to compile, test, package and deploy Java projects. To build our project, we can use the following command:
#> mvn compile
...
#> java -cp target/classes hello.HelloWorld
Hello World!
Or, if you want to execute the software from its jar package:
#> mvn package
...
#> java -jar target/hello-world-1.0.jar
Hello World!
Similarly, to run the tests, we can use the following command:
#> mvn test
...
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running hello.HelloWorldTest
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.017 sec - in hello.HelloWorldTest
Results :
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
...
Finally, to generate the JaCoCo report, we can use the following command:
#> mvn jacoco:report
#> firefox target/site/jacoco/index.html
Then, we can look at several views on the code coverage of our project. The package view shows the coverage of the hello
package:
The class view shows the coverage of the HelloWorld
class:
The method view shows the coverage of the sayHello()
and max()
methods:
And, finally, the code view shows the coverage of the lines of code:
If you want to build the jar file, you can use the following commands:
#> mvn package
#> java -jar target/hello-world-1.0.jar
Hello World!
Finally, you can clean the project using the following command:
#> mvn clean
Generating documentation
The Maven Javadoc plugin helps to build the documentation of the project. But, first, you need to embed comments in the code. The Javadoc comments are written in the form of HTML tags. Here is an example on how to document the HelloWorld
class:
package hello;
/**
* The HelloWorld class implements a simple class with a method that returns a
* string and a static max() function.
*/
public class HelloWorld
{
/**
* Returns the string "Hello World!".
*
* @return the string "Hello World!"
*/
public String sayHello() {
return "Hello World!";
}
/**
* Returns the maximum of two integers.
*
* @param a the first integer
* @param b the second integer
* @return the maximum of the two integers
*/
public static int max(int a, int b) {
return a > b ? a : b;
}
/**
* Main function that prints "Hello World!".
*
* @param args the command line arguments
*/
public static void main(String[] args) {
System.out.println(new HelloWorld().sayHello());
}
}
Then, you can regenerate the Javadoc and look at it with your browser.
#> mvn javadoc:javadoc
#> firefox target/site/apidocs/index.html
Checking coding standards
The Maven Checkstyle plugin can be used to check the coding standards of the Java code. To use it, you need to add the following plugin in the pom.xml
file:
<build>
<plugins>
...
<!-- Checkstyle Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<configLocation>google_checks.xml</configLocation>
</configuration>
</plugin>
...
</plugins>
</build>
The Checkstyle plugin comes with three predefined configurations: a default
configuration (no need to specify configLocation
), google_checks.xml
and
sun_checks.xml
. You can also create your own configuration file (see the
Checkstyle documentation). The google_checks.xml
configuration file is a good starting point. Then, you can use the following
command to check your code and get the list of errors:
#> mvn checkstyle:check
...
[INFO] There are 5 errors reported by Checkstyle 9.3 with sun_checks.xml ruleset.
[ERROR] src/main/java/hello/HelloWorld.java:[1] (javadoc) JavadocPackage: Missing package-info.java file.
[ERROR] src/main/java/hello/HelloWorld.java:[8,1] (blocks) LeftCurly: '{' at column 1 should be on the previous line.
[ERROR] src/main/java/hello/HelloWorld.java:[25,25] (misc) FinalParameters: Parameter a should be final.
[ERROR] src/main/java/hello/HelloWorld.java:[25,32] (misc) FinalParameters: Parameter b should be final.
[ERROR] src/main/java/hello/HelloWorld.java:[34,27] (misc) FinalParameters: Parameter args should be final.
...
If you want to generate an HTML report, you can use the following command:
#> mvn checkstyle:checkstyle
#> firefox target/site/checkstyle.html
PMD static code analyzer
PMD is also another very useful tool to find common programming flaws. To use it, you need to add the following plugin in the pom.xml
file:
<build>
<plugins>
...
<!-- PMD Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.26.0</version>
<configuration>
<rulesets>
<!-- If you want to start small, take only this line -->
<!-- <ruleset>/rulesets/java/quickstart.xml</ruleset> -->
<!-- All the rulesets -->
<ruleset>/category/java/bestpractices.xml</ruleset>
<ruleset>/category/java/codestyle.xml</ruleset>
<ruleset>/category/java/design.xml</ruleset>
<ruleset>/category/java/documentation.xml</ruleset>
<ruleset>/category/java/errorprone.xml</ruleset>
<ruleset>/category/java/multithreading.xml</ruleset>
<ruleset>/category/java/performance.xml</ruleset>
<ruleset>/category/java/security.xml</ruleset>
</rulesets>
</configuration>
</plugin>
...
</plugins>
</build>
Then, you can use the following command to check your code:
#> mvn pmd:check
...
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-pmd-plugin:3.26.0:
check (default-cli) on project hello-world: PMD 7.7.0 has found 6 violations.
For more details see: .../maven-project.git/target/pmd.xml -> [Help 1]
...
#> firefox target/reports/pmd.html
Create a Starter Script
You may admit that running your program with ‘java -jar
/path/to/jar/file
‘ is not very convenient for an end-user. That is why
most of the developers add a starter script to run the program in more
usual manner (taking arguments from the command line). Here is a simple
basic script that you can use for that (set it executable and):
#!/bin/sh
JAR=target/hello-world-1.0.jar
if [ ! -f $JAR ]
echo "$0: jar not found!"
exit 1
fi
java -jar $JAR $@
Conclusion
Maven offers a large number of plugins that can be used to automate the build, test and analysis of Java projects. In this article, we have seen how to setup a Java project using Maven, JUnit and JaCoCo. We have also seen how to run the tests, generate the JaCoCo report and build the jar file. Finally, we have seen how to integrate other tools like Checkstyle, PMD into the Maven build lifecycle.