认识自己的无知是认识世界最可靠的方法————互联网中的无名之辈
背景
公司最的海外项目临近上线,国内所有的环境都进行了单元测试,唯有海外环境还没进行,所以公司需要在海外环境进行单元测试。公司使用的是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
观众老爷们,看到这了来个一键三连不过分吧,喜欢的话记得关注哦,后期的踩坑笔记记得来围观啊 😄