If you’re a developer or come from a development background, you’ve seen a lot of bad code in your career. It’s just part of the learning experience… we all wrote bad code at some point in time and learned from it.
src: xkcd comics
I can’t cure bad code.
That’s something to be fixed through a code review process. What this post does is show you a few maven plugins you can take advantage of to automate things like:
- Known bug detection via SpotBugs and PMD
- Java StyleGuide Enforcement via CheckStyle
- Code coverage report generation via Clover
The Lineup
SpotBugs
SpotBugs is a program which uses static analysis to look for bugs in Java code.
SpotBugs checks for more than 400 bug patterns. Bug descriptions can be found here
We will be using the SpotBugs Maven Plugin.
PMD
PMD is a static source code analyzer. It finds common programming flaws like unused variables, empty catch blocks, unnecessary object creation, and so forth. It’s mainly concerned with Java and Apex, but supports six other languages.
A list of PMD’s java rules can be found here.
We will be using the PMD Maven Plugin.
CheckStyle
Checkstyle is a development tool to help programmers write Java code that adheres to a coding standard. It automates the process of checking Java code to spare humans of this boring (but important) task. This makes it ideal for projects that want to enforce a coding standard.
We will be using the CheckStyle Maven Plugin and using the Google Java Style Guide with it, you can also configure Sun’s Java Style if you like.
Clover
Clover was an Atlassian offering, you know them as the people behind Jira. In 2017, Atlassian open sourced Clover. We will be using Clover to generate code coverage reports for unit tests. We will also add jUnit5 and aem-mock dependencies for reference.
SonarQube and SonarLint
SonarQube is a standalone server that can analyze your project for all types of bugs/code smells. If you do not wish to setup a server, you can use SonarLint which provides multiple IDE plugins that will analyze your code similar to how SonaqQube would. It is not a replacement to a full SonarQube server, but can be used in conjuction or by itself. This is especially important if you are on AMS; since the Adobe AMS Cloud Manager runs SonarQube for code quality.
The Setup
I assume you have a working Maven AEM project and know how to add maven dependencies.
Let’s start by adding the dependencies we need. You can add this to your parent POM.
The versions I am using here are the latest as of this post.
Make sure you are usingmaven-surefire-plugin
v2.22.0 or later as it supports jUnit5.
<build> .... <pluginManagement> <plugins> ... <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.0</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-pmd-plugin</artifactId> <version>3.9.0</version> </plugin> <plugin> <groupId>org.openclover</groupId> <artifactId>clover-maven-plugin</artifactId> <version>4.3.0</version> </plugin> <plugin> <groupId>com.github.spotbugs</groupId> <artifactId>spotbugs-maven-plugin</artifactId> <version>3.1.6</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-site-plugin</artifactId> <version>3.7.1</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-project-info-reports-plugin</artifactId> <version>3.0.0</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-checkstyle-plugin</artifactId> <version>3.0.0</version> <dependencies> <dependency> <groupId>com.puppycrawl.tools</groupId> <artifactId>checkstyle</artifactId> <version>8.12</version> </dependency> </dependencies> </plugin> ... </plugins> <build> .... <pluginManagement>
Then to your <dependencies>
add the following:
<!-- TESTING --> <dependency> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> <version>5.2.0</version> <scope>test</scope> </dependency> <dependency> <groupId>io.wcm</groupId> <artifactId>io.wcm.testing.aem-mock</artifactId> <version>2.2.16</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>1.10.19</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.felix</groupId> <artifactId>org.apache.felix.http.servlet-api</artifactId> <version>1.1.2</version> <scope>provided</scope> </dependency> <!-- // TESTING -->
We are using jUnit5 and aem-mock. I won’t cover those two, as they are both well documented and easy to work with.
Now that our plugins are defined, we are going to add them to our core
or bundle
maven submodule, you know the one. We are going to do so by adding a new Maven profile for called codeQuality
that is enabled by default:
I have chosen a profile so that it can be easily disabled when needed. This is particularly helpful for when in initial active development and not yet concerned with quality.
<!-- ====================================================================== --> <!-- C O D E Q U A L I T Y P R O F I LE --> <!-- ====================================================================== --> <profiles> <profile> <id>codeQuality</id> <!-- Active by default. use `-P-codeQuality` to deactivate it. Note the prefix `-` which disables the `codeQuality` profile Combine it with other profiles using CVS. Example: `-P-codeQuality,autoInstallPackage` --> <activation> <activeByDefault>true</activeByDefault> </activation> <properties> <!-- log in color, if terminal supports ANSI escape sequences see: https://confluence.atlassian.com/clover/using-test-optimization-in-maven-170492714.html --> <ansi.color>true</ansi.color> </properties> <!-- Code Quality Reporting --> <reporting> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-pmd-plugin</artifactId> <configuration> <skipEmptyReport>false</skipEmptyReport> <printFailingErrors>true</printFailingErrors> <!-- Dont use cache here, the lifesycle for `site` goal wont support it--> <analysisCache>false</analysisCache> <targetJdk>1.8</targetJdk> </configuration> <reportSets> <reportSet> <reports> <report>pmd</report> </reports> </reportSet> </reportSets> </plugin> <plugin> <groupId>com.github.spotbugs</groupId> <artifactId>spotbugs-maven-plugin</artifactId> <configuration> <xmlOutput>true</xmlOutput> <trace>true</trace> </configuration> <reportSets> <reportSet> <reports> <report>spotbugs</report> </reports> </reportSet> </reportSets> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-checkstyle-plugin</artifactId> <configuration> <encoding>UTF-8</encoding> <failsOnError>true</failsOnError> <failOnViolation>true</failOnViolation> <violationSeverity>warning</violationSeverity> <includes>src/**\/*.java</includes> <configLocation>google_checks.xml</configLocation> </configuration> <reportSets> <reportSet> <reports> <report>checkstyle</report> </reports> </reportSet> </reportSets> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jxr-plugin</artifactId> <version>2.5</version> </plugin> <plugin> <groupId>org.openclover</groupId> <artifactId>clover-maven-plugin</artifactId> <reportSets> <reportSet> <reports> <report>clover</report> </reports> </reportSet> </reportSets> </plugin> </plugins> </reporting> <!-- Code Quality Execution --> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-checkstyle-plugin</artifactId> <configuration> <encoding>UTF-8</encoding> <failsOnError>true</failsOnError> <failOnViolation>true</failOnViolation> <violationSeverity>warning</violationSeverity> <includes>src/**\/*.java</includes> <configLocation>google_checks.xml</configLocation> </configuration> <executions> <execution> <id>checkstyle</id> <goals> <goal>check</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>com.github.spotbugs</groupId> <artifactId>spotbugs-maven-plugin</artifactId> <configuration> <xmlOutput>true</xmlOutput> <trace>true</trace> </configuration> <executions> <execution> <id>spotbugs</id> <goals> <goal>check</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-pmd-plugin</artifactId> <configuration> <printFailingErrors>true</printFailingErrors> <analysisCache>true</analysisCache> <targetJdk>1.8</targetJdk> </configuration> <executions> <execution> <!-- run inverfy phase to allow analysis cache to work properly --> <goals> <goal>check</goal> </goals> </execution> </executions> </plugin> <!-- Clover configs to run Optimize, Report, Log and Check see: https://confluence.atlassian.com/clover/best-practices-for-maven-171180506.html --> <plugin> <groupId>org.openclover</groupId> <artifactId>clover-maven-plugin</artifactId> <configuration> <targetPercentage>90%</targetPercentage> <debug>true</debug> </configuration> <executions> <execution> <id>clover.inst</id> <phase>validate</phase> <goals> <!-- Instrumentation changes source code, we need to use instrument-test because it forks a custom build lifecycle, runs only to the test phase If we use a goal that does not fork, it will affect other plugins like PMD and findbugs see: http://openclover.org/doc/maven/4.2.0/plugin-info.html --> <goal>instrument-test</goal> </goals> </execution> <execution> <id>clover.check</id> <!-- In the verify phase, we can generate the report, log results and check coverage against configured targetPercentage. see: http://openclover.org/doc/maven/4.2.0/plugin-info.html --> <phase>verify</phase> <goals> <goal>clover</goal> <goal>log</goal> <goal>check</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </profile> </profiles>
For the most part, the options for each plugin are self explanatory. I added comments next to the ones that needed explaining.
And yeah, it is long, but that’s maven’s fault ¯_(ツ)_/¯ I can hear you screaming…
Let’s run it!
Now onto the good part! run it! mvn clean install
will run the build with codeQuality
profile enabled by default, if you need to disable it run mvn clean install -P-codeQuality
If you need to disable code quality and apply another profile, like
autoInstallPackage
you can do so withmvn clean install -P-codeQuality,autoInstallPackage
Read more about deactivating profiles in the “Deactivating a profile” section in the maven docs
By default when code quality is enabled, any bug or style error will cause the build to fail, this is by default so that your CI fails when a violation is committed into source code.
Generating reports
To generate your reports, run mvn site
, this will generate reports for all our plugins, you can view the report by opening: target/site/project-reports.html
Gotchas
The Maven Build Lifecycle
When working with maven plugins, you have to beware of the maven build lifecycle since some plugins may alter the generated source, like in the case of Clover where instrumentation is necessary.
In the case of Clover, we used the
instrument-test
goal, which will form a new build lifecycle; so that the instrumented source does not get deployed to your AEM instance. As a side effect, unit tests will run twice. You can fix this by moving Clover to its own profile and activating that Clover profile separately from your main build.
Balancing those plugins can be tough, but a good understanding of the maven build lifecycle is key.
Reports
Some plugins may not generate any reports if you do not have anything in your src/main/java
(no java code). Also, if a plugin is not in the <reporting>
section, a report will not be generated with mvn site