How to include test classes in Jar created by mave

2019-01-17 06:31发布

问题:

I'm trying to package my test classes in to an executable jar with dependencies using Maven, but I'm struggling to get this right.

This is my pom.xml so far:

<project>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.c0deattack</groupId>
    <artifactId>executable-tests</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>

    <name>executable-tests</name>

    <dependencies>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-java</artifactId>
            <version>1.0.0</version>
        </dependency>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-junit</artifactId>
            <version>1.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>2.21.0</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.4</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>test-jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>1.6</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <finalName>cucumber-tests</finalName>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>cucumber.cli.Main</mainClass>
                                </transformer>
                            </transformers>
                            <artifactSet>
                                <includes>
                                    <include>info.cukes:*</include>
                                </includes>
                            </artifactSet>
                        </configuration>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>com.c0deattack</groupId>
                        <artifactId>executable-tests</artifactId>
                        <version>1.0</version>
                        <type>test-jar</type>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
</project>

When I execute mvn clean package the build creates 3 jars:

  • executable-tests-1.0.jar // built by the mvn package phase
  • executable-tests-1.0-teststjar // built by the jar-plugin
  • cucumber-tests.jar // build by the shade-plugin

Cucumber-tests.jar contains the info.cuke dependencies but it doesn't contain the executable-tests-1.0-tests.jar.

I've done all sorts of things to try and have the test classes included but nothing has worked, what am I missing?

Edit: I've pushed my example project to GitHub if any one fancies playing around with it :) https://github.com/C0deAttack/ExecutableTests

回答1:

Answering late, but got a working solution which may help future visitors of this question.

I succeed on having a fat jar using only one Maven plugin and including:

  • The test classes
  • The application code classes
  • External dependencies required by application code (in compile scope)
  • External dependencies required by the test code (in test scope)

Which basically means a fat jar with the addition of test classes (and their dependencies). The Maven Jar Plugin and its test-jar goal would not suit this need. The Maven Shade Plugin and its shadeTestJar option would not help neither.

So, how to create in Maven a fat jar with test classes and external dependencies?

The Maven Assembly Plugin is a perfect candidate in this case.

Here is a minimal POM sample:

<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.sample</groupId>
    <artifactId>sample-project</artifactId>
    <version>1.0-SNAPSHOT</version>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <descriptor>src/main/assembly/assembly.xml</descriptor>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                        <configuration>
                            <archive>
                                <manifest>
                                    <mainClass>com.sample.TestMain</mainClass>
                                </manifest>
                            </archive>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

The configuration above is also setting a main class defined in test classes (for a quick check whether it works or not, down on the answer). But that's not enough.

You also need to create a descriptor file, in the src\main\assembly folder an assembly.xml file with the following content:

<assembly
    xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
    <id>fat-tests</id>
    <formats>
        <format>jar</format>
    </formats>
    <includeBaseDirectory>false</includeBaseDirectory>
    <dependencySets>
        <dependencySet>
            <outputDirectory>/</outputDirectory>
            <useProjectArtifact>true</useProjectArtifact>
            <unpack>true</unpack>
            <scope>test</scope>
        </dependencySet>
    </dependencySets>
    <fileSets>
        <fileSet>
            <directory>${project.build.directory}/test-classes</directory>
            <outputDirectory>/</outputDirectory>
            <includes>
                <include>**/*.class</include>
            </includes>
            <useDefaultExcludes>true</useDefaultExcludes>
        </fileSet>
    </fileSets>
</assembly>

The configuration above is:

  • setting external dependencies to be taken from the test scope (which will also take the compile scope as well)
  • setting a fileset to include compiled test classes as part of the packaged fat jar
  • setting a final jar with fat-tests classifier (hence your final file will be something like sampleproject-1.0-SNAPSHOT-fat-tests.jar).

How can we test it?

Build the jar:

mvn clean compile test-compile assembly:single

Running from the target folder:

java -jar sampleproject-1.0-SNAPSHOT-fat-tests.jar

We would get the main (from tests classes) executed. The main may invoke others tests or application code (and hence require external dependencies, in both compile or test scope) and everything would work fine.


From such a main, you could also invoke all of your test cases as following:

  • Create a JUni test suite
  • Add to the test suite the concerned tests
  • Invoke the test suite from your plain Java main

Example of test suite:

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

@RunWith(Suite.class)
@SuiteClasses({ AppTest.class })
public class AllTests {

}

Note: in this case the test suite is only concerning the AppTest sample test.

Then you could have a main class as following:

import org.junit.internal.TextListener;
import org.junit.runner.JUnitCore;

public class MainAppTest {

    public static void main(String[] args) {
        System.out.println("Running tests!");

        JUnitCore engine = new JUnitCore();
        engine.addListener(new TextListener(System.out)); // required to print reports
        engine.run(AllTests.class);
    }
}

The main above would then execute the test suite which will in chain execute all of the configured tests.



回答2:

I've solved my problem a different way; using two other plugins.

First I use the maven-dependency-plugin to get the dependencies and unpack them locally, then I use the maven-jar-plugin to create a test-jar and include the classes from the unpacked dependencies.