Contents


Predictive Cloud Computing for professional golf and tennis, Part 6

Maven, unit and integration testing, and static code analysis

Comments

Content series:

This content is part # of 8 in the series: Predictive Cloud Computing for professional golf and tennis, Part 6

Stay tuned for additional content in this series.

This content is part of the series:Predictive Cloud Computing for professional golf and tennis, Part 6

Stay tuned for additional content in this series.

Several build tools and techniques were utilized throughout the Predictive Cloud Computing (PCC) project that enabled the development team to automatically build and validate changes. The tooling and techniques selected enabled the team to automate the build process and deploy changes with a high degree of confidence they would be functional and supportable. In this article, we discuss the build and testing methods used in PCC. Figure 1 illustrates the PCC build and test architecture.

Figure 1. Build and test architecture
Image of build and test architecture
Image of build and test architecture

Maven build automation

Build automation is the practice of removing time-consuming and error-prone manual steps in a build process and replacing them with automatic and consistent processes that create artifacts from source code. Build automation tools promote testing and propagation of small changes by offloading and automating the build process. PCC uses Maven as its build automation tool. With Maven, developers can check in changes to source control where the code is automatically tested and built into artifacts that can be deployed.

Maven executes builds and tests through the use of XML files that define the build process. Maven is a highly structured build system that enforces uniformity across projects. This enables developers to move between different projects and have a consistent interface to their build system.

The tooling and techniques used in the PCC system enabled the team to automate the build process and deploy changes with a high degree of confidence that the code would be functional and supportable.

Maven is lifecycle-based. A build lifecycle consists of the following high-level steps. If any of these steps fails, the next step will not be executed and an error log will be generated.

  1. Validate. Verifies that all information to perform the build lifecycle is available and syntactically correct.
  2. Generate-sources. Generates any source code necessary for a project. In the PCC project, that step was used to automatically generate some segments of code related to data store access using OpenJPA provider.
  3. Process-sources. Inspects the source code and performs transformation steps on it prior to compilation.
  4. Generate-resources and process-resources. Similar to their corresponding source steps, but act on resources—such as XML files—rather than code.
  5. Compile. Compiles the source code into executable code.
  6. Process-test-sources and process-test-resources. Pre-process test configurations to set up testing frameworks and contexts.
  7. Test-compile. Compiles the tests into executable code.
  8. Test. Executes the tests.
  9. Package. Takes the compiled code and places it into a distributable format, such as a Java Archive (JAR).
  10. Install. Installs the packaged code into a local repository. Once installed, the JAR is suitable for use as a dependency by other related projects.
  11. Deploy. Pushes artifacts to production and development environments to provide PCC operations.

Table 1 lists all the projects that were part of the Maven build process and their respective functions to support PCC.

Table 1. Projects involved in the Maven build process
ProjectFunction
BigEngine Performs the multithreaded job runs for big data, establishes the end points for RESTful services, and has configuration for WebSphere Liberty Profile.
Config Parses application, tennis, golf, and overall tournament configurations.
Factors Feature extractor algorithms from golf and tennis simulations.
Persistence Handles database functions.
RacketStream Extracts sentiment analysis in InfoSphere Streams.
Player Contains information about players.
Shared Contains elements shared between PCC projects.
Shared-Integration Provides integration testing for shared projects.
TwitterAnalysis Performs analysis on tweet volumes.
TwitterExporter Exports Twitter feeds to other InfoSphere Steams processes.
TwitterFeed Reads from Twitter API.
WebLogAnalyzer Analyzes web log content to extract player mentions from content.
Visual System to support proxying of requests from the front end visual to the backend BigEngine System.

The BigEngine project in Figure 2 is the primary job for PCC. This job builds the analytic and decision engine used by PCC to forecast site traffic. The BigEngine-Integration job performs the time-consuming integration testing forBigEngine and is segregated from the BigEngine job to speed up delivery time for small changes. TwitterAnalysis, TwitterExporter, and StreamsLogAggregator jobs are used to create artifacts for deployment within InfoSphere Streams. Additional projects such as the WebLogAnalyzer provide big data functions that support the PCC. These are all Maven-based projects.

Figure 2. The Jenkins Maven-based build jobs for PCC
Image of build jobs
Image of build jobs

Listing 1 contains the Maven configuration directives for the BigEngine project. The directives are contained in a document called a Project Object Model (POM) file. That file contains all the necessary directives to compile, test, and perform static analysis on the BigEngine project in PCC. The POM file contains discrete sections including version, model version, packaging, properties, and build. The version directive defines the current version of the project being built by Maven. The packaging section tells Maven how the resulting artifact should be packaged after compilation and test. The modelVersion informs maven what version of the Maven model is being used. In most cases this should be 4.0.0. The artifactID is name of the packaged product without the file extension. The POM file is a very large contiguous file; for illustrative purposes, it is broken into separate segments below.

Listing 1. BigEngine Maven configuration
<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>
        <artifactId>BigEngine</artifactId>
        <version>2.0.0</version>
        <packaging>war</packaging>

The properties section defines several properties to be used by Maven during the build process. For this particular project, we set the output directory and a build time directive, and by default we skip integration testing for this build. The source and output encodings are also set and used by Maven.

Listing 2. BigEngine Maven properties configuration
        <properties>
                <output.directory>${project.basedir}\WebContent\WEB-INF\classes</output.directory>
                <buildtime>${maven.build.timestamp}</buildtime>
                <skip.integration.tests>true</skip.integration.tests>
                <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
                <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        </properties>

The build section contains the majority of directives in this document and governs how Maven should build this project. The section has several subsections: resources, test-resources, plugins, plugin-management, parent, and dependencies. In addition to the subsections, the build configuration contains directives that direct the build process. The finalName instruction provides a name for Maven to use for the resulting build artifact. This is commonly the same as the artifactID in the higher-level project configuration. The directory is the location for Maven to place the build artifact. The outputDirectory is where Maven will put the results of compilation. The testOutputDirectory is the destination for compiled test source files. This directory is separate so that test files are not packaged with the final artifact.

Listing 3. BigEngine Maven build configuration
        <build>
                <finalName>BigEngine</finalName>
                <directory>target</directory>
                <outputDirectory>${output.directory}</outputDirectory>
                <testOutputDirectory>${output.directory}</testOutputDirectory>
                <sourceDirectory>src/main/java</sourceDirectory>

The resource section defines the location for non-code resources that should be included in the resulting package. The test-resources section instructs Maven on where to locate resources that should be available during the test phase but not packaged with the build artifacts.

Listing 4. BigEngine Maven resources and test resources configuration
                <resources>
                        <resource>
                                <directory>src/main/resources</directory>
                        </resource>
                </resources>
                <testResources>
                        <testResource>
                                <directory>src/test/resources</directory>
                        </testResource>
                </testResources>

The plugins directive contains a list of Maven plugins that are used during the build process. Configuration for each plugin varies. In general, the version of the plugin must always be defined. Each plugin might have some specific configuration directives that can be supplied. At times, we defined which phases of execution utilize the plugin.

Listing 5. BigEngine Maven plugin configuration
                <plugins>
                        <plugin>
                                <artifactId>maven-clean-plugin</artifactId>
                                <version>2.4.1</version>
                                <executions>
                                        <execution>
                                                <id>auto-clean</id>
                                                <phase>initialize</phase>
                                                <goals>
                                                        <goal>clean</goal>
                                                </goals>
                                        </execution>
                                </executions>
                        </plugin>
                        <plugin>
                                <artifactId>maven-compiler-plugin</artifactId>
                                <version>2.3.2</version>
                                <configuration>
                                        <source>1.7</source>
                                        <target>1.7</target>
                                </configuration>
                        </plugin>
                        <plugin>
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-resources-plugin</artifactId>
                                <version>2.6</version>
                                <configuration>
                                        <encoding>UTF-8</encoding>
                                </configuration>
                        </plugin>
                        <plugin>
                                <artifactId>maven-war-plugin</artifactId>
                                <version>2.2</version>
                                <configuration>
                                        <webXml>${output.directory}\web.xml</webXml>
                                        <warSourceDirectory>WebContent</warSourceDirectory>
                                        <archive>
                                                <addMavenDescriptor>false</addMavenDescriptor>
                                                <manifest>
                                                        <addClasspath>true</addClasspath>
                                                </manifest>
                                        </archive>
                                </configuration>
                        </plugin>
                        <plugin>
                                <groupId>org.codehaus.mojo</groupId>
                                <artifactId>templating-maven-plugin</artifactId>
                                <version>1.0-alpha-3</version>
                                <executions>
                                        <execution>
                                                <id>filter-src</id>
                                                <goals>
                                                        <goal>filter-sources</goal>
                                                </goals>
                                                <configuration>
                                                        <sourceDirectory>${basedir}/src/main/java-templates</sourceDirectory>
                                                        <outputDirectory>${project.build.directory}/generated-sources/java-templates</outputDirectory>
                                                </configuration>
                                        </execution>
                                </executions>
                        </plugin>
                        <plugin>
                                <groupId>org.codehaus.mojo</groupId>
                                <artifactId>build-helper-maven-plugin</artifactId>
                                <version>1.9.1</version>
                                <executions>
                                        <!-- States that the plugin's add-test-source goal is executed at generate-test-sources 
                                                phase. -->
                                        <execution>
                                                <id>add-integration-test-sources</id>
                                                <phase>generate-test-sources</phase>
                                                <goals>
                                                        <goal>add-test-source</goal>
                                                </goals>
                                                <configuration>
                                                        <!-- Configures the source directory of integration tests. -->
                                                        <sources>
                                                                <source>src/integration-test/java</source>
                                                        </sources>
                                                </configuration>
                                        </execution>
                                </executions>
                        </plugin>
                        <plugin>
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-surefire-plugin</artifactId>
                                <version>2.18.1</version>
                                <configuration>
                                        <forkCount>4</forkCount>
                                        <reuseForks>true</reuseForks>
                                        <excludes>
                                                <exclude>**/IT*.java</exclude>
                                        </excludes>
                                </configuration>
                        </plugin>
                        <!-- Used for integration tests -->
                        <plugin>
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-failsafe-plugin</artifactId>
                                <version>2.18.1</version>
                                <configuration>
                                        <reportsDirectory>${project.build.directory}/surefire-reports</reportsDirectory>
                                </configuration>
                                <executions>
                                        <!-- States that both integration-test and verify goals of the Failsafe 
                                                Maven plugin are executed. -->
                                        <execution>
                                                <id>integration-tests</id>
                                                <goals>
                                                        <goal>integration-test</goal>
                                                        <goal>verify</goal>
                                                </goals>
                                                <configuration>
                                                        <skipTests>${skip.integration.tests}</skipTests>
                                                </configuration>
                                        </execution>
                                </executions>
                        </plugin>
                </plugins>

The pluginmanagement section is used by Maven to appropriately define where certain plugins should be executed, which is alternatively called lifecycle-mapping.

Listing 6. BigEngine Maven plugin management configuration
                <pluginManagement>
                        <plugins>
                                <!--This plugin's configuration is used to store Eclipse m2e settings 
                                        only. It has no influence on the Maven build itself. -->
                                <plugin>
                                        <groupId>org.eclipse.m2e</groupId>
                                        <artifactId>lifecycle-mapping</artifactId>
                                        <version>1.0.0</version>
                                        <configuration>
                                                <lifecycleMappingMetadata>
                                                        <pluginExecutions>
                                                                <pluginExecution>
                                                                        <pluginExecutionFilter>
                                                                                <groupId>
                                                                                        org.apache.maven.plugins
                                                                                </groupId>
                                                                                <artifactId>
                                                                                        maven-clean-plugin
                                                                                </artifactId>
                                                                                <versionRange>
                                                                                        [2.4.1,)
                                                                                </versionRange>
                                                                                <goals>
                                                                                        <goal>clean</goal>
                                                                                </goals>
                                                                        </pluginExecutionFilter>
                                                                        <action>
                                                                                <ignore></ignore>
                                                                        </action>
                                                                </pluginExecution>
                                                                <pluginExecution>
                                                                        <pluginExecutionFilter>
                                                                                <groupId>
                                                                                        com.google.code.maven-replacer-plugin
                                                                                </groupId>
                                                                                <artifactId>
                                                                                        replacer
                                                                                </artifactId>
                                                                                <versionRange>
                                                                                        [1.5.3,)
                                                                                </versionRange>
                                                                                <goals>
                                                                                        <goal>replace</goal>
                                                                                </goals>
                                                                        </pluginExecutionFilter>
                                                                        <action>
                                                                                <ignore></ignore>
                                                                        </action>
                                                                </pluginExecution>
                                                                <pluginExecution>
                                                                        <pluginExecutionFilter>
                                                                                <groupId>
                                                                                        org.codehaus.mojo
                                                                                </groupId>
                                                                                <artifactId>
                                                                                        templating-maven-plugin
                                                                                </artifactId>
                                                                                <versionRange>
                                                                                        [1.0-alpha-3,)
                                                                                </versionRange>
                                                                                <goals>
                                                                                        <goal>filter-sources</goal>
                                                                                </goals>
                                                                        </pluginExecutionFilter>
                                                                        <action>
                                                                                <ignore></ignore>
                                                                        </action>
                                                                </pluginExecution>
                                                        </pluginExecutions>
                                                </lifecycleMappingMetadata>
                                        </configuration>
                                </plugin>
                        </plugins>
                </pluginManagement>
        </build>

The parent directives instruct Maven that this project has a parent POM and should inherit details from the project. This can be used to share libraries and library versions across projects. The artifactID tells Maven the name of the artifact so that Maven can search for the appropriate project. The version tells Maven which version of the artifactID should be used. The groupId is a Java-style dotted name used to group projects. The relativePath tells Maven where to look for the project relative to the current project.

Listing 7. BigEngine Maven parent configuration
        <parent>
                <groupId>com.ibm.ei.bigdata</groupId>
                <artifactId>Shared</artifactId>
                <version>1</version>
                <relativePath>../Shared</relativePath>
        </parent>

The dependencies section defines all the projects that are used by this project. Each dependency consists of a groupId and an artifactID. If a version for a specific dependency is not specified, the version for that dependency is defined in the parent described above.

Listing 8. BigEngine Maven dependency configuration
        <dependencies>
                <dependency>
                        <groupId>com.fasterxml.jackson.core</groupId>
                        <artifactId>jackson-core</artifactId>
                </dependency>
                <dependency>
                        <groupId>com.fasterxml.jackson.core</groupId>
                        <artifactId>jackson-databind</artifactId>
                </dependency>
                <dependency>
                        <groupId>com.fasterxml.jackson.datatype</groupId>
                        <artifactId>jackson-datatype-joda</artifactId>
                </dependency>
                <dependency>
                        <groupId>com.fasterxml.jackson.module</groupId>
                        <artifactId>jackson-module-mrbean</artifactId>
                </dependency>
                <dependency>
                        <groupId>com.fasterxml.jackson.jaxrs</groupId>
                        <artifactId>jackson-jaxrs-json-provider</artifactId>
                </dependency>
                <dependency>
                        <groupId>com.fasterxml.jackson.core</groupId>
                        <artifactId>jackson-databind</artifactId>
                </dependency>
                <dependency>
                        <groupId>com.fasterxml.jackson.module</groupId>
                        <artifactId>jackson-module-jaxb-annotations</artifactId>
                </dependency>
                <dependency>
                        <groupId>javax.servlet</groupId>
                        <artifactId>javax.servlet-api</artifactId>
                        <version>3.1.0</version>
                </dependency>
                <dependency>
                        <groupId>com.timgroup</groupId>
                        <artifactId>java-statsd-client</artifactId>
                </dependency>
                <dependency>
                        <groupId>joda-time</groupId>
                        <artifactId>joda-time</artifactId>
                </dependency>
                <dependency>
                        <groupId>com.ibm.ei.bigdata</groupId>
                        <artifactId>Persistence</artifactId>
                        <version>2.0.0-SNAPSHOT</version>
                </dependency>
                <dependency>
                        <groupId>com.ibm.ei.bigdata</groupId>
                        <artifactId>Engine</artifactId>
                        <version>2.0.0-SNAPSHOT</version>
                </dependency>
                <dependency>
                        <groupId>com.ibm.ei.bigdata</groupId>
                        <artifactId>Forecast</artifactId>
                        <version>2.0.0-SNAPSHOT</version>
                </dependency>
                <dependency>
                        <groupId>com.ibm.ei.bigdata</groupId>
                        <artifactId>PersistenceLoader</artifactId>
                        <version>2.0.0-SNAPSHOT</version>
                </dependency>
                <dependency>
                        <groupId>com.ibm.ei.bigdata</groupId>
                        <artifactId>PlayerDictionary</artifactId>
                        <version>2.0.0-SNAPSHOT</version>
                </dependency>
                <dependency>
                        <groupId>com.ibm.ei.bigdata</groupId>
                        <artifactId>Social</artifactId>
                        <version>2.0.0-SNAPSHOT</version>
                </dependency>
                <dependency>
                        <groupId>org.quartz-scheduler</groupId>
                        <artifactId>quartz</artifactId>
                        <version>2.1.7</version>
                </dependency>
                <dependency>
                        <groupId>ch.qos.logback</groupId>
                        <artifactId>logback-classic</artifactId>
                </dependency>
                <dependency>
                        <groupId>com.ibm.ei.bigdata</groupId>
                        <artifactId>ScheduleOfPlay</artifactId>
                        <version>2.0.0-SNAPSHOT</version>
                </dependency>
                <dependency>
                        <groupId>org.glassfish.jersey.test-framework</groupId>
                        <artifactId>jersey-test-framework-core</artifactId>
                        <version>2.16</version>
                        <scope>test</scope>
                </dependency>
                <dependency>
                        <groupId>org.glassfish.jersey.test-framework.providers</groupId>
                        <artifactId>jersey-test-framework-provider-inmemory</artifactId>
                        <version>2.16</version>
                        <scope>test</scope>
                </dependency>
                <dependency>
                        <groupId>com.h2database</groupId>
                        <artifactId>h2</artifactId>
                        <version>1.3.171</version>
                        <scope>test</scope>
                </dependency>
                <dependency>
                        <groupId>commons-io</groupId>
                        <artifactId>commons-io</artifactId>
                </dependency>
                <dependency>
                        <groupId>com.ibm.ei.bigdata</groupId>
                        <artifactId>UnitTest</artifactId>
                        <version>2.0.0-SNAPSHOT</version>
                        <scope>test</scope>
                </dependency>
                <dependency>
                        <groupId>com.ibm.ei.bigdata</groupId>
                        <artifactId>Charting</artifactId>
                        <version>2.0.0-SNAPSHOT</version>
                        <scope>test</scope>
                </dependency>
                <dependency>
                        <groupId>org.easymock</groupId>
                        <artifactId>easymock</artifactId>
                        <scope>test</scope>
                </dependency>
                <dependency>
                        <groupId>org.powermock</groupId>
                        <artifactId>powermock-api-easymock</artifactId>
                        <scope>test</scope>
                </dependency>
                <dependency>
                        <groupId>org.apache.openjpa</groupId>
                        <artifactId>openjpa</artifactId>
                </dependency>
                <dependency>
                        <groupId>org.apache.wink</groupId>
                        <artifactId>wink-guice-server</artifactId>
                        <version>1.4</version>
                </dependency>
                <dependency>
                        <groupId>com.ibm.ei.bigdata</groupId>
                        <artifactId>WebCrawler</artifactId>
                        <version>2.0.4-SNAPSHOT</version>
                </dependency>
        </dependencies>

Unit and integration testing

Testing is an important part of software development practices. Unit and integration testing were implemented in the PCC project. Unit tests are quick tests that validate a small segment of functionality. Integration tests may be slower and validate the interaction between components of the system. JUnit was used for both unit and integration testing.

The Apache Maven build system dictates the location and layout of tests in a project. Unit tests are placed in the src/test/java subdirectory of the project. Integration tests are placed in the project's src/integration-test/java subdirectory. Resources to be used during testing are placed in the resources subdirectory of either the test or test-integration directory. Resources are non-source files that may be used by the unit and integration tests. For example, the resources might include xml configuration data. By convention, unit test file names end with Test.java and integration tests start with IT and end with Test.java.

Figure 3. Unit and integration test file layout
Image of test file layout
Image of test file layout

To execute unit tests, invoke Maven with the command line mvn test. To execute integration tests, use the command line mvn integration-test. This will execute all phases up to and including the test and integration test phases of the Maven build process.

For PCC, we used the JUnit framework for unit testing. JUnit test cases are executed like regular Java code with a set of assertions applied to validate that the code under test functions properly. Additionally, we utilized the Hamcrest matchers to enable easily readable tests. Listing 9 shows one such unit test class utilized by PCC. In this unit test, a data store is populated with player information and an XML file is parsed where its contents are placed into objects. Next, the system tests to determine if a cross-lookup can be completed between the contents in the XML file and the data store.

Listing 9. Example unit test
package com.ibm.ei.engine.golf;

import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;

import java.net.URL;

import javax.persistence.EntityManager;

import org.dbunit.dataset.DataSetException;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
import org.junit.Test;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.io.Resources;
import com.ibm.ei.persistence.Player;
import com.ibm.ei.persistence.Site;
import com.ibm.ei.persistence.jpaloaders.Sites;
import com.ibm.ei.zepplin.H2DatabaseTestCase;

public class XMLFeaturedGroupsTest extends H2DatabaseTestCase {

        private final Function<Player, String> playerIDFunction = new Function<Player, String>() {

                @Override
                public String apply(Player input) {
                        return input.getPlayerId();
                }

        };

        @Test
        public void testFromInputSupplier() throws Exception {

                EntityManager manager = getEntityManager();

                Site site = Sites.loadSite(manager, "masters", 2013);

                URL group = Resources.getResource("featuredGroup.xml");
                ImmutableList<FeaturedGroup> groups = XMLFeaturedGroups
                                .fromInputSupplier(manager, site, Resources.asByteSource(group));

                FeaturedGroup group1 = groups.get(0);
                FeaturedGroup group2 = groups.get(1);

                assertThat(group1, notNullValue());
                assertThat(group2, notNullValue());

                assertThat(group1.getPlayers(), notNullValue());
                assertThat(group2.getPlayers(), notNullValue());
                assertThat(group1.getPlayers().size(), is(3));
                assertThat(group2.getPlayers().size(), is(3));
                assertThat(Lists.transform(group1.getPlayers(), playerIDFunction),
                                containsInAnyOrder("20396", "1810", "10885"));
                assertThat(Lists.transform(group2.getPlayers(), playerIDFunction),
                                containsInAnyOrder("24357", "8793", "20229"));

        }

        @Override
        protected Optional<IDataSet> getDataSet() throws DataSetException {
                return Optional.of((IDataSet)new FlatXmlDataSetBuilder().build(Resources.getResource("players.xml")));
        }
}

A single atomic test has a lot of complexity. An inspection of the segments may yield insight into the testing methodology used by PCC. The package name and the imports are defined in the top section of the test file. The package name is important and should map the package of the class you are testing. This makes package, private, or default methods available for testing where necessary. The imports define all of the classes necessary to test the functionality and are themselves broken into sections by Java namespace. The Hamcrest static imports are used to make tests easier to read. The Dbunit imports are used to assist in persistence testing. The google.common libraries are used to enable functional programming in Java. The com.ibm classes are the classes under test with some test helper classes.

Listing 10. Example unit test top section
package com.ibm.ei.engine.golf;

import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;

import java.net.URL;

import javax.persistence.EntityManager;

import org.dbunit.dataset.DataSetException;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
import org.junit.Test;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.io.Resources;
import com.ibm.ei.persistence.Player;
import com.ibm.ei.persistence.Site;
import com.ibm.ei.persistence.jpaloaders.Sites;
import com.ibm.ei.zepplin.H2DatabaseTestCase;

The next section defines the Test class. Note that it ends in Test, which is customary. This particular test extends an H2DatabaseTest case that provides common methods of loading persistence tests and enabling access to those tests in PCC. The playerIDFunction is a functional programming method used to transform a collection from one format to another. This is utilized in a different section of the Test class.

Listing 11. Example unit test top section
public class XMLFeaturedGroupsTest extends H2DatabaseTestCase {

        private final Function<Player, String> playerIDFunction = new Function<Player, String>() {

                @Override
                public String apply(Player input) {
                        return input.getPlayerId();
                }

        };

The next section defines the test to be executed during unit test. The @Test annotation signals to the JUnit test framework that this method should be executed as a test. The method then requests an EntityManager from the parent H2DatabaseTest case class. The class provides access to persistence methods. The method then uses the Resources class from Google Guava to load the XML. Once that is complete, the method is ready to execute the tests. First, the test checks that neither group loaded from the XML file is null. Next, that same check is performed to validate that each group contains players and number of players in each loaded group is 3. After the validation, a compound test is performed. The lists of group 1 and group 2 players are transformed using the function described above to transform the player object to just the playerID for each player. After that is completed, the unit testing framework validates that the list of player IDs equals the expected set. The containsInAnyOrder method is used to not require the players to be in any specific order.

Listing 12. Test code
        @Test
        public void testFromInputSupplier() throws Exception {

                EntityManager manager = getEntityManager();

                Site site = Sites.loadSite(manager, "masters", 2013);

                URL group = Resources.getResource("featuredGroup.xml");
                ImmutableList<FeaturedGroup> groups = XMLFeaturedGroups
                                .fromInputSupplier(manager, site, Resources.asByteSource(group));

                FeaturedGroup group1 = groups.get(0);
                FeaturedGroup group2 = groups.get(1);

                assertThat(group1, notNullValue());
                assertThat(group2, notNullValue());

                assertThat(group1.getPlayers(), notNullValue());
                assertThat(group2.getPlayers(), notNullValue());
                assertThat(group1.getPlayers().size(), is(3));
                assertThat(group2.getPlayers().size(), is(3));
                assertThat(Lists.transform(group1.getPlayers(), playerIDFunction),
                                containsInAnyOrder("20396", "1810", "10885"));
                assertThat(Lists.transform(group2.getPlayers(), playerIDFunction),
                                containsInAnyOrder("24357", "8793", "20229"));

        }

The next section, depicted in Listing 13, is used by the parent test class to load data into a data store for access by the persistence framework. The code specifies an XML file that is the dump of a database table. The table is then loaded into an in-memory database for quick unit testing.

Listing 13. Data set loading
                @Override
        protected Optional<IDataSet> getDataSet() throws DataSetException {
                return Optional.of((IDataSet)new FlatXmlDataSetBuilder().build(Resources.getResource("players.xml")));
        }

Integration testing is performed just as Unit tests are. In PCC, integrations tests may run longer and may test integrations between multiple components.

Often when testing complex systems, it is a requirement to mock components that you cannot easily test. Such components can include things like network access, file access, or some other resource that is not easy to recreate in a testing environment. Mocking is the process of taking the interface to those components and defining a pattern by which that interface will respond. The mocked interface or object is then presented to the object under test. The test can proceed without requiring resources that may not be available while testing. Predictive Cloud Computing used the EasyMock framework for test mocks. The code section below demonstrates one of the test mocks for inspecting pairing for a golf tournament. The object under test requires a complex site object that normally requires access to a database. To facilitate testing, that object is mocked instead. The mock setup involves creating a new mock with the specific class to mock, and then listing the expected method calls and what to return to when they are called. This object is then passed to the schedule parser, which enables testing of that module without database access.

Listing 14. Mocks
       @Test
        public void testParseFile() throws Exception {
                final File file = new File("src/test/resources/mastersreplay/2014/pairings1.json");

                final FileReader reader = new FileReader(file);
                final Reader fixedReader = PairingFileParser.fixJSONFormatting(reader);

                final DateTime nowMidnight = DateTime.now().withMillisOfDay(0);

                final Site site = EasyMock.createMock(Site.class);
                expect(site.getName()).andReturn("masters");
                expect(site.getYear()).andReturn(2013);
                expect(site.getTimezoneOffset()).andStubReturn(0);
                expect(site.getTournamentScheduleStart()).andReturn(nowMidnight).anyTimes();
                replay(site);

                final ImmutableList<PlayerTeeTime> result = PairingFileParser.parseSchedule(fixedReader, site);
                assertEquals(96, result.size());

                final DateTime expectedTime = nowMidnight.withZone(DateTimeZone.forOffsetHours(-4)).withHourOfDay(7).withMinuteOfHour(50);

                assertEquals(expectedTime, result.get(0).getTeeTime());
                assertEquals(expectedTime.getMillis(), result.get(0).getTeeTime().getMillis());
                assertEquals(expectedTime.getMillis(), result.get(1).getTeeTime().getMillis());
                assertEquals(expectedTime.getMillis(), result.get(2).getTeeTime().getMillis());
        }

Static analysis testing

Static analysis testing is different from unit and integration tests because it is not executed. Instead, the source code is inspected to look for anti patterns and other types of mistakes. Static analysis testing is useful for helping to maintain a code base and validate that the code being delivered meets team or industry standards.

The PCC project utilized SonarQube for static analysis testing. SonarQube was selected because of its excellent graphical interface and ability to help the team identify areas to improve the code.

The user interface for SonarQube is highly configurable. PCC configures the home page for SonarQube to contain three sections. The section on the upper left shows a timeline for the number of lines of code, test coverage, and open issues. The right-hand side shows all the projects of PCC with some simple statistics such as lines of code and technical debt. Technical debt is measured in the approximate amount of time it would take to fix all the defects detected by static analysis. The bottom left side depicts a heat map where the size of each square represents the lines of code and the color tone shows the lines of test coverage. Developers can thereby visualize which projects need the most improvement.

Figure 4. SonarQube user interface
Image of SonarQube user interface
Image of SonarQube user interface

If a project is selected from the right-hand side of Figure 5, the user is provided an overview of the static analysis for that particular project. PCC was configured to show the following details about a selected project: debt, duplications, complexity over time, current complexity, size over time, lines of code, and unit test details. Each of the sections of the project page was selected by the PCC team to provide meaningful insight into project status.

Figure 5. SonarQube project interface
Image of SonarQube project interface
Image of SonarQube project interface

The technical debt section of the user interface provides a high-level summary of the amount of time it would take to fix all known issues in the project as well as the total number of issues and severity of each issue. The red issues (topmost three) have a higher severity than those colored green (bottom two).

Figure 6. SonarQube debt interface
Image of SonarQube debt interface
Image of SonarQube debt interface

Code duplications can result in code that is hard to maintain. In general, if code is duplicated, it should be extracted out to a method. If code is copied and pasted throughout a code base, it can be difficult to maintain because a change to code in one location can easily be forgotten in the other. This can result in defects. SonarQube provides a view that gives a quick look at how much code is duplicated in a project. The visual provides several metrics about duplicates: a percentage, the number of lines, the blocks, and the number of files that contain duplications.

Figure 7. SonarQube duplications interface
Image of SonarQube duplications interface
Image of SonarQube duplications interface

Complex code is hard to understand. Often it is not clear how complex a piece of code is until another developer has to maintain that code and understand what it does. Comments can be helpful, but static analysis can help measure code complexity and identify which areas in the code need improvement. The code duplication detection configured for PCC indicates the complexity of each class, file, and function. In a project that has a lot of contributors, attempting to reduce complexity metrics over time can be worth the effort.

Figure 8. SonarQube code complexity
Image showing SonarQube code complexity
Image showing SonarQube code complexity

Much like complexity, if a single project contains too much code, it may be hard to understand. When a project begins to grow in size, a developer should think about re-factoring it into multiple projects. Additionally, the detection can be useful to understand what percentage of the lines of code are covered by a test. In PCC, SonarQube was configured to show the lines of code, number of classes, and test coverage percentage of those classes.

Figure 9. SonarQube code size
Image showing SonarQube code size
Image showing SonarQube code size

The code size over time view assists developers in spotting code trends. SonarQube provides another view that gives more insight into the current code size metrics of a project. The code size detail view was configured to show the total lines of code in the project, the number of files, directories, functions, classes, statements, and accessors. This information can indicate to a developer if a project is getting too big to be maintained and should be broken into multiple projects.

Figure 10. SonarQube code size detail
Image showing detail of SonarQube code size
Image showing detail of SonarQube code size

As described earlier, testing is a very important practice for developing software, and PCC is no exception. SonarQube provides good visual insights into the current state of testing for a project. A developer can quickly see what percent of the code has unit test coverage and the percentage of conditions in the code that have coverage. The developer can also easily see how successful a test has been with metrics such as failure count, error lists, and total test time.

Figure 11. SonarQube test coverage and performance
Image showing SonarQube code size detail
Image showing SonarQube code size detail

Conclusion

The combination of build automation, unit and integration testing, and static analysis increased the reliability of PCC while reducing the time required to debug code in production. Testing and build automation can be easily overlooked when starting a project, yet it is important to have them at the beginning. If a project lacks good test hygiene at the start, introducing it later in the project might be made overwhelming by the technical debt. Starting a project with build and test automation will likely lead to more maintainable code.

In part 7 of this series, we will introduce the implementation of IBM® DB2® and a time series database, Graphite, within the overall PCC architecture.


Downloadable resources


Related topics


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Cloud computing, Data and analytics
ArticleID=1035515
ArticleTitle=Predictive Cloud Computing for professional golf and tennis, Part 6: Maven, unit and integration testing, and static code analysis
publish-date=08052016