全网最靠谱的SpringBoot+TestNG+Maven打包成jar运行单元测试

  • Post author:
  • Post category:其他

认识自己的无知是认识世界最可靠的方法————互联网中的无名之辈

背景

公司最的海外项目临近上线,国内所有的环境都进行了单元测试,唯有海外环境还没进行,所以公司需要在海外环境进行单元测试。公司使用的是SpringBoot+TestNG写的单元测试,单元测试中去调用了其他微服务的Service。在国内环境只需要在本地或者jenkins上执行测试命令,比如通过mvn test 或者在IDEA里面直接执行对应的testng.mxl文件。

由于海外环境网络的限制和服务的限制公司需要将SpringBoot+TestNG打成jar包的形式去执行。不巧这个任务分配到我这了,我一听,哦,就是打个jar包跑测试啊,那岂不是洒洒水。

在这里插入图片描述

初探

坑1

我之前遇见的打包都是把单元测试排除的,现在的需求是将测试打包进去于是我就去网上找了找例子。嗯很快就找到了下面这篇博客.。

https://blog.csdn.net/qq_15290529/article/details/78989234

其实写的挺好的,我照做了之后发现问题来了,打包执行的时候需要有程序入口,现在的项目的有一个标准的SpringBoot Application入口,我想都没想就把它设置成了入口,不一会儿我的jar包就完成了。

后面我怀着不屑运行了java -jar 启动了程序,不出意外Application正常启动,等了等,嗯…嗯?不对劲,猛地意识到这个程序的入口有问题,入口应该设置成一个能启动所有TestNG测试的方法。

坑2

于是我开始思考怎么在Application启动的时候去执行单元测试呢?
不一会儿,我看了下面这篇博客之后有思路了.

http://www.voidcn.com/article/p-yehsfrxe-btn.html

我写了一个TestStartService 继承了ApplicationRunner,重写了run方法,我想的是这样能够在启动Application的时候执行单元测试。

@Component
@Order(value = 1)
public class TestStartService  implements ApplicationRunner {


    @Autowired
    TestNG testNG;

    @Override
    public void run(ApplicationArguments applicationArguments) throws Exception {
        List<String> suites = new ArrayList<String>();
        suites.add("src/main/resources/testng.xml");
        testNG.setTestSuites(suites);
        testNG.run();
    }
}

测试立马就报错了

Error starting ApplicationContext.

通过DEBUG发现确实是在容器启动的时候去加载了testng.xml,也去找了对应测试类,因为我们的测试类注入了其他系统的Service,但是这样的方式去执行测试的时候不会去加载上下文环境。我又去网上查了一波资料,看到 stackoverflow 上有个一样的需求,但是没人回答,我越来越觉得这个任务是个坑了。

https://stackoverflow.com/questions/63934534/how-to-create-fat-jar-with-tests-sources-and-all-dependencies-on-spring-boot-app

后来跑去读了SpringBoot官方文档、读了TestNG API文档、写了几个自己猜想的idea ,但是任还是失败告终。值得一提的是有一个类在我看到所以资料里面出现率非常高,我先留了个印象,先看看这个类,埋个伏笔。

  org.testng.TestNG

最后的挣扎

这已经是第二天的事情了,昨日几乎找遍了全网有关TestNG的资料,尝试了我能想到的所有方法。距离任务结束就只有8小时左右了,作为刚刚入职的应届生(而且还没过试用期),我真的慌了,甚至都联想了卷包袱走人的场面。

现在该求的人都求了,该找的额资料都找了,唯有源码还没看,于是我就沉下心思看来看源码,因为上面提到的类出现频率最高,所以我就直接从这个类进去开始阅读源码。

  org.testng.TestNG

看到下面这段代码我顿时沸腾了


    public static void main(String[] argv) {
        TestNG testng = privateMain(argv, (ITestListener)null);
        System.exit(testng.getStatus());
    }
    @Deprecated
    public void configure(Map cmdLineArgs) {
        CommandLineArgs result = new CommandLineArgs();
        Integer verbose = (Integer)cmdLineArgs.get("-log");
        if (null != verbose) {
            result.verbose = verbose;
        }

        result.outputDirectory = (String)cmdLineArgs.get("-d");
        String testClasses = (String)cmdLineArgs.get("-testclass");
        if (null != testClasses) {
            result.testClass = testClasses;
        }

        String testNames = (String)cmdLineArgs.get("-testnames");
        if (testNames != null) {
            result.testNames = testNames;
        }

        String useDefaultListeners = (String)cmdLineArgs.get("-usedefaultlisteners");
        if (null != useDefaultListeners) {
            result.useDefaultListeners = useDefaultListeners;
        }

        result.groups = (String)cmdLineArgs.get("-groups");
        result.excludedGroups = (String)cmdLineArgs.get("-excludegroups");
        result.testJar = (String)cmdLineArgs.get("-testjar");
        result.xmlPathInJar = (String)cmdLineArgs.get("-xmlpathinjar");
        result.junit = (Boolean)cmdLineArgs.get("-junit");
        result.mixed = (Boolean)cmdLineArgs.get("-mixed");
        result.master = (String)cmdLineArgs.get("-master");
        result.slave = (String)cmdLineArgs.get("-slave");
        result.skipFailedInvocationCounts = (Boolean)cmdLineArgs.get("-skipfailedinvocationcounts");
        String parallelMode = (String)cmdLineArgs.get("-parallel");
        if (parallelMode != null) {
            result.parallelMode = parallelMode;
        }

        String threadCount = (String)cmdLineArgs.get("-threadcount");
        if (threadCount != null) {
            result.threadCount = Integer.parseInt(threadCount);
        }

        Integer dptc = (Integer)cmdLineArgs.get("-dataproviderthreadcount");
        if (dptc != null) {
            result.dataProviderThreadCount = dptc;
        }

        String defaultSuiteName = (String)cmdLineArgs.get("-suitename");
        if (defaultSuiteName != null) {
            result.suiteName = defaultSuiteName;
        }

        String defaultTestName = (String)cmdLineArgs.get("-testname");
        if (defaultTestName != null) {
            result.testName = defaultTestName;
        }

        Object listeners = cmdLineArgs.get("-listener");
        if (listeners instanceof List) {
            result.listener = Utils.joinClasses((List)listeners, ",");
        } else {
            result.listener = (String)listeners;
        }

        String ms = (String)cmdLineArgs.get("-methodselectors");
        if (null != ms) {
            result.methodSelectors = ms;
        }

        String objectFactory = (String)cmdLineArgs.get("-objectfactory");
        if (null != objectFactory) {
            result.objectFactory = objectFactory;
        }

        String runnerFactory = (String)cmdLineArgs.get("-testrunfactory");
        if (null != runnerFactory) {
            result.testRunnerFactory = runnerFactory;
        }

        String reporterConfigs = (String)cmdLineArgs.get("-reporter");
        if (reporterConfigs != null) {
            result.reporter = reporterConfigs;
        }

        String failurePolicy = (String)cmdLineArgs.get("-configfailurepolicy");
        if (failurePolicy != null) {
            result.configFailurePolicy = failurePolicy;
        }

        this.configure(result);
    }

看见main方法,再结合出现的configure,我就联想到了shell 命令,通过 -【options】 xxx 就能设置对应的参数,我单独起了个程序写了下面这段代码。

public class TestNGMain {
    public static void main(String[] args) {
        org.testng.TestNG.main(args);
    }
}

将程序打成jar,看见下面的内容,我就感觉我距离出坑不远了

You need to specify at least one testng.xml, one class or one method
Usage: <main class> [options] The XML suite files to run
  Options:
    -configfailurepolicy               Configuration failure policy (skip or
                                       continue)
    -d                                 Output directory
    -dataproviderthreadcount           Number of threads to use when running
                                       data providers
    -excludegroups                     Comma-separated list of group names to
                                       exclude
    -groups                            Comma-separated list of group names to be
                                       run
    -junit                             JUnit mode
                                       Default: false
    -listener                          List of .class files or list of class
                                       names implementing ITestListener or
                                       ISuiteListener
    -methods                           Comma separated of test methods
                                       Default: []
    -methodselectors                   List of .class files or list of class
                                       names implementing IMethodSelector
    -mixed                             Mixed mode - autodetect the type of
                                       current test and run it with appropriate runner
                                       Default: false
    -objectfactory                     List of .class files or list of class
                                       names implementing ITestRunnerFactory
    -parallel                          Parallel mode (methods, tests or classes)
    -port                              The port
    -reporter                          Extended configuration for custom report
                                       listener
    -suitename                         Default name of test suite, if not
                                       specified in suite definition file or source code
    -suitethreadpoolsize               Size of the thread pool to use to run
                                       suites
                                       Default: 1
    -testclass                         The list of test classes
    -testjar                           A jar file containing the tests
    -testname                          Default name of test, if not specified in
                                       suitedefinition file or source code
    -testnames                         The list of test names to run
    -testrunfactory, -testRunFactory   The factory used to create tests
    -threadcount                       Number of threads to use when running
                                       tests in parallel
    -usedefaultlisteners               Whether to use the default listeners
                                       Default: true
    -log, -verbose                     Level of verbosity
    -xmlpathinjar                      The full path to the xml file inside the
                                       jar file (only valid if -testjar was
                                       specified)
                                       Default: testng.xml

于是我将之前的maven打包配置改成了上面这段代码。立马大了jar包
根据上面的命令提示我执行了下面的代码

java -jar 包名.jar -testjar 包名.jar

nice 我出坑了,ohhhhhhhhhhhhhhhhhhhhhhhhhh。

因为默认的suite就是testng.xml 和我的情况符合,所以我就没去设置 -xmlpathinjar 参数,如果情况不同的需要进行设置,这个是最重要的!

关键点

大概的看看我的pom配置和代码,因为上问的博客其实还是存在点差异的,这里我该踩的坑都踩了放心食用!

<?xml version="1.0" encoding="UTF-8"?>
<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">
   
    <packaging>jar</packaging>

    <properties>
    <!-- 上文提到的TestNG入口函数-->
        <start-class>org.kuohai.TestNGMain</start-class>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.uncommons</groupId>
            <artifactId>reportng</artifactId>
            <version>1.1.4</version>
        </dependency>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>6.8</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>包名</finalName>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <!--包含文件夹以及子文件夹下所有资源-->
                    <include>**/*.*</include>
                </includes>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>2.16</version>
                <executions>
                    <execution>
                        <id>default-integration-test</id>
                        <goals>
                            <goal>integration-test</goal>
                        </goals>
                        <configuration>
                            <excludes>
                                <exclude>none</exclude>
                            </excludes>
                            <includes>
                                <include>**/*Test.java</include>
                            </includes>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.17</version>
                <configuration>
                    <suiteXmlFiles>
                        <suiteXmlFile>src/main/resources/testng.xml</suiteXmlFile>
                    </suiteXmlFiles>

                    <properties>
                        <property>
                            <name>usedefaultlisteners</name>
                            <value>false</value>
                        </property>
                        <property>
                            <name>listener</name>
                            <value>org.uncommons.reportng.HTMLReporter,
                                org.uncommons.reportng.JUnitXMLReporter
                            </value>
                        </property>
                    </properties>      
                    <forkMode>always</forkMode>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>META-INF/spring.handlers</resource>
                                </transformer>
                                <transformer
                                        implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
                                    <resource>META-INF/spring.factories</resource>
                                </transformer>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>META-INF/spring.schemas</resource>
                                </transformer>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>${start-class}</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
package org.kuohai;

public class TestNGMain {
    public static void main(String[] args) {
        org.testng.TestNG.main(args);
    }
}

java -jar 包名.jar  -testjar 包名.jar

观众老爷们,看到这了来个一键三连不过分吧,喜欢的话记得关注哦,后期的踩坑笔记记得来围观啊 😄


版权声明:本文为qq_37339399原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。