Understanding Apache Maven – Part 4 – Maven Lifecycle

java, maven

Published: 2020-05 (May 2020)
Verified with: Apache Maven 3.6.3

Link to an index, to find other blogs in this series.

In Part 4 of the series , a walkthrough of maven lifecycles and executions is covered.

Apache Maven executions are tied to lifecycles. A lifecycle groups a sequence of activities. Maven provides three basic lifecycles for its standard build management. More lifecycles can be created as needed, though that is a rare need. There are three standard lifecycles provided by maven.

Standard lifecycles

  • clean – Intended for clean-up of any prior build-managed outputs and artifacts.
  • default (build) – Intended for project build, test and deployment of artifacts.
  • site – Intended for project site documentation.

What’s in a lifecycle?

A lifecycle is a collection of related activities pertaining to a specific type of build-management. Standard lifecycles include generation of build output artifacts, creation of a documentation site as well as cleaning any artifacts produced from a prior execution.

Lifecycles in Maven comprise of phases. Each standard lifecycle is made up of a few phases. Maven commands are typically executions of phases. More on this in a little bit.

Phases are sequentially executed.

Invoking a phase implies all prior phases in that lifecycle are executed.

A tree structure of the standard lifecycles and phases in each.
Maven standard lifecycles and their respective phases

Exploring phases

Phases are executable blocks. Phases follow an ordered sequence within a given lifecycle. Reiterating what was already mentioned, invoking a phase implies invoking all phases before it in the lifecycle.

Goals are bound to phases.

Goals – units of work

Goals are units of work (tasks). Goals are attached to a phase and this is called a binding. A goal performs a task that is considered relevant for the given lifecycle and phase. Maven provides some built in goals. Goals are defined in plugins.

Binding Goals to Phases

Phases can be bound to goals via two separate mechanisms. Both have distinct purposes.

  • plugins – Maven plain Old Java Object (MOJO) objects that define goals that can be connected to phases.
  • packaging – Bindings based on Maven output artifact types that can lead to different goal bindings.

A deeper dive into both is the next logical step in this blog.

Binding Goals Using Plugins

Plugins – definers of goals

Plugins are maven’s way of defining goals and providing connectors for the goals to phases. Plugins are developed as MOJOs (Maven’s plain Old Java Objects). The plugins define goals and supply logic to deliver the goals. Goals are usually bound to phases in either the maven built-in configurations or the project POM file.

An example of binding

Here is a dissection for the Clean lifecycle (very confusing nomenclature, but worth reading):

Lifecycle clean : Phase clean
Plugin maven-clean-plugin (prefix clean) : Goal clean

The clean:clean (plugin:goal) is bound to the clean phase. Thus, executing the clean phase will trigger the run of the maven-clean-plugin which will look for and execute the clean goal.

Another example

Lifecycle site : Phase site-deploy
Plugin maven-site-plugin (prefix site) : Goal site-deploy

The site:site-deploy (plugin:goal) is bound to the site-deploy phase. Thus, executing the site-deploy phase will trigger the run of the maven-site-plugin which will look for and execute the site-deploy goal.

Plugins and Goals

Maven offers a set of bundled plugins and these plugins have a defined set of goals. Plugins can also be external to the maven distribution and can be built by other users of maven. The standard lifecycle phases of clean and site have default phases and bindings defined. This is done in the maven-core META-INF/plexus/components.xml, if you’re curious to dig through the details.

It is usually rare to bind goals, by default, to phases prefixed with pre and post, but those are valid phases and goals can be bound to such.

Here is a dissection for the Clean lifecycle (very confusing nomenclature, but worth reading)

Lifecycle clean
Default Phase: clean

Associated plugin (as of Maven 3.6.3): org.apache.maven.plugins:maven-clean-plugin:2.5 : clean

The default phase implies that if the lifecycle is invoked without a phase, the default phase (and any phases prior to it) will be executed. In this case, invoking the lifecycle will invoke both the pre-clean and clean phases.

The plugin coordinates follow the standard maven GAV coordinate system that was covered in Part 3 of this series. The content after the colon separator after the plugin coordinates is to point to the goal in the plugin.

Command line: mvn clean

The site lifecycle has two default phases, site and site-deploy. A dissection of the site-deploy default phase is shared below.

Lifecycle site
Default Phase(s) site and site-deploy

Associated plugin (for site-deploy and as of Maven 3.6.3): org.apache.maven.plugins:maven-site-plugin:3.3 : deploy

Invoking the default phase of site-deploy will cause all phases (pre-site, site, post-site and site-deploy) to be executed.

The plugin coordinates follow the standard maven GAV coordinate system that was covered in Part 3 of this series. The content after the colon separator after the plugin coordinates is to point to the goal in the plugin.

Command line: mvn site-deploy

The default lifecycle is unique. The overarching focus of the default lifecycle is to verify, compile, generate sources/resources, test, install and deploy the project code. It is thus not practical to setup defaults in this lifecycle, to a phase. The bindings are set at a different level called packaging.

A good listing for site and clean phases’ bindings can be found at: https://maven.apache.org/ref/3.6.3/maven-core/lifecycles.html.

Binding Goals Using Packaging

Packages – descriptors of outputs

Packages are a core element in the Maven POM and define the type of output produced by building the POM. It is possible to tie goals to the package element. Valid package names include: jar, war, ear, pom etc. Each package has some unique goals and thus goals are bound to these package names.

Packages and Goals

Maven allows binding goals to packages. The default lifecycle is a great example and the bindings can be located at the maven-core META-INF/plexus/default-bindings.xml.

A few examples of bindings:

Package bindings for ejb, ejb3, jar, war, par and rar packages:

jar, war, rar, ejb, ejb3,par packages

Phaseplugin:goal
process-resourcesresources:resources
compilecompiler:compile
process-test-resourcesresources:testResources
test-compilecompiler:testCompile
testsurefire:test
packageejb:ejb or ejb3:ejb3 or jar:jar or par:par or rar:rar or war:war
installinstall:install
deploydeploy:deploy

Package bindings for pom package:

pom package

Phaseplugin:goal
package 
installinstall:install
deploydeploy:deploy

A good listing for the default lifecycle bindings can be found at: https://maven.apache.org/ref/3.6.3/maven-core/default-bindings.html.

Commands in Maven

In several examples in this and prior blogs in the series, we displayed maven commands. Maven executes such commands by invoking goals of a plugin. Maven commands could match any of the following:
  • mvn [<flags>] [<options>] <groupId>:<artifactId>:<version>:goal>
  • mvn [<flags>] [<options>] <groupId>:<artifactId>:goal>
  • mvn [<flags>] [<options>] <pluginPrefix>:<goal>

Summary

This blog was lengthy !

A lot of material was covered. In summary:

  1. Maven provides standard lifecycles.
  2. Lifecycles are a collection of phases.
  3. Logic to execute specific actions is developed in plugins.
  4. Plugins are MOJOs (Maven plain Old Java Objects)
  5. Plugins define and carve out tasks called goals.
  6. A plugin can define several goals.
  7. Plugin goals are bound to parts of lifecycle and executed.
  8. The goals can be bound at either by plugin or based on packaging type of the POM.
  9. Execution of a maven lifecycle implies execution of goals bound to parts of the lifecycle.
  10. Maven commands invoke a goal .

A deeper dive into plugin-prefixes can be found at: https://maven.apache.org/guides/introduction/introduction-to-plugin-prefix-mapping.html.

Convention standards for plugin prefixes:

  • maven-${prefix}-plugin – for official plugins maintained by the Apache Maven team itself (you must not use this naming pattern for your plugin, more on this in a future blog on plugin development)
  • ${prefix}-maven-plugin – for plugins from other sources

Something Something – Personal Learning

Here is a very crude and unscientific pictorial of the my understanding of lifecycles, phases, goals and plugins. This is not meant to be accurate in terms of either human lifecycles or in explaining maven’s lifecycles. This picture is absolutely a personal means of illustrating how I went about learning these concepts.

Possibly inaccurate analogy of a young human lifecycle with phases such as terrible twos and adoloscence, with goals associated with each and soe external influences as plugins.
Highly unscientific, possibly inaccurate lifecycle of a young human from Age 0 to Age 18. Time ranges also not distributed proportionally.

That’s a wrap on this post. Have fun !

Part 3
Dependency coordinates and POM hierarchies
IndexPart 5

Understanding Apache Maven – Part 3 – Maven Coordinates & POM Inheritance

java, maven

Published: 2020-05 (May 2020)
Verified with: Apache Maven 3.6.3

Link to an index, to find other blogs in this series.

In Part 3 of the series , an explanation of dependency coordinates and ‘distinguishers’ as well as a more detailed look at POM hierarchies are covered.

What are dependency coordinates?

There are hundreds or thousands of projects that produce artifacts. Some such artifacts can potentially be used in a current project as libraries. For instance, a project may depend on a logging framework or a JSON library. It is possible to host many such dependency artifacts on a central repository. Maven’s primary such repository is called Maven Central. Many other such repositories also exist. More on this later.

With the availability of such repositories, the next question is, how are exact artifacts needed for a project identified and downloaded? A proper way to identify the artifact is via its maven coordinates.

What are maven coordinates?

A way to uniquely identify an artifact. There are three primary coordinates that are used to identify an artifact.

groupId

A grouping classification, typically referring to an organization, a company and may include a basic theme for one or more projects. A groupId typically follows a dot notation similar to a Java package name. Each token in the dot notation corresponds to a directory in a tree structure on the repository. For instance, a groupId of org.apache.commons corresponds to $REPO/org/apache/commons.

While is it strongly recommended, it is not a requirement to have the dot notation. Several projects have forgone the need for a dot notation and simply carried a simple name as the groupId. This single-name practice is discouraged. All projects are recommended to maintain a fully-qualified dot-notated groupId.

artifactId

A proper name for the project. Among the many projects that exist in the group, the artifactId can uniquely identify the artifact. An artifactId follows a simple name nomenclature with hyphenation recommended for multi-word names. Names should ideally be small in length. An artifact manifests itself as a sub-directory under directory tree that represents the groupId. For instance, an artifactId of commons-lang3 under a groupId of org.apache.commons would determine that the artifact can be found under : $REPO/org/apache/commons/commons-lang3/.

version

An identifier that tracks unique builds of an artifact. A version is a string that is constructed by the project’s development team to identify a set of changes from a previous creation of an artifact of the same project. It is strongly recommended to follow semantic versioning schemes for versions, although it is not mandated. A version manifests itself as a sub-directory under the directory tree that represents the groupId and artifactId. For instance a version of 3.1.0 for an artifactId commons-lang3 under the groupId of org.apache.commons would determine that the artifact would located under: $REPO/org/apache/commons/commons-lang3/3.1.0/.

The GAV coordinate system

A common way of communicating an artifacts coordinates is with a colon separation. Together the coordinates are referred to as Group-Artifact-Version or GAV coordinates. The GAV coordinates for commons-lang3 version 3.1.0 will be: org.apache.commons:commons-lang3:3.1.0.

Additional distinguishers

Often times, a project’s build may include more than one format of artifacts. A maven execution on a project could emit a jar file, a zip file a tarball and many other artifacts. An execution could also emit different outputs such as a binary, a zip file of sources, a zip file of javadoc files etc. Apart from the above mentioned GAV coordinates, distinguishers are thus needed to identify such diverse outputs.

classifier

A classifier is used to distinguish an alternate output emitted by executing maven on the project POM. Common examples include sources as a .jar/ .zip file and javadoc as a .jar / .zip file. The classifier manifests itself as a part of the artifact name. For our above example of commons-lang3, the artifact to look for is: commons-lang3-3.10-javadoc.jar or commons-lang3-3.10-sources.jar under $REPO/org/apache/commons/commons-lang3/3.1.0/.

type

A type is used to distinguish the artifact format. Artifacts emitted from a maven execution can be of various types as already discussed: .jar, .war, .zip etc. It may, at times be beneficial for a project to produce artifacts in different formats. These formats are specified under a type distinguisher.

Putting it all together

A combination of GAV coordinates and distinguishers can be used to locate the exact artifact needed for the project.

POM Hierarchies

This section describes the hierarchy in maven POMs.

Parent POM

A parent POM is a POM from which the current project POM can inherit content. The project POM can depend on exactly one parent POM. This single-parent inheritance is one-way. The parent POM is unaware of the POM that inherits from it. The child POM declares the parentage in its own pom.xml (standard file to hold the POM, can be customized to a different name). Any number of POMs can declare another POM as their parent.
USAGE: While we will delve into the contents in a future blog, the parent POM can be used to declare re-usable portions of the POM that individual child POMs can then inherit. This helps in both maintenance and to reduce clutter.

Aggregator POM

An aggregator POM (also known as a reactor POM) is a POM that can sequence the builds of many projects. An aggregate POM specifies all the projects that can be build-managed together. The child POM(s) remain unaware of the aggregator POM that invokes it. A child POM can be a listed in more than one aggregator POM. The aggregator POM lists the child POM by name in it’s own pom.xml as a module. As the declared module suggests, this pattern is for modular builds of projects. There is no inheritance of any content from the aggregator POM.
USAGE: While we will dig deeper in a future blog, the aggregator POM can be used to ensure the sequence of builds and maintain a list of projects that should be build-managed together.

Bill-Of-Materials POM

A bill-of-materials POM is a POM that can declare bundles of dependencies that have been tested to work well together. The abundance of artifacts and versions of each can, at times, lead to confusion and needs for trial-and-error mechanisms to determine compatibility and/or right functionality. A bill-of-materials POM reduces that overhead. A bill-of-materials POM is a means of multiple inheritance too, since a project POM can import multiple bill-of-material POMs. The bill-of-materials POM is unaware of the child POMs that import it. The child POMs declare the bill-of-materials in the project POM.
USAGE: While this will be covered in detail in a future blog, the bill-of-materials POM can be used to bundle well-working dependencies (with right versions) together to avoid repetition for every project.

A POM can be both a parent as well as an aggregator. This allows for a two-way relationship in a tightly knit set of modules that depend on a common set of inherited content.

That’s a wrap on this blog. Have fun!

Part 2
The Project Object Model (POM) and Effective POMs
IndexPart 4
Maven Lifecycles, Phases, Plugins and Goals

Understanding Apache Maven – Part 2 – POM Hierarchy & Effective POM

java, maven

Published: 2020-05 (May 2020)
Verified with: Apache Maven 3.6.3

Link to an index, to find other blogs in this series

In this blog, the Project Object Model (POM) is explored.

What is the Project Object Model?

First, a maven POM is not a popular pomegranate juice nor is it related to the colorful pom-poms. A maven POM is definitely is wonderful and brings as much joy to a developer as does a pom-pom to a kid.

A POM describes build management needs of a project:

  • project coordinates – uniquely identifiable set of properties by which the project artifacts can be consumed elsewhere.
  • dependencies – libraries and code needed to execute the project build management
  • plugins – helper tools that execute build and build management aspects
  • properties – common and extracted values used in the project
  • inheritance details – the ability to create a hierarchy of re-usable POM components
  • profiles – alternate execution pathways that can be activated on a per-execution basis
  • . . .

How does maven interact with a POM?

Maven utilizes content in the POM for its build management. However, maven also has convention-based defaults. Maven thus has the onus of amalgamating defaults and applying overrides and additions discovered in the project’s pom file (typically a pom.xml).

This amalgamation of defaults and applying overrides and additions results in an effective POM.

What is an effective POM?

An effective POM is:

  • an assembly of execution steps, properties and profiles
  • the content that maven can execute for the project
  • an exhaustive set of dependencies and plugins needed for such an execution
  • determination of any transitive dependencies (dependencies of dependencies, full depth imaginable)
  • any conflict resolution in terms of dependency versions

How does maven assemble the effective POM?

A set of boxes representing the maven internal defaults -> maven super pom -> maven global settings -> maven user settings -> Parent/bill-of-material poms -> project pom that finally results in an effective POM.

Maven assembles its effective POM by traversing the layers that act as building blocks. Each layer used has the ability to override or enrich the content of what will become an effective POM. Maven internal defaults and the super POM are built-in to the maven installation, so ideally not subject to customization. The layers below, the global settings and user settings are, as their name suggests, inclined towards hosting and overriding any settings for maven. The parent, bill-of-material and project POM files are where maven instructions can be customized. Default values from above layers are utilized if no customization is made.

The layers explained

Going through the layers:

Maven Internal Defaults

This layer is within the maven’s own code. Unless the intent is to modify maven’s source code, it is safe to assume that these defaults are not modifiable.
Location: Internal to the maven installation.

Maven Super POM

This Super POM exists in the maven’s own code. Once again, unless the intent is to modify maven’s own source code, it is same to assume that the Super POM is not modifiable.
Location: Internal to the maven installation

Maven Global Settings

Once Maven is installed/unarchived on a computing device, it creates a directory structure. One of the base directories, (right below the maven directory) is conf. This directory contains a settings.xml referred to as a global settings file. Since this file exists on the local computing device, it is editable. Typically, any settings that apply to all projects being managed on the computing device are managed in this global settings.xml. Examples may include proxy settings, corporate server URLs, mirrors etc. It should not often be modified, in any case.
Location:
Unix/MacOS: <maven installation>/conf/settings.xml
Windows: <maven installation\conf\settings.xml

Maven User Settings

Similar to the global settings, but at a different location, it is possible to create a user settings file. This file is also named settings.xml but its location is under the user home in an .m2 directory. The purpose of the user settings.xml is to setup any settings that apply to projects managed by the specific user (there could be multiple users on a given computing device). Examples include usernames and passwords to connect to the network, repository ordering etc.
Location:
Unix/MacOS: <user.home>/.m2/settings.xml
Windows: <user.home>\.m2\settings.xml

Parent / Bill-of-Material POMs

Maven has an inheritance and version-trait model. Maven can inherit content from a parent pom and version-traits from a bill-of-materials pom. Typically parent POMs contain re-usable dependencies, plugins and properties used by a project POM. The Bill-of-Material (BOM) POM is a specialized POM that allows to group together dependency versions of dependencies that are known to be valid and tested to work together. Using a BOM POM reduces the developer grief from having to test compatibility of different dependencies. Modification of either is possible if there is a need and the entitlement to change. Since changes to either can impact several other projects, appropriate version increments and reviews are recommended for changes.
Location: Various locations, either on device or elsewhere in some repository.

Project POM

This is the Project Object Model for the project. Project specific maven instructions are detailed in this location. Typical contents include: a unique set of coordinates used to identify the project, name and description of the project, a set of developers associated with the project, any source control management details specific to the project, all project-specific dependencies and plugins, any profiles that allow for alternate executions of maven on this project and so on. All these will be covered in a blog in this series. The file containing this POM is, by convention, named pom.xml, but other names can be used. If an alternate name is used, then the maven executable will need to be pointed to the the filename for execution.
Location:
UNIX/MacOS: $PROJECT/pom.xml
Windows: $PROJECT\pom.xml

The next blog will cover POM hierarchies. Have fun!

Part 1
Apache Maven basics
IndexPart 3
Dependency coordinates and POM hierarchies

Understanding Apache Maven – Part 1 – The basics

Uncategorized

Published: 2020-05 (May 2020)
Verified with: Apache Maven 3.6.3

Link to an index, to find other blogs in this series.

This blog (a part of the series) is an introduction to Apache Maven basics.

Apache Maven (commonly referred to as “maven” ) is a Build Management tool. Maven is primarily used to build Java projects. Other language projects can also be built using maven. Apache Maven is written in Java, for most part. It is an open source project.

Maven follows a convention-over-configuration philosophy. More on this follows.

Why is Maven a Build Management tool?

Maven aims at achieving the following for a project:

  • build tooling
  • version management
  • re-usability
  • maintainability
  • comprehensibility
  • inheritance

In addition, maven provides capabilities for a project to :

  • produce different build targets and results builds via profiles
  • test code
  • generate documentation
  • furnish metrics and reports
  • deploy built artifacts

What is Convention-Over-Configuration?

Convention-over-configuration is a software paradigm. The main intent of such a paradigm is to reduce the number of superfluous decisions required by a developer to build her/his project. The paradigm aims to meet and satisfy the “principle of least astonishment“.

Apache Maven – convention over configuration

Apache Maven provides sensible defaults for a project’s build management. A developer can then choose to override any preset defaults.

An example of such is clear in the conventional directory structure of a maven project.

project
|
|____src
|   |
|   |____main
|   |   |
|   |   |____java
|   |   |
|   |   |____resources
|   |
|   |____test
|       |
|       |____java
|       |
|       |____resources
| 
|____pom.xml

Printed using: alias tree="find . -print | sed -e 's;[^/]*/;|____;g;s;____|; |;g'"

In this directory structure, the source code is, by convention located under the project root directory in a src directory. Under the src directory there exists a main directory that is, by convention expected to contain production code, that is, code that is expected to be a part of the final executable. Parallel to the main directory, there is a test directory, where test code is expected, by convention. Java code, once again, by convention (I think by now the point is made, will stop referring to it) exists in a java directory either in the production or in the test location. Similarly resources needed for production or test outputs, reside respectively in the resources directory.

This is just one example. Maven uses the convention-over-configuration philosophy in many other areas.

What are some Maven capabilities?

A few features that maven offers (this is not a comprehensive list):

  • Validate the project structure
  • Auto generate any code/resources needed by the project
  • Generate any documentation
  • Compile source code, display errors / warnings
  • Test the project based on existing tests
  • Package compiled code into artifacts (examples include .jar, .war, .ear, .zip archives and many more)
  • Package source code into downloadable archives / artifacts
  • Install packaged artifacts on to a server for deployment or into a repository for distribution
  • Generate site reports and test evidence
  • Report a build as success or failure
  • . . .

How does Maven work?

Maven uses a Project Object Model (POM) to manage a project. Maven commands execute parts of its Project Object Model. A Project Object Model is usually described as an XML document. A POM description is NOT limited to XML. Other formats can be used to describe the Project Object Model, however, XML was the first format used.

A picture to illustrate a typical maven execution:

A pictorial overview of how maven interacts with a project's Project Object Model. Includes assembling, download of dependencies and plugins, execution of build lifecycles and an upload of build artifacts to either a local repository or to a maven repository on a network
Maven – A pictorial overview

The next blog in this series will dig into details of a Project Object Model (POM). Have fun !

             IndexPart 2
The Project Object Model (POM) and Effective POMs

Understanding Apache Maven – Part 1 – Basics

java, maven

Published: 2020-05 (May 2020)
Verified with: Apache Maven 3.6.3

Link to an index, to find other blogs in this series.

This blog (a part of the series) is an introduction to Apache Maven basics.

Apache Maven (commonly referred to as “maven” ) is a Build Management tool. Maven is primarily used to build Java projects. Other language projects can also be built using maven. Apache Maven is written in Java, for most part. It is an open source project.

Maven follows a convention-over-configuration philosophy. More on this follows.

Why is Maven a Build Management tool?

Maven aims at achieving the following for a project:

  • build tooling
  • version management
  • re-usability
  • maintainability
  • comprehensibility
  • inheritance

In addition, maven provides capabilities for a project to :

  • produce different build targets and results builds via profiles
  • test code
  • generate documentation
  • furnish metrics and reports
  • deploy built artifacts

What is Convention-Over-Configuration?

Convention-over-configuration is a software paradigm. The main intent of such a paradigm is to reduce the number of superfluous decisions required by a developer to build her/his project. The paradigm aims to meet and satisfy the “principle of least astonishment“.

Apache Maven – convention over configuration

Apache Maven provides sensible defaults for a project’s build management. A developer can then choose to override any preset defaults.

An example of such is clear in the conventional directory structure of a maven project.

project
|
|____src
|   |
|   |____main
|   |   |
|   |   |____java
|   |   |
|   |   |____resources
|   |
|   |____test
|       |
|       |____java
|       |
|       |____resources
| 
|____pom.xml

Printed using: alias tree="find . -print | sed -e 's;[^/]*/;|____;g;s;____|; |;g'"

In this directory structure, the source code is, by convention located under the project root directory in a src directory. Under the src directory there exists a main directory that is, by convention expected to contain production code, that is, code that is expected to be a part of the final executable. Parallel to the main directory, there is a test directory, where test code is expected, by convention. Java code, once again, by convention (I think by now the point is made, will stop referring to it) exists in a java directory either in the production or in the test location. Similarly resources needed for production or test outputs, reside respectively in the resources directory.

This is just one example. Maven uses the convention-over-configuration philosophy in many other areas.

What are some Maven capabilities?

A few features that maven offers (this is not a comprehensive list):

  • Validate the project structure
  • Auto generate any code/resources needed by the project
  • Generate any documentation
  • Compile source code, display errors / warnings
  • Test the project based on existing tests
  • Package compiled code into artifacts (examples include .jar, .war, .ear, .zip archives and many more)
  • Package source code into downloadable archives / artifacts
  • Install packaged artifacts on to a server for deployment or into a repository for distribution
  • Generate site reports and test evidence
  • Report a build as success or failure
  • . . .

How does Maven work?

Maven uses a Project Object Model (POM) to manage a project. Maven commands execute parts of its Project Object Model. A Project Object Model is usually described as an XML document. A POM description is NOT limited to XML. Other formats can be used to describe the Project Object Model, however, XML was the first format used.

A picture to illustrate a typical maven execution:

A pictorial overview of how maven interacts with a project's Project Object Model. Includes assembling, download of dependencies and plugins, execution of build lifecycles and an upload of build artifacts to either a local repository or to a maven repository on a network
Maven – A pictorial overview

The next blog in this series will dig into details of a Project Object Model (POM). Have fun !

             IndexPart 2
The Project Object Model (POM) and Effective POMs

Understanding Apache Maven – The Series

Uncategorized

This is a series of blogs about Apache Maven – a build management tool. This series is intended to be an introductory set of blogs to introduce, familiarize or brush up on maven.

The blogs in this series:

Part 1 – Apache Maven basics
Bare Link: https://cguntur.me/2020/05/23/understanding-apache-maven-part-1/

Part 2 – The Project Object Model (POM) and Effective POMs
Bare Link: https://cguntur.me/2020/05/24/understanding-apache-maven-part-2/

Part 3 – Dependency coordinates and POM hierarchies
Bare Link: https://cguntur.me/2020/05/26/understanding-apache-maven-part-3/

Part 4 – Maven Lifecycles, Phases, Plugins and Goals
Bare Link: https://cguntur.me/2020/05/29/understanding-apache-maven-part-4/

Using JUnit5 – Part 4 – Filtering Tests

java, junit5

Published: 2019-07 (July 2019)
Relevant for: JUnit 5.5.0

JUnit5 Blog Series

Part 1 – Introduction
Part 2 – Test Basics
Part 3 – Display Names
Part 4 – Filtering tests

In Part 1 of the blog series, we looked at several annotations used in JUnit5. We covered test methods as well as lifecycle methods.

In Part 2 of the blog series, we covered the basics of testing using JUnit5. We covered test annotations such as marking a test method and asserting. We saw how a test method could be tagged and how assumptions can be used. We finally wrapped up with test execution ordering mechanisms.

In Part 3 of the blog series, we detailed ways in which JUnit test classes and test methods could be customized with readable and meaningful names rather than the standard class and method names.

This post will cover filtering JUnit tests for execution. There are several reasons for running a subset of your comprehensive suite of tests. Some example usages include:

  • You may have unit and integration tests and wish to only run unit tests.
  • You may want a sanity check with the core features alone being tested.
  • You may wish to bypass a few tests to test a specific situation.
  • You may wish to skip a few failing tests, since you know they fail.

Filtration Types

Dynamic filtration

In Part 2 of the blog series, we covered what I term, dynamic filtration. This was done using Assumptions (https://junit.org/junit5/docs/5.5.0/user-guide/#writing-tests-assumptions). Assumptions are conditions that when not met, cause the test to be aborted rather than fail. Assumptions are a great feature to dynamically filter unit tests.

Assumptions are evaluated during execution of the test class, and are embedded within the code block of the test method. Repeating the example we covered in that blog:

class TestWithAssumptions {

    @Test
    void testOnlyOnHost123() {
        assumeTrue("host123".equals(System.getenv("HOSTNAME")));
        // remainder of test
    }

    @Test
    void testOnHost123OrAbortWithMessage() {
        assumeTrue("host123".equals(System.getenv("HOSTNAME")),
            () -> "Aborting test: not on host123");
        // remainder of test
    }

}

Static filtration

In Part 2 of the blog series, we covered the @Tag (https://junit.org/junit5/docs/5.5.0/api/org/junit/jupiter/api/Tag.html) annotation on the unit test method. This annotation can be used to statically label classes and methods. The benefit of such an identification (or label or category) is that a predicate check can be made of such.

Filtering tests based on static labels is what I term static filtration. Since the Tag is an annotation, it is not a part of the code block that executes the unit test. A Tag is a @Repeatable (https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/annotation/Repeatable.html) annotation, which implies a class or method can have several tags annotating them.

How Static Filtration works

Code Katas are means to teach a concept, a feature or a functionality. In some Code Katas we deliberately remove logic to make tests fail. The mission for the student is to “fix” these tests. Since this is a learning process, there is a known solution also provided. In order to verify that the solutions are not failing, we can run a build that executes only the solutions (and other tests that are expected to pass). A way of doing that is to leverage Tags.

In this above example, we can thus tag known passing tests as “PASSING” and the kata tests as “TODO”.

See PublicMethodInvocationTest (https://github.com/c-guntur/java-katas/blob/baseline/java-handles/src/test/java/none/cvg/methods/PublicMethodInvocationTest.java).

  • On line 37, the test method is tagged as PASSING. This is a test that is expected to always pass, since it is already solved.
  • On line 63, the other test method is tagged as TODO, since it is what needs to be fixed (and is expected to fail).

We will now look at how we can filter tests both in an IDE (IntelliJ IDEA) as well as during a build process (Apache Maven).

Filtering tests in IDE

When we run all tests in the IDE, screen shot below, ALL tests found are executed, which is not the ideal outcome since several tests marked TODO will fail.

JUNitRunAllTests

Run All Tests (in IntelliJ IDEA)

The Runner configuration can be edited. Select a Test Kind, with value Tags. In this example we used PASSING and TODO as the tags. We are trying to only run the PASSING tests, thus the Tag expression we use is PASSING.

JUNitRunConfiguration

Configure to Run tests with a specific Tag (in IntelliJ IDEA)

When this run configuration is executed, any test tagged as PASSING is included and executed. Tests without this tag (or with the TODO tag, in this example), are filtered out and ignored.

Filtering tests in a build

It is great to setup the IDE on a desktop to work as needed. Repeatability and automation drives using a build tool to do the same. We can also filter tests based on Tags using a build tool such as maven.

JUnit tests are executed in the test phase of the maven lifecycle. A common (and default) plugin to run unit tests is the maven-surefire-plugin. The plugin can be configured to include and exclude groups (generic name corresponding to tags in JUnit5, other unit test frameworks may have other names).

Example: pom.xml (https://github.com/c-guntur/java-katas/blob/baseline/pom.xml#L59-L67)

                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.22.2</version>
                    <configuration>
                        <groups>PASSING</groups>
                        <excludedGroups>TODO</excludedGroups>
                    </configuration>
                </plugin>

In this example, when the maven build is run, any test with a Tag of PASSING is included and any test with a Tag of TODO is excluded.

Summary

In this blog, we saw how we could filter test class and test method names both dynamically using assumptions and more statically using Tags.

Hope this was helpful !

Using JUnit5 – Part 3 – Display Names

java, junit5

Published: 2019-07 (July 2019)
Relevant for: JUnit 5.5.0

JUnit5 Blog Series

Part 1 – Introduction
Part 2 – Test Basics
Part 3 – Display Names
Part 4 – Filtering tests

In Part 1 of the blog series, we looked at several annotations used in JUnit5. We covered test methods as well as lifecycle methods.

In Part 2 of the blog series, we looked at the basics of testing using JUnit5. We covered test annotations such as marking a test method and asserting. We saw how a test method could be tagged and how assumptions can be used. We finally wrapped up with test execution ordering mechanisms.

This post will cover some customization of names for tests. First, a justification of why names should be customized.

Why customize names?

When test class with a few test methods is run with JUnit, the output produced lists the name of the class and a status of execution for each method. The name of the class is used as the top level identifier

JUnitNoDisplayName

As is visible from the image above, a JUnit test was run on a class STestSolution3PeriodsAndDurations. This has four test methods that were tested and they all verify something. All tests passed. However, one really has to peer into the names of all the tests to understand what they executed. For instance, the second test verifies creation of a Period using fluent methods. This was inferred and hopefully most developers name their test methods to convey meaningful intent to anyone who looks at the result.

Let’s compare that to the next image.

JUnitWithDisplayName

Clearly the latter image communicates a lot better about what was tested and what the intent was. The test class is replaced with a meaningful text of what the overall theme for all test methods enclosed was : “Periods (days, months, years) and Durations (hours, minutes, seconds)“. Also individual test methods had proper space-separated words rather than a camel-cased name.

Let’s now look at how we customize the names in JUnit5.

Customizing names in JUnit5

There are primarily two ways in which JUnit5 allows for customizing names.

  1. Using a @DisplayName on a test class or a test method.
  2. Using a @DisplayNameGeneration on the test class which accepts an attribute of a DisplayNameGenerator class.

DisplayName API: https://junit.org/junit5/docs/5.5.0/api/org/junit/jupiter/api/DisplayName.html
DisplayNameGeneration API: https://junit.org/junit5/docs/5.5.0/api/org/junit/jupiter/api/DisplayNameGeneration.html
DisplayNameGenerator API: https://junit.org/junit5/docs/5.5.0/api/org/junit/jupiter/api/DisplayNameGenerator.html

Using a DisplayName annotation

Adding a @DisplayName annotation on a given class or test method can help customize a single class or method name. Let us look at examples.

DisplayName on a test class

Example: @DisplayName (https://github.com/c-guntur/java-katas/blob/baseline/java-datetime/src/solutions/java/none/cvg/datetime/STest3PeriodsAndDurationsTest.java#L35)

/**
* DateTime ranges: Period, Duration tests.
*
* Note: We create a Clock instance in setup() used for some of the tests.
*
* @see Clock
* @see Period
* @see Duration
* @see ChronoUnit
*/
@DisplayNameGeneration(DateTimeKataDisplayNames.class)
@DisplayName("Periods (days, months, years) and Durations (hours, minutes, seconds)")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class STest3PeriodsAndDurationsTest {

DisplayName on a test method

Example: @DisplayName (https://github.com/c-guntur/java-katas/blob/baseline/java-datetime/src/solutions/java/none/cvg/datetime/STest2LocalAndZonedDateTimesTest.java#L304)

    @Test
    @Tag("PASSING")
    @Order(10)
    @DisplayName("verify conversion of UTC date time to Indian Standard Time")
    public void verifyConversionOfUTCDateTimeToIndianStandardTime() {

        ZonedDateTime allDateTimeOhFives =
                ZonedDateTime.of(5, 5, 5, 5, 5, 5, 555, ZoneId.ofOffset("", ZoneOffset.UTC));

        ZoneId gmtPlusOneZoneId = ZoneId.ofOffset("", ZoneOffset.of("+0530"));

        // DONE: Replace the ZonedDateTime.now() below to display the below UTC time in GMT +0530
        //  The ZonedDateTime created in GMT. Fix the calls so a ZonedDateTime
        //  can be created with the offset of GMT +0530. Use an ofInstant from a toInstant.
        //  Check: java.time.ZonedDateTime.ofInstant(java.time.Instant, java.time.ZoneId)
        //  Check: java.time.ZonedDateTime.toInstant()
        ZonedDateTime gmtPlusOneHourTimeForAllFives =
                ZonedDateTime.ofInstant(
                        allDateTimeOhFives.toInstant(),
                        gmtPlusOneZoneId);

        assertEquals(10,
                gmtPlusOneHourTimeForAllFives.getHour(),
                "The hour should be at 10 AM when Zone Offset is GMT +0530");

        assertEquals(35,
                gmtPlusOneHourTimeForAllFives.getMinute(),
                "The minute should be 35 when Zone Offset is GMT +0530");
    }

Using DisplayNameGenerator

Using a generator to modify display names is a two step process.

  1. Create a DisplayNameGenerator class.
  2. Set DisplayNameGeneration annotation on the Test class.

Setting up the DisplayNameGenerator

DisplayNameGenerator is an interface that has three methods with very sensible names that convey theor purpose:

  • generateDisplayNameForClass(Class<?> testClass) – This method can be implemented to provide a meaningful display name to the test class.
  • generateDisplayNameForNestedClass(Class<?> nestedClass) – This method can be implemented to provide a meaningful display name to a nested class in the test class.
  • generateDisplayNameForMethod(Class<?> testClass, Method testMethod) – This method can be implemented to provide a meaningful display name to a test method of a given test class.

Usage

DisplayNameGenerator is an interface, but has two out-of-the-box implementations that can be extended/adapted as needed.

  1. DisplayNameGenerator.Standard – converts camel case to spaced words.
  2. DisplayNameGenerator.ReplaceUnderscores – converts underscores in names as space-separated words.

The example extends the Standard implementation.

Example: DisplayNameGenerator (https://github.com/c-guntur/java-katas/blob/baseline/java-handles/src/main/java/none/cvg/handles/HandlesKataDisplayNames.java)

package none.cvg.handles;

import java.lang.reflect.Method;

import org.junit.jupiter.api.DisplayNameGenerator;

import static java.lang.Character.isDigit;
import static java.lang.Character.isLetterOrDigit;
import static java.lang.Character.isUpperCase;

public class HandlesKataDisplayNames extends DisplayNameGenerator.Standard {
    @Override
    public String generateDisplayNameForClass(Class<?> aClass) {
        return super.generateDisplayNameForClass(aClass);
    }

    @Override
    public String generateDisplayNameForNestedClass(Class<?> aClass) {
        return super.generateDisplayNameForNestedClass(aClass);
    }

    @Override
    public String generateDisplayNameForMethod(Class<?> aClass, Method method) {
        String methodName = method.getName();
        if (methodName.startsWith("reflection")) {
            return "using Reflection";
        }
        if (methodName.startsWith("unsafe")) {
            return "using Unsafe";
        }
        if (methodName.startsWith("methodHandle")) {
            return "using Method Handles";
        }
        if (methodName.startsWith("compareAndSet")) {
            return camelToText(methodName.substring(13));
        }
        if (methodName.startsWith("get")) {
            return camelToText(methodName.substring(3));
        }
        return camelToText(methodName);
    }


    private static String camelToText(String text) {
        StringBuilder builder = new StringBuilder();
        char lastChar = ' ';
        for (char c : text.toCharArray()) {
            char nc = c;

            if (isUpperCase(nc) && !isUpperCase(lastChar)) {
                if (lastChar != ' ' && isLetterOrDigit(lastChar)) {
                    builder.append(" ");
                }
                nc = Character.toLowerCase(c);
            } else if (isDigit(lastChar) && !isDigit(c)) {
                if (lastChar != ' ') {
                    builder.append(" ");
                }
                nc = Character.toLowerCase(c);
            }

            if (lastChar != ' ' || c != ' ') {
                builder.append(nc);
            }
            lastChar = c;
        }
        return builder.toString();
    }
}

Once a DisplayNameGenerator is created, the second step is to associate it with a test class. This requires using the @DisplayNameGeneration annotation on the test class.

Applying a DisplayNameGenerator

An annotation on a test class is required to avail of the generator logic. This is done by adding a @DisplayNameGeneration annotation on the test class.

Example: @DisplayNameGeneration (https://github.com/c-guntur/java-katas/blob/baseline/java-handles/src/solutions/java/none/cvg/constructors/SDefaultConstructorInvocationTest.java#L34)

package none.cvg.constructors;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

import none.cvg.handles.DemoClass;
import none.cvg.handles.HandlesKataDisplayNames;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import sun.misc.Unsafe;

import static none.cvg.handles.ErrorMessages.REFLECTION_FAILURE;
import static none.cvg.handles.ErrorMessages.TEST_FAILURE;
import static none.cvg.handles.ErrorMessages.UNSAFE_FAILURE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;

/*
 * DONE:
 *  This test aims at using MethodHandles to invoke a default constructor on a class in order to
 *  create a new instance.
 *  Each solved test shows how this can be achieved with the traditional reflection/unsafe calls.
 *  Each unsolved test provides a few hints that will allow the kata-taker to manually solve
 *  the exercise to achieve the same goal with MethodHandles.
 */
@DisplayNameGeneration(HandlesKataDisplayNames.class)
@DisplayName("Invoke DemoClass()")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class TestSolutionDefaultConstructorInvocation {

Summary

In this blog, we saw how we could customize test class and test method names to produce a more meaningful output. The next blog will cover how we can filter tests based on tags.

Hope this was helpful !

 

Using JUnit5 – Part 2 – Testing Basics

java, junit5

Published: 2019-07 (July 2019)
Relevant for: JUnit 5.5.0

JUnit5 Blog Series

Part 1 – Introduction
Part 2 – Test Basics
Part 3 – Display Names
Part 4 – Filtering tests

In Part 1 of the blog series, we looked at several annotations used in JUnit5. We covered test methods as well as lifecycle methods.

This post will share examples of a JUnit test which has a few of these annotations.

We will use a recently created code kata for the examples.

Testing

Marking a method as a Test

Tests in JUnit5 are annotated with the @Test annotation. Unlike prior versions of JUnit, the JUnit5 @Test annotation does not have any attributes. Prior versions supported extensions via attributes, while JUnit5 fosters a custom annotation based extension (more on this in a future blog).

Example: @Test (https://github.com/c-guntur/java-katas/blob/baseline/java-datetime/src/solutions/java/none/cvg/datetime/STest1InstantAndDateInteropTest.java#L43)

    @Test
    @Tag("PASSING")
    @Order(1)
    public void verifyInstantAndDateHaveSameEpochMilliseconds() {

        // DONE: Replace the Instant.now() with an instant from classicDate.
        //  Use a Date API that converts Date instances into Instant instances.
        //  Check: java.util.Date.toInstant()
        Instant instant = classicDate.toInstant();

        // DONE: Replace the "null" below to get milliseconds from epoch from the Instant
        //  Use an Instant API which converts it into milliseconds
        //  Check: java.time.Instant.toEpochMilli()
        assertEquals(Long.valueOf(classicDate.getTime()),
                instant.toEpochMilli(),
                "Date and Instant milliseconds should be equal");
    }

Assertions

Assertions are how testing is conducted. Several types of assertions exist for testing. Some examples include:

  • assertTrue / assertFalse
  • assertEquals / assertNotEquals / assertSame / assertNotSame
  • assertNull / assertNotNull
  • assertArrayEquals / assertIterableEquals / assertLinesMatch
  • assertThrows / assertNotThrows
  • assertAll
  • assertTimeout / assertTimeoutPreemptively
  • fail

There are several polymorphs for most of the methods listed above. JUnit5 aggregates all such assertions as static methods in a single factory utility aptly named Assertions (https://junit.org/junit5/docs/5.5.0/api/org/junit/jupiter/api/Assertions.html).

Assertions are mostly unary or binary (There are assertions, with other arities, that are less commonly used)

Unary assertions are usually boolean condition evaluators. assertTrue or assertNull are good examples of unary assertions. The expectation in such cases is built into the actual assertion method name. A second optional parameter for unary assertions is a message that is returned in case of an assertion failure and exists to provide more meaningful readable failure details.

Binary assertions typically have an expected value (a known), an actual value (evaluated) and an optional third parameter of message (in case of the expectation not being met). assertEquals and assertSame are good examples of binary assertions.

Typically, unit tests statically import the assertions required for the given tests in a test class. An example of such an assertion is the assertEquals method as shown below.

Example: Assertion (https://github.com/c-guntur/java-katas/blob/baseline/java-datetime/src/solutions/java/none/cvg/datetime/STest1InstantAndDateInteropTest.java#L56)

    @Test
    @Tag("PASSING")
    @Order(1)
    public void verifyInstantAndDateHaveSameEpochMilliseconds() {

        // DONE: Replace the Instant.now() with an instant from classicDate.
        //  Use a Date API that converts Date instances into Instant instances.
        //  Check: java.util.Date.toInstant()
        Instant instant = classicDate.toInstant();

        // DONE: Replace the "null" below to get milliseconds from epoch from the Instant
        //  Use an Instant API which converts it into milliseconds
        //  Check: java.time.Instant.toEpochMilli()
        assertEquals(Long.valueOf(classicDate.getTime()),
                instant.toEpochMilli(),
                "Date and Instant milliseconds should be equal");
    }

See also: Static Import (https://github.com/c-guntur/java-katas/blob/baseline/java-datetime/src/solutions/java/none/cvg/datetime/STest1InstantAndDateInteropTest.java#L18)

import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Date;
import java.util.TimeZone;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;

import static none.cvg.datetime.LenientAssert.assertAlmostEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

NOTE: Assertion parameter ordering in JUnit 5 is different from the order in prior versions. In my opinion, the current parameter arrangement makes a lot more sense.

Filtering and Categorizing Tests

Tags

Tags are a means to categorize test methods and classes. Tagging also leads to discovery and filtering of tests. Tagging is done by annotating the class or method with an @Tag annotation. More on filtering in another blog of this series.

Example: @Tag (https://github.com/c-guntur/java-katas/blob/baseline/java-datetime/src/solutions/java/none/cvg/datetime/STest1InstantAndDateInteropTest.java#L62)

    @Test
    @Tag("PASSING")
    @Order(1)
    public void verifyInstantAndDateHaveSameEpochMilliseconds() {

        // DONE: Replace the Instant.now() with an instant from classicDate.
        //  Use a Date API that converts Date instances into Instant instances.
        //  Check: java.util.Date.toInstant()
        Instant instant = classicDate.toInstant();

        // DONE: Replace the "null" below to get milliseconds from epoch from the Instant
        //  Use an Instant API which converts it into milliseconds
        //  Check: java.time.Instant.toEpochMilli()
        assertEquals(Long.valueOf(classicDate.getTime()),
                instant.toEpochMilli(),
                "Date and Instant milliseconds should be equal");
    }

Assumptions

Assumptions are conditions that determine if the rest of the test code block should be either evaluated or aborted. Not meeting an assumption will not cause the code block conditioned by it, to fail. It would rather simply abort execution of such a code block. Some assumption methods:

  • assumeTrue / assumeFalse
  • assumeThat

Similar to assertions, assumptions are grouped as static methods, in a factory utility class, Assumptions (https://junit.org/junit5/docs/5.5.0/user-guide/#writing-tests-assumptions).

class TestWithAssumptions {

    @Test
    void testOnlyOnHost123() {
        assumeTrue("host123".equals(System.getenv("HOSTNAME")));
        // remainder of test
    }

    @Test
    void testOnHost123OrAbortWithMessage() {
        assumeTrue("host123".equals(System.getenv("HOSTNAME")),
            () -> "Aborting test: not on host123");
        // remainder of test
    }

}

Typically, unit tests statically import the assumptions required for the given tests in a test class.

There is no current example of an assumption in the code kata.

Ordering Tests

Test execution order

As stated in the previous part of the blog series, I reserve my opinions of ordering the sequence of test executions. It is useful in certain cases, such as code katas. Test ordering requires either one or two steps depending on the type of ordering.

NOTE: If no order is specified for a test class, JUnit 5 looks for instructions from parent class hierarchy and if still none found, will order tests in a deterministic but non-obvious manner.

Instructing JUnit to order tests

An annotation on the test class is needed to instruct JUnit5 to order tests. This annotation is called the @TestMethodOrder. This annotation accepts an attribute of type MethodOrderer. There are three default implementations that exist:

  1. Alphanumeric – uses String::compareTo to order execution of test methods
  2. OrderAnnotation – uses the @Order annotation on each test method to determine order. The Order annotation accepts a int attribute that specifies the ranking.
  3. Random – uses a random order either simply from System.nanoTime() or in combination with a custom seed.

More custom orders can be created by implementing the MethodOrderer interface.

Example: @TestMethodOrder (https://github.com/c-guntur/java-katas/blob/baseline/java-datetime/src/solutions/java/none/cvg/datetime/STest1InstantAndDateInteropTest.java#L31)

/**
 * The tests in this class aim to show interoperability between
 * `java.util.Date` and the newer `java.time.Instant`.
 *
 * @see Instant
 * @see Date
 * @see LenientAssert
 */
@DisplayNameGeneration(DateTimeKataDisplayNames.class)
@DisplayName("Instant And Date Interoperability")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class STest1InstantAndDateInteropTest {

Extra Step (For OrderAnnotation only): Adding an Order via annotations

In addition to the above annotation instructing JUnit to order test methods, an additional annotation is needed if the OrderAnnotation orderer is specified. The @Order annotation accepts an integer that specifies the ascending order of execution.

Example: @Order (https://github.com/c-guntur/java-katas/blob/baseline/java-datetime/src/solutions/java/none/cvg/datetime/STest1InstantAndDateInteropTest.java#L45)

    @Test
    @Tag("PASSING")
    @Order(1)
    public void verifyInstantAndDateHaveSameEpochMilliseconds() {

        // DONE: Replace the Instant.now() with an instant from classicDate.
        //  Use a Date API that converts Date instances into Instant instances.
        //  Check: java.util.Date.toInstant()
        Instant instant = classicDate.toInstant();

        // DONE: Replace the "null" below to get milliseconds from epoch from the Instant
        //  Use an Instant API which converts it into milliseconds
        //  Check: java.time.Instant.toEpochMilli()
        assertEquals(Long.valueOf(classicDate.getTime()),
                instant.toEpochMilli(),
                "Date and Instant milliseconds should be equal");
    }

That’s a wrap of part two of this blog series. The next blog will include customizing tests with DisplayNames and writing a custom DisplayNameGeneration. Happy coding !

Using JUnit5 – Part 1 – An Introduction

java, junit5

Published: 2019-07 (July 2019)
Relevant for: JUnit 5.5.0

JUnit5 Blog Series

Part 1 – Introduction
Part 2 – Test Basics
Part 3 – Display Names
Part 4 – Filtering tests

Code Katas are a great way of teaching programming practices. The effectiveness of a code kata is to “solve” something repeatedly in order to gain a “muscle memory” of sorts on the subject matter.

Nothing stresses repeatability more than unit tests. Code Katas thus, in many cases can be associated with or run via unit tests.

Many of us have been long used to JUnit4 as a formidable unit testing framework. This blog is not going to be a comparison between JUnit4 and JUnit5, but you will notice some differences as italicized text.

Let us explore JUnit5 as it was used for a recent code kata, this is how I learnt using JUnit 5 !

JUnit5 Logo

JUnit5 dependencies

JUnit5 can be added as a single maven dependency:

            <dependency>
                <groupId>org.junit.jupiter</groupId>
                <artifactId>junit-jupiter</artifactId>
                <version>${junit5.version}</version>
            </dependency>

The equivalent gradle dependency can be inferred.

What is JUnit5 and What is Jupiter?

JUnit5 is made of three separate parts:

  1. JUnit5 Platform: Provides a TestEngine and a testing platform for the JVM.
  2. JUnit5 Jupiter: Programming and extension model for JUnit5 tests.
  3. JUnit Legacy: Backward compatibility TestEngine for JUnit 3 and 4.

Read more about this at the JUnit5 User Guide (https://junit.org/junit5/docs/current/user-guide/).

JUnit5 Basics

Base package for JUnit 5 is: org.junit.jupiter. Most unit test annotations are located at: org.junit.jupiter.apipackage (in the junit-jupiter-api module). Methods in JUnit5 Test can be typically grouped into :

  1. Test methods: Methods that are run as unit tests.
  2. Lifecycle methods: Methods that are executed as before or after one or more (or all) test methods.

Basic Annotations

@Test: Identifies a method as a test method. Unlike prior versions, this annotation does not have attributes. #TestMethod

@Disabled: An annotation to ignore running a method marked as @Test. #TestMethod

@BeforeEach: A setup method that is run before execution of each test. #LifecycleMethod

@BeforeAll: A static setup method run once before execution of all tests. #LifecycleMethod

@AfterEach: A teardown method this is run after execution of each test. #LifecycleMethod

@AfterAll: A static teardown method run once after execution of all tests. #LifecycleMethod

Other Annotations

@Tag: A category or grouping annotation. This is very useful specially when filtering which tests should be run via build tools such as maven. Example in another blog in this series.

@DisplayName: A string that can represent the class or method in the JUnit exection results instead of the actual name. Example in another blog in this series.

@DisplayNameGeneration: A class that can generate class and method names based upon conditions. Examples in another blog in this series.

Custom annotations: It is quite simple to create custom annotations and inherit the behavior.

JUnit5 Conditional Control of Test Methods

Operating System Conditions

@EnabledOnOs: Enable a test to run on a specific array of one or more operating systems.

@DisabledOnOs: Disable a test to run on a specific array of one or more operating systems.

Java Runtime Environment Conditions

@EnabledOnJre: Enable a test to run on a specific array of one or more Java Runtime Environments.

@DisabledOnJre: Disable a test to run on a specific array of one or more Java Runtime Environments.

System Property Conditions

@EnabledIfSystemProperty: Enable a test to run if a System Property matches the condition attributes.

@DisabledIfSystemProperty: Disable a test to run if a System Property matches the condition attributes.

Ordering Test method execution

JUnit5 allows for ordering test method execution. This causes mixed feelings for me.

My feelings: Ordering methods may lead to some developers building out dependent tests where the result of one test is needed for the next to run or pass. Tests should be independent. That said, it is an incredibly useful a feature when used in code katas where the run of tests may have to follow a certain sequence. In the past, I used to solve this by naming my test methods with some numeral-inclusive prefix and sort the results alphabetically. With great power, comes great responsibility.

@TestMethodOrder: Test methods can be ordered when the Test class is marked with this annotation.

@Order: Each test method can then include an Order annotation that includes a numeric attribute.

These were some of the basic that we covered. The next blog in this series will show examples of how we use these features.