Maven基本使用(中)
Maven基本使用(中)
6、Maven生命周期和插件
6.1 生命周期
-
clean
: 删除由以前的生成的所有文件。 -
validate
:验证项目是否正确,以及所有必要的信息是否可用。 -
compile
:编译项目的源代码。 -
test
:使用合适的单元测试框架测试编译的源代码,这些测试不应该要求打包或部署代码。 -
package
:将编译后的代码打包为可分发的格式,例如JAR。 -
vertify
:验证-对集成测试的结果进行任何检查,以确保符合质量标准。 -
install
:将包安装到本地存储库中,用作本地其他项目的依赖项。 -
site
: 生成项目的现场文档 。 -
deploy
:在构建环境中完成,将最终包复制到远程存储库,以便与其他开发人员和项目共享。
mvn执行阶段的命令格式是:
# 多个阶段的名称之间用空格隔开
$ mvn 阶段1 [阶段2] [阶段n]
6.2 三套生命周期
Maven 拥有三套相互独立的生命周期,它们分别为 clean、default 和 site。clean 生命周期的目的是清理项
目,default 生命周期的目的是构建项目,而 site 生命周期的目的是建立项目站点。
6.2.1 clean生命周期
clean 声明周期的目的是清理项目,它包含是三个阶段:
生命周期阶段 | 描述 |
---|---|
pre-clean | 执行一些需要在clean之前完成的工作 |
clean | 移除所有上一次构建生成的文件 |
post-clean | 执行一些需要在clean之后立刻完成的工作 |
6.2.2 default生命周期
default声明周期定义了真正构建时所需要执行的所有步骤,它是生命周期中最核心的部分,其包含的阶段如下:
生命周期阶段 | 描述 |
---|---|
validate | 校验:校验项目是否正确并且所有必要的信息可以完成项目的构建过程。 |
initialize | 初始化:初始化构建状态,比如设置属性值。 |
generate-sources | 生成源代码:生成包含在编译阶段中的任何源代码。 |
process-sources | 处理源代码:处理源代码,比如说,过滤任意值。 |
generate-resources | 生成资源文件:生成将会包含在项目包中的资源文件。 |
process-resources | 编译:复制和处理资源到目标目录,为打包阶段最好准备。 |
compile | 处理类文件:编译项目的源代码。 |
process-classes | 处理类文件:处理编译生成的文件,比如说对Java class文件做字节码改善优化。 |
generate-test-sources | 生成测试源代码:生成包含在编译阶段中的任何测试源代码。 |
process-test-sources | 处理测试源代码:处理测试源代码,比如说,过滤任意值。 |
generate-test-resources | 生成测试源文件:为测试创建资源文件。 |
process-test-resources | 处理测试源文件:复制和处理测试资源到目标目录。 |
test-compile | 编译测试源码:编译测试源代码到测试目标目录. |
process-test-classes | 处理测试类文件:处理测试源码编译生成的文件。 |
test | 测试:使用合适的单元测试框架运行测试(Juint是其中之一)。 |
prepare-package | 准备打包:在实际打包之前,执行任何的必要的操作为打包做准备。 |
package | 打包:将编译后的代码打包成可分发格式的文件,比如JAR、WAR或者EAR文件。 |
pre-integration-test | 集成测试前:在执行集成测试前进行必要的动作。比如说,搭建需要的环境。 |
integration-test | 集成测试:处理和部署项目到可以运行集成测试环境中。 |
post-integration-test | 集成测试后:在执行集成测试完成后进行必要的动作。比如说,清理集成测试环境。 |
verify | 验证:运行任意的检查来验证项目包有效且达到质量标准。 |
install | 安装:安装项目包到本地仓库,这样项目包可以用作其他本地项目的依赖。 |
deploy | 部署:将最终的项目包复制到远程仓库中与其他开发者和项目共享。 |
6.2.3 site生命周期
site生命周期的目的是建立和发布项目站点,Maven 能够基于POM所包含的信息,自动生成一个友好的站点,方
便团队交流和发布项目信息。该声明周期包含如下阶段:
阶段 | 描述 |
---|---|
pre-site | 执行一些需要在生成站点文档之前完成的工作 |
site | 生成项目的站点文档 |
post-site | 执行一些需要在生成站点文档之后完成的工作,并且为部署做准备 |
site-deploy | 将生成的站点文档部署到特定的服务器上 |
6.3 默认插件的生命周期
为了完成某个具体的构建任务,Maven 生命周期的阶段需要和 Maven 插件的目标相互绑定。
Maven 默认为一些核心的生命周期阶段绑定了插件目标,当用户调用这些阶段时,对应的插件目标就会自动执行
相应的任务。
表中,default 生命周期中只列出了绑定了插件目标的阶段,它还有很多其他的阶段,但这些阶段默认没有绑定任
何插件目标,因此它们也没有任何实际的行为。
生命周期阶段 | 插件:目标 | 执行任务 |
---|---|---|
clean | maven-clean-plugin:clean | 清理Maven的输出目录 |
site | maven-site-plugin:site | 生成项目站点 |
site-deploy | maven-site-plugin:deploy | 部署项目站点 |
process-resources | maven-resources-plugin:resources | 复制主资源文件至主输出目录 |
compile | maven-compiler-plugin:compile | 编译主代码至主输出目录 |
process-test-resources | maven-resources-plugin:testResources | 复制测试资源文件至测试输出目录 |
test-compile | maven-compiler-plugin:testCompile | 编译测试代码至测试输出目录 |
test | maven-surefile-plugin:test | 执行测试用例 |
package | maven-jar-plugin:jar | 创建项目jar包 |
install | maven-install-plugin:install | 将输出构件安装到本地仓库 |
deploy | maven-deploy-plugin:deploy | 将输出的构件部署到远程仓库 |
使用 Maven 命令执行插件的目标,语法如:mvn [插件名]:[目标名]。
例如,调用 maven-compiler-plugin 插件的 compile 目标,命令如:mvn compiler:compile(注意不能使用插件
的全名,一定是缩写名,不然会报找不到插件)。
6.4 自定义绑定插件
除了内置绑定之外,用户也可以自己选择将某个插件目标绑定到 Maven 生命周期的某个阶段上,这种绑定方式就
是自定义绑定。自定义绑定能够让 Maven 在构建过程中执行更多更丰富的任务。
例如,我们想要在 clean 生命周期的 clean 阶段中显示自定义文本信息,则只需要在项目的 POM 中 ,通过 build
元素的子元素 plugins,将 maven-antrun-plugin插件的run 目标绑定到 clean 阶段上,并使用该插件输出自定义
文本信息即可。
<build>
<plugins>
<!-- 绑定插件 maven-antrun-plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<!--自定义 id -->
<id>custom clean</id>
<!--插件目标绑定的构建阶段 -->
<phase>clean</phase>
<!--插件目标 -->
<goals>
<goal>run</goal>
</goals>
<!--配置 -->
<configuration>
<!-- 执行的任务 -->
<target>
<!--自定义文本信息 -->
<echo message="清理阶段"/>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
以上配置中除了插件的坐标信息之外,还通过 executions 元素定义了一些执行配置。executions 下的每一个
execution 子元素都可以用来配置执行一个任务。execution 下各个元素含义如下:
id
:任务的唯一标识。phase
:插件目标需要绑定的生命周期阶段。goals
:用于指定一组插件目标,其子元素 goal 用于指定一个插件目标。configuration
:该任务的配置,其子元素 tasks 用于指定该插件目标执行的任务。
# 执行
$ mvn clean
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building maven hello world 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ hello-maven ---
[INFO] Deleting D:\zhangshixing\hexo\website-hexo-theme-particlex\source\_posts\【进行】Maven\code\HelloMaven\target
[INFO]
[INFO] --- maven-antrun-plugin:3.0.0:run (custom clean) @ hello-maven ---
[INFO] Executing tasks
[WARNING] [echo] 清理阶段
[INFO] Executed tasks
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.536 s
[INFO] Finished at: 2024-03-21T18:16:57+08:00
[INFO] Final Memory: 10M/245M
[INFO] ------------------------------------------------------------------------
可以使用 maven-help-plugin 查看插件详细信息,了解插件目标的默认绑定阶段。运行命令如下:
$ mvn help:describe -Dplugin=org.apache.maven.plugins:maven-antrun-plugin:3.0.0 -Ddetail
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building maven hello world 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-help-plugin:3.2.0:describe (default-cli) @ hello-maven ---
[INFO] org.apache.maven.plugins:maven-antrun-plugin:3.0.0
Name: Apache Maven AntRun Plugin
Description: Runs Ant scripts embedded in the POM
Group Id: org.apache.maven.plugins
Artifact Id: maven-antrun-plugin
Version: 3.0.0
Goal Prefix: antrun
This plugin has 2 goals:
antrun:help
Description: Display help information on maven-antrun-plugin.
Call mvn antrun:help -Ddetail=true -Dgoal=<goal-name> to display parameter
details.
Implementation: org.apache.maven.plugins.antrun.HelpMojo
Language: java
......
antrun:run
Description: Maven AntRun Mojo.
This plugin provides the capability of calling Ant tasks from a POM by
running the nested Ant tasks inside the <target/> parameter. It is
encouraged to move the actual tasks to a separate build.xml file and call
that file with an <ant/> task.
Implementation: org.apache.maven.plugins.antrun.AntRunMojo
Language: java
......
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.872 s
[INFO] Finished at: 2024-03-21T18:22:40+08:00
[INFO] Final Memory: 13M/309M
[INFO] ------------------------------------------------------------------------
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<executions>
<!-- 使用插件需要执行的任务 -->
<execution>
<!-- 任务id -->
<id>attach-source</id>
<!-- 任务中插件的目标,可以指定多个 -->
<goals>
<goal>jar-no-fork</goal>
</goals>
<!-- 绑定的阶段 -->
<phase>verify</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>2.5</version>
<executions>
<!-- 使用插件需要执行的任务 -->
<execution>
<!-- 任务中插件的目标,可以指定多个 -->
<id>clean-target</id>
<goals>
<goal>clean</goal>
</goals>
<!-- 绑定的阶段 -->
<phase>validate</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
7、插件配置
7.1 命令行插件配置
在日常的 Maven 使用中,我们会经常从命令行输入并执行 Maven 命令。在这种情况下,如果能够方便地更改某
些插件的行为,无疑会十分方便。很多插件目标的参数都支持从命令行配置,用户可以在 Maven 命令中使用 -D
参数,并伴随一个参数键=参数值的形式,来配置插件目标的参数。
$ mvn 插件goupId:插件artifactId[:插件version]:插件目标 [-D目标参数1] [-D目标参数2] [-D目标参数n]
# 等价
$ mvn 插件前缀:插件目标 [-D目标参数1] [-D目标参数2] [-D目标参数n]
$ mvn org.apache.maven.plugins:maven-surefire-plugin:help
......
surefire:help
Display help information on maven-surefire-plugin.
Call mvn surefire:help -Ddetail=true -Dgoal=<goal-name> to display parameter
details.
surefire:test
Run tests using Surefire.
......
maven-surefire-plugin插件有2个目标help和test,描述中可以看出test目标是用来运行测试用例的。
$ mvn org.apache.maven.plugins:maven-surefire-plugin:help -Dgoal=test -Ddetail=true
......
skip (Default: false)
Set this to "true" to bypass unit tests entirely. Its use is NOT
RECOMMENDED, especially if you enable it using the "maven.test.skip"
property, because maven.test.skip disables both running the tests and
compiling the tests. Consider using the skipTests parameter instead.
User property: maven.test.skip
......
大家认真看一下skip这个参数说明,这个参数默认是false,如果设置为true的时候,项目将跳过测试代码的编译和
测试用例的执行,可以maven.test.skip这个属性来进行命令行传参,将其传递给test目标的skip属性,这个通过-D
传递的参数名称就和目标参数名称不一样了,所以需要注意-D后面并不一定是参数名称。
例如,maven-surefire-plugin 提供了一个 maven.test.skip 参数,当其值为 true 的时候,就会跳过执行测试。于
是,在运行命令的时候,加上如下 -D 参数就能跳过测试:
$ mvn org.apache.maven.plugins:maven-surefire-plugin:test
$ mvn org.apache.maven.plugins:maven-surefire-plugin:test -Dmaven.test.skip=true
$ mvn install -Dmaven.test.skip=true
说明跳过了测试的执行。
也可以在 pom.xml 文件的 properties中进行配置跳过测试:
<maven.test.skip>true</maven.test.skip>
也可以在 build 中配置插件参数的方式:
<configuration>
<skip>true</skip>
</configuration>
7.2 POM中插件全局配置
并不是所有的插件参数都适合从命令行配置,有些参数的值从项目创建到项目发布都不会改变,或者说很少改变,
对于这种情况,在POM文件中一次性配置就显然比重复在命令行输人要方便。
用户可以在声明插件的时候,对此插件进行一个全局的配置。也就是说,所有该基于该插件目标的任务,都会使用
这些配置。例如,我们通常会需要配置 maven-compiler-plugin 告诉它编译 Java 1.8 版本的源文件,生成与
JVM1.8 兼容的字节码文件。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
这样,不管绑定到 compile 阶段的 maven-compiler-plugin:compile 任务,还是绑定到test-compiler 阶段的
maven-compiler-plugin:testCompiler 任务,就都能够使用该配置,基于 Java 1.8 版本进行编译。
7.3 POM 中插件任务配置
除了为插件配置全局的参数,用户还可以为某个插件任务配置特定的参数。以 maven-antrun-plugin 为例,它有
一个目标run,可以用来在 Maven 中调用 Ant 任务。用户将 maven-antrun-plugin:run 绑定到多个生命周期阶段
上,再加以不同的配置,就可以让 Maven 在不同的生命阶段执行不同的任务。
<build>
<plugins>
<!-- 绑定插件 maven-antrun-plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>ant-validate</id>
<phase>validate</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<echo>I'm bound to validate phase.</echo>
</target>
</configuration>
</execution>
<execution>
<id>ant-verify</id>
<phase>verify</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<echo>I'm bound to verify phase.</echo>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
7.4 获取插件信息
仅仅理解如何配置使用插件是不够的。当遇到一个构建任务的时候,用户还需要知道去哪里寻找合适的插件,以帮
助完成任务。找到正确的插件之后,还要详细了解该插件的配置点。由于Maven 的插件非常多,而且这其中的大
部分没有完善的文档,因此,使用正确的插件并进行正确的配置,其实并不是一件容易的事。
7.4.1 在线插件信息
基本上所有主要的 Maven 插件都来自 Apache 和 Codehaus。由于 Maven 本身是属于 Apache 软件基金会的,
因此它有很多官方的插件,每天都有成千上万的 Maven 用户在使用这些插件,它们具有非常好的稳定性。详细的
列表可以在这个地址得到:https://maven.apache.org/plugins/index.html
单击某个插件的链接便可以得到进一步的信息。
所有官方插件能在这里下载:https://repo1.maven.org/maven2/org/apache/maven/plugins/
。
虽然并非所有插件都提供了完善的文档,但一些核心插件的文档还是非常丰富的。以 maven-surefire-plugin 为
例,访问 http://maven.apache.org/plugins/maven-surefire-plugin/
可以看到该插件的简要介绍、包含
的目标、使用介绍、FAQ 以及很多实例。
一般来说,通过阅读插件文档中的使用介绍和实例,就应该能够在自己的项目中很好地使用该插件。但当我们想了
解非常细节的目标参数时,就需要进一步访问该插件每个目标的文档。以 maven-surefire-plugin 为例,可以通过
在命令行传人 maven.test.skip 参数来跳过测试执行,而执行测试的插件目标是 surefire:test,访问其文档:
http://maven.apache.org/plugins/maven-surefire-plugin/test-mojo.html
,可以找到目标参数 skip。
文档详细解释了该参数的作用、类型等信息。基于该信息,用户可以在POM中配置 maven-surefire-plugin 的
skip 参数为 true来跳过测试。这个时候读者可能会不理解了,之前在命令行传入的参数不是 maven.test.skip吗?
的确如此,虽然对于该插件目标的作用是一样的,但从命令行传人的参数确实不同于该插件目标的参数名称。命令
行参数是由该插件参数的表达式(Expression)决定的。可以看到,surefire:test skip参数的表达式为
${maven.test.skip}
,它表示可以在命令行以 -Dmaven.test.skip=true
的方式配置该目标。并不是所有
插件目标参数都有表达式,也就是说,一些插件目标参数只能在POM中配置。
7.4.2 使用maven-help-plugin描述插件
除了访问在线的插件文档之外,还可以借助 maven-help-plugin 来获取插件的详细信息。
可以运行如下命令来获取 maven-compiler-plugin 3.10.1 版本的信息:
$ mvn help:describe -Dplugin=org.apache.maven.plugins:maven-compiler-plugin:3.10.1
这里执行的是 maven-help-plugin 的 describe 目标,在参数 plugin 中输入需要描述插件的 groupId、artifactld
和 version。Maven 在命令行输出 maven-compiler-plugin 的简要信息,包括该插件的坐标、目标前缀和目标
等。
对于坐标和插件目标,不再多做解释,这里值得一提的是目标前缀(Goal Prefix),其作用是方便在命令行直接运行
插件。maven-compiler-plugin 的目标前缀是 compiler。
在描述插件的时候,还可以省去版本信息,让Maven 自动获取最新版本来进行表述。
例如:
$ mvn help:describe -Dplugin=org.apache.maven.plugins:maven-compiler-plugin
进一步简化,可以使用插件目标前缀替换坐标。例如:
$ mvn help:describe -Dplugin=compiler
如果想仅仅描述某个插件目标的信息,可以加上 goal 参数:
$ mvn help:describe -Dplugin=compiler -Dgoal=compile
如果想让 maven-help-plugin 输出更详细的信息,可以加上detail 参数:
$ mvn help:describe -Dplugin=compiler -Ddetail
读者可以在实际环境中使用 help:describe 描述一些常用插件的信息,以得到更加直观的感受。
7.5 获取插件信息的另一种方式
插件目标是用来执行任务的,那么执行任务肯定是有参数配的,这些就是目标的参数,每个插件目标对应于java中
的一个类,参数就对应于这个类中的属性。
# 列出插件所有目标
$ mvn 插件goupId:插件artifactId[:插件version]:help
$ mvn 插件前缀:help
# 对于该插件
# <groupId>org.apache.maven.plugins</groupId>
# <artifactId>maven-shade-plugin</artifactId>
# <version>3.2.4</version>
$ mvn org.apache.maven.plugins:maven-clean-plugin:2.5:help
# 等价
$ mvn help:describe -Dplugin=org.apache.maven.plugins:maven-clean-plugin:2.5
......
Maven Clean Plugin
The Maven Clean Plugin is a plugin that removes files generated at build-time
in a project's directory.
This plugin has 2 goals:
clean:clean
Goal which cleans the build.
This attempts to clean a project's working directory of the files that were
generated at build-time. By default, it discovers and deletes the directories
configured in project.build.directory, project.build.outputDirectory,
project.build.testOutputDirectory, and project.reporting.outputDirectory.
Files outside the default may also be included in the deletion by configuring
the filesets tag.
clean:help
Display help information on maven-clean-plugin.
Call
mvn clean:help -Ddetail=true -Dgoal=<goal-name>
to display parameter details.
......
上面列出了maven-clean-plugin这个插件所有的目标,有2个,分别是clean:clean、clean:help,分号后面的部分
是目标名称,分号前面的部分是插件的前缀,每个目标的后面包含对这个目标的详细解释说明。
# 查看插件目标参数列表
$ mvn 插件goupId:插件artifactId[:插件version]:help -Dgoal=目标名称 -Ddetail
$ mvn 插件前缀:help -Dgoal=目标名称 -Ddetail
$ mvn org.apache.maven.plugins:maven-clean-plugin:2.5:help -Dgoal=help -Ddetail
......
Maven Clean Plugin
The Maven Clean Plugin is a plugin that removes files generated at build-time
in a project's directory.
clean:help
Display help information on maven-clean-plugin.
Call
mvn clean:help -Ddetail=true -Dgoal=<goal-name>
to display parameter details.
Available parameters:
detail (Default: false)
If true, display all settable properties for each goal.
Expression: ${detail}
goal
The name of the goal for which to show help. If unspecified, all goals
will be displayed.
Expression: ${goal}
indentSize (Default: 2)
The number of spaces per indentation level, should be positive.
Expression: ${indentSize}
lineLength (Default: 80)
The maximum length of a display line, should be positive.
Expression: ${lineLength}
......
上面命令中的-Ddetail用户输出目标详细的参数列表信息,如果没有这个,目标的参数列表不会输出出来,看效
果。
注意上面参数详细参数说明中有 Expression: ${xxx}
这样的部分,这种表示给这个运行的目标传参,可以通过
mvn -Dxxx 这种方式传参,xxx为${xxx}中的xxx部分,这个xxx有时候和目标参数的名称不一致,所以这点需要注
意,运行带参数的目标,看一下效果:
$ mvn org.apache.maven.plugins:maven-clean-plugin:2.5:help -Dgoal=help -Ddetail=false
......
Maven Clean Plugin
The Maven Clean Plugin is a plugin that removes files generated at build-time
in a project's directory.
clean:help
Display help information on maven-clean-plugin.
Call
mvn clean:help -Ddetail=true -Dgoal=<goal-name>
to display parameter details.
......
上面传了一个detail=false,上面未输出目标的详细参数信息。
7.6 插件前缀
$ mvn help:describe -Dplugin=插件goupId:插件artifactId[:插件version]
$ mvn help:describe -Dplugin=org.apache.maven.plugins:maven-compiler-plugin:3.10.1
......
Name: Apache Maven Compiler Plugin
Description: The Compiler Plugin is used to compile the sources of your
project.
Group Id: org.apache.maven.plugins
Artifact Id: maven-compiler-plugin
Version: 3.10.1
Goal Prefix: compiler
This plugin has 3 goals:
compiler:compile
Description: Compiles application sources
compiler:help
Description: Display help information on maven-compiler-plugin.
Call mvn compiler:help -Ddetail=true -Dgoal=<goal-name> to display
parameter details.
compiler:testCompile
Description: Compiles application test sources.
For more information, run 'mvn help:describe [...] -Ddetail'
......
输出中的 Goal Prefix: compiler
部分对应的就是插件的前缀,上面这个插件的前缀是 compiler
。
上面用了很多mvn help:这个命令,这个调用的是maven-help-plugin插件的功能,help是插件的前缀,它的坐标
是:
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-help-plugin</artifactId>
<version>3.2.0</version>
</dependency>
8、聚合与继承
8.1 pom.xml文件build配置
<build>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<filtering>true</filtering>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>1.8</source>
<source>1.8</source>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
build 元素下还包含了两个插件的配置。首先是配置 maven-compiler-plugin 支持Java 1.8,我们知道,虽然这里
没有配置插件版本,但由于 maven-compiler-plugin 是核心插件,它的版本已经在超级 POM 中设定了。此外,
如果这里不配置 groupld,Maven 也会使用默认的 groupld org.apache.maven.plugins。除了 maven-compiler-
plugin,这里还配置了 maven-resources-plugin 使用 UTF-8编码处理资源文件。
<build>
<!-- 项目的名字 -->
<finalName>WebMavenDemo</finalName>
<!-- 描述项目中资源的位置 -->
<resources>
<!-- 自定义资源1 -->
<resource>
<!-- 资源目录 -->
<directory>src/main/java</directory>
<!-- 包括哪些文件参与打包 -->
<includes>
<include>**/*.xml</include>
</includes>
<!-- 排除哪些文件不参与打包 -->
<excludes>
<exclude>**/*.txt</exclude>
<exclude>**/*.doc</exclude>
</excludes>
</resource>
</resources>
<!-- 设置构建时候的插件 -->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.1</version>
<configuration>
<!-- 源代码编译版本 -->
<source>1.8</source>
<!-- 目标平台编译版本 -->
<target>1.8</target>
</configuration>
</plugin>
<!-- 资源插件(资源的插件) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.1</version>
<executions>
<execution>
<phase>compile</phase>
</execution>
</executions>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- war插件(将项目打成war包) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.1</version>
<configuration>
<!-- war包名字 -->
<warName>WebMavenDemo1</warName>
</configuration>
</plugin>
</plugins>
</build>
8.2 聚合配置
<modules>
<module>account-email</module>
<module>account-persist</module>
</modules>
<package>pom</package>
8.3 继承
父 pom:
<modelVersion>4.0.0</modelVersion>
<groupId>com.juvenxu.mvnbook.account</groupId>
<artifactId>account-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Account Parent</name>
子 pom:
<parent>
<groupId>com.juvenxu.mvnbook.account</groupId>
<artifactId>account-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../account-parent/pom.xml</relativePath>
</parent>
上述 POM 中使用 parent 元素声明父模块,parent 下的子元素 groupld、artifactld 和 version 指定了父模块的
坐标,这三个元素是必须的。元素 relativePath 表示父模块 POM 的相对路径。
当项目构建时,Maven 会首先根据 relativePath 检查父 POM,如果找不到,再从本地仓库查找。relativePath 的
默认值是 …/pom.xml,也就是说,Maven 默认父 POM 在上一层目录下。
8.4 可继承的pom元素
在上面我们看到,groupld 和 version是可以被继承的,那么还有哪些 POM 元素可以被继承呢?以下是一个完整的
列表,并附带了简单的说明:
-
groupld
:项目组 ID,项目坐标的核心元素。 -
version
:项目版本,项目坐标的核心元素。 -
description
:项目的描述信息。 -
organization
:项目的组织信息。 -
inceptionYear
:项目的创始年份。 -
url
:项目的URL地址。 -
developers
:项目的开发者信息。 -
contributors
:项目的贡献者信息。 -
distributionManagement
:项目的部署配置。 -
issueManagement
:项目的缺陷跟踪系统信息。 -
ciManagement
:项目的持续集成系统信息。 -
scm
:项目的版本控制系统信息。 -
mailingLists
:项目的邮件列表信息。 -
properties
:自定义的 Maven 属性。 -
dependencies
:项目的依赖配置。 -
dependencyManagement
:项目的依赖管理配置。 -
repositories
:项目的仓库配置。 -
build
:包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等。 -
reporting
:包括项目的报告输出目录配置、报告插件配置等。
8.5 依赖管理dependencyManagement
可继承元素列表包含了 dependencies 元素,说明依赖是会被继承的。
Maven 提供的 dependencyManagement 元素既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用
的灵活性。在 dependencyManagement 元素下的依赖声明不会引人实际的依赖,不过它能够约束dependencies
下的依赖使用。例如,可以在 account-parent
中加人这样的 dependencyManagement
配置。
<?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">
<parent>
<artifactId>ProjectMaven</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>account-parent</artifactId>
<packaging>pom</packaging>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<elasticsearch.version>7.15.2</elasticsearch.version>
<redisson.version>3.13.6</redisson.version>
<junit.version>4.7</junit.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.6</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.15.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
首先该父 POM 将 springframework 和 junit 依赖的版本以 Maven 变量的形式提取了出来,不仅消除了一些重
复,也使得各依赖的版本处于更加明显的位置。
这里使用 dependencyManagement 声明的依赖既不会给 account-parent 引入依赖,也不会给它的子模块引入
依赖、不过这段配置是会被继承的。现在 account-email
的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">
<modelVersion>4.0.0</modelVersion>
<artifactId>account-email</artifactId>
<parent>
<artifactId>account-parent</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath>../account-parent/pom.xml</relativePath>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<javax.mail.version>1.4.1</javax.mail.version>
<mail.version>1.4.1</mail.version>
</properties>
<dependencies>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>${mail.version}</version>
</dependency>
</dependencies>
</project>
这里 elasticsearch 和 redisson 如果不指定版本,则使用父类指定的版本。
这里没有声明 spring-context-support,那么该依赖就不会被引入。这正是 dependencyManagement 的灵活性
所在。
这里介绍名为 import 的依赖范围,推迟到现在介绍是因为该范围的依赖只在 dependencyManagement 元素下
才有效果,使用该范围的依赖通常指向一个 POM,作用是将目标POM中的 dependencyManagement 配置导入
并合并到当前 POM 的 dependencyManagement 元素中。例如想要在另外一个模块中使用与上面完全一样的
dependencyManagement 配置,除了复制配置或者继承这两种方式之外,还可以使用 import 范围依赖将这一配
置导入。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>account-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
注意,上述代码中依赖的 type 值为pom,import 范围依赖由于其特殊性,一般都是指向打包类型为pom的模
块。如果有多个项目,它们使用的依赖版本都是一致的,则就可以定义一个使用 dependencyManagement 专门
管理依赖的 POM,然后在各个项目中导入这些依赖管理配置。
8.6 插件管理pluginManagement
Maven 提供了 dependencyManagement 元素帮助管理依赖,类似地,Maven 也提供了pluginManagement 元
素帮助管理插件。在该元素中配置的依赖不会造成实际的插件调用行为,当 POM 中配置了真正的 plugin 元素,
并且其 groupld 和 artifactld 与 pluginManagement 中配置的插件匹配时,pluginManagement 的配置才会影响
实际的插件行为。
前面配置了 maven-source-plugin,将其 jar-no-fork 目标绑定到了 verity 生命周期阶段,以生成项目源码包。如
果一个项目中有很多子模块,并且需要得到所有这些模块的源码包,那么很显然,为所有模块重复类似的插件配置
不是最好的办法。这时更好的方法是在父 POM 中使用 pluginManagement 配置插件。
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
当子模块需要生成源码包的时候,只需要如下简单的配置。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
</plugins>
</build>
子模块声明使用了 maven-source-plugin 插件,同时又继承了父模块的 pluginManagement配置,两者基于
groupld 和 artifactId 匹配合并之后进行了插件配置。
如果子模块不需要使用父模块中 pluginManagement 配置的插件,可以尽管将其忽略。
如果子模块需要不同的插件配置,则可以自行配置以覆盖父模块的 pluginManagement 配置。
有了 pluginManagement元素,account-email 的 POM 也能得以简化了,它们都配置了
maven-compiler-plugin 和 maven-resources-plugin。可以将这两个插件的配置移到 account-parent 的
pluginManagement元素中。
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
account-email 可以完全地移除关于 maven-compiler-plugin 和 maven-resources-plugin 的配置,但它们仍能
享受这两个插件的服务,前一插件开启了 Java 8 编译的支持,后一插件也会使用UTF-8编码处理资源文件。
当项目中的多个模块有同样的插件配置时,应当将配置移到父 POM 的 pluginManagement 元素中。即使各个模
块对于同一插件的具体配置不尽相同,也应当使用父 POM 的 pluginManagement 元素统-声明插件的版本。甚至
可以要求将所有用到的插件的版本在父 POM 的 pluginManagement 元素中声明,子模块使用插件时不配置版本
信息,这么做可以统一项目的插件版本,避免潜在的插件不一致或者不稳定问题,也更易于维护。
8.7 聚合与继承的关系
基于前面的内容,读者可以了解到,多模块 Maven 项目中的聚合与继承其实是两个概念,其目的完全是不同的。
前者主要是为了方便快速构建项目,后者主要是为了消除重复配置。
对于聚合模块来说,它知道有哪些被聚合的模块,但那些被聚合的模块不知道这个聚合模块的存在。
对于继承关系的父POM来说,它不知道有哪些子模块继承于它,但那些子模块都必须知道自己的父POM是什么。
如果非要说这两个特性的共同点,那么可以看到,聚合POM与继承关系中的父 POM 的 packaging 都必须是
pom,同时,聚合模块与继承关系中的父模块除了 POM 之外都没有实际的内容。
在现有的实际项目中,读者往往会发现一个 POM 既是聚合 POM,又是父 POM,这么做主要是为了方便。一般来
说,融合使用聚合与继承也没有什么问题。
8.8 约定优于配置
标准的重要性已不用过多强调,想象一下,如果不是所有程序员都基于 HTTP 协议开发Web 应用,互联网会乱成
怎样。各个版本的IE、Firefox等浏览器之间的差别已经让很多开发者头痛不已。而Java 成功的重要原因之一就是
它能屏蔽大部分操作系统的差异,XML流行的原因之一是所有语言都接受它。Maven 当然还不能和这些既成功又
成熟的技术相比,但Maven 的用户都应该清楚,Maven 提倡约定优于配置(Convention Over Configuration),这
是 Maven 最核心的设计理念之一。
那么为什么要使用约定而不是自己更灵活的配置呢?原因之一是,使用约定可以大量减少配置。
Maven 会假设用户的项目是这样的:
- 源码目录为
src/main/java/
- 编译输出目录为
target/classes/
- 打包方式为
jar
- 包输出目录为
target/
遵循约定虽然损失了一定的灵活性,用户不能随意安排日录结构,但是却能减少配置。
更重要的是,遵循约定能够帮助用户遵守构建标准。
多次提到超级POM,任何一个Maven 项目都隐式地继承自该POM,这有点类似于任何一个 Java 类都隐式地继承
于 Object 类。因此,大量超级 POM 的配置都会被所 Maven 项目继承,这些配置也就成为了 Maven 所提倡的约
定。
Maven 设定核心插件的原因是防止由于插件版本的变化而造成构建不稳定。
Maven 提倡使用一个共同的标准目录结构,Maven 使用约定优于配置的原则,大家尽可能的遵守这样的目录结
构,如下所示:
目录 | 目的 |
---|---|
${basedir} | 存放pom.xml和所有的子目录 |
${basedir}/src/main/java | 项目的java源代码 |
${basedir}/src/main/resources | 项目的资源,比如说property文件,springmvc.xml |
${basedir}/src/test/java | 项目的测试类,比如说Junit代码 |
${basedir}/src/test/resources | 测试用的资源 |
${basedir}/src/main/webapp/WEB-INF | web应用文件目录,web项目的信息,比如存放web.xml、本地图片、jsp视图页面 |
${basedir}/target | 打包输出目录 |
${basedir}/target/classes | 编译输出目录 |
${basedir}/target/test-classes | 测试编译输出目录 |
Test.java | Maven只会自动运行符合该命名规则的测试类 |
~/.m2/repository | Maven默认的本地仓库目录位置 |
8.9 反应堆
在一个多模块的Maven 项目中,反应堆(Reactor)是指所有模块组成的一个构建结构。
对于单模块的项目,反应堆就是该模块本身,但对于多模块项目来说,反应堆就包含了各模块之间继承与依赖的关
系,从而能够自动计算出合理的模块构建顺序。
8.9.1 反应堆的构建顺序
实际的构建顺序是这样形成的:Maven 按序读取 POM,如果该 POM 没有依赖模块,那么就构建该模块,否则就
先构建其依赖模块,如果该依赖还依赖于其他模块,则进一步先构建依赖的依赖。
模块间的依赖关系会将反应堆构成一个有向非循环图(Directed Acyelic Graph,DAG),各个模块是该图的节点,
依赖关系构成了有向边。这个图不允许出现循环,因此,当出现模块A依赖于B,而B又依赖于A的情况时,Maven
就会报错。
8.9.2 裁剪反应堆
一般来说,用户会选择构建整个项目或者选择构建单个模块,但有些时候,用户会想要仅仅构建完整反应堆中的某
些个模块。换句话说,用户需要实时地裁剪反应堆。
Maven 提供很多的命令行选项支持裁剪反应堆,输入 mvn -h 可以看到这些选项:
-
-am,--also-make
:同时构建所列模块的依赖模块。 -
-amd,--also-make-dependents
:同时构建依赖于所列模块的模块。 -
-pl,--projects<arg>
:构建指定的模块,模块间用逗号分隔。 -
-rf,-resume-from<arg>
:从指定的模块回复反应堆。
$ mvn clean install
可以使用 -pl 选项指定构建某几个模块,如运行如下命令:
$ mvn clean install -pl accout-email,accoun-persist
使用 -am 选项可以同时构建所列模块的依赖模块。例如:
$ mvn clean install -pl accout-email -am
使用 -amd 选项可以同时构建依赖于所列模块的模块。例如:
$ mvn clean install -pl accout-parent -amd
使用 -rf 选项可以在完整的反应堆构建顺序基础上指定从哪个模块开始构建。例如:
$ mvn clean install -rf accout-email
在 -pl -am 或者 -pl -amd的基础上,还能应用 -rf 参数,以对裁剪后的反应堆再次裁剪。例如:
$ mvn clean install -pl accout-parent -amd -rf accout-email
在开发过程中,灵活应用上述4个参数。可以帮助我们跳过无须构建的模块,从而加速构建。在项目庞大、模块特
别多的时候,这种效果就会异常明显。
9、使用Maven进行测试
9.1 测试案例
@Before
注解用来在进行测试前进行初始化操作。
在 JUnit 中可以使用 @Ignore
注解标记忽略测试方法。
@Test
对方法进行单元测试。
<?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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>hello-maven</artifactId>
<version>1.0-SNAPSHOT</version>
<name>maven hello world</name>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
package org.apache;
/**
* @author zhangshixing
*/
public class HelloWorld {
public String sayHello(){
return "Hello Maven";
}
public static void main(String[] args) {
System.out.println(new HelloWorld().sayHello());
}
}
package org.apache;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class AddTest {
@Test
public void testSayHello() {
int a = 10 + 10;
assertEquals(a, 20);
}
}
package org.apache;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class SubTests {
@Test
public void testSayHello() {
int a = 20 - 10;
assertEquals(a, 10);
}
}
package org.apache;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class HelloWorldTest {
@Before
public void testInit() {
System.out.println("init");
}
@Test
public void testSayHello() {
HelloWorld helloWorld = new HelloWorld();
String result = helloWorld.sayHello();
assertEquals("Hello Maven", result);
}
@Ignore
@Test
public void testSayHello1() {
HelloWorld helloWorld = new HelloWorld();
String result = helloWorld.sayHello();
assertEquals("Hello Maven", result);
}
}
# 运行测试
$ mvn test
[INFO] Scanning for projects...
......
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running org.apache.HelloWorldTest
init
Tests run: 2, Failures: 0, Errors: 0, Skipped: 1, Time elapsed: 0.053 sec
Results :
Tests run: 2, Failures: 0, Errors: 0, Skipped: 1
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.476 s
[INFO] Finished at: 2023-10-03T12:09:01+08:00
[INFO] Final Memory: 11M/309M
[INFO] ------------------------------------------------------------------------
9.2 maven-surefire-plugin简介
Maven 本身并不是一个单元测试框架,Java 世界中主流的单元测试框架为JUnit(http://www.junit.org/
)和
TestNG(http://testng.org/
)。Maven 所做的只是在构建执行到特定生命周期阶段的时候,通过插件来执行
JUnit 或者 TestNG 的测试用例。这一插件就是 maven-surefire-plugin
,可以称之为测试运行器(Test
Runner),它能很好地兼容JUnit3、JUnit 4 以及TestNG。
在 default 生命周期,其中的 test 阶段被定义为"使用单元测试框架运行测试"。我们知道,生命周期阶段需要绑定
到某个插件的目标才能完成真正的工作,test 阶段正是与 maven-surefire-plugin 的 test 目标相绑定了,这是一
个内置的绑定。
在默认情况下,maven-surefire-plugin 的 test 目标会自动执行测试源码路径(默认为src/test/java/)下所有符合一
组命名模式的测试类。这组模式为:
-
**/Test*.java
:任何子目录下所有命名以 Test 开头的 Java类。 -
**/*Test.java
:任何子目录下所有命名以 Test 结尾的 Java类。 -
**/*TestCase.java
:任何子目录下所有命名以 TestCase 结尾的 Java类。
只要将测试类按上述模式命名,Maven 就能自动运行它们,用户也就不再需要定义测试集合(TestSuite)来聚合测
试用例(TestCase)。关于模式需要注意的是,以Tests结尾的测试类是不会得以自动执行的。
当然,如果有需要,可以自己定义要运行测试类的模式。
此外,maven-surefire-plugin 还支持更高级的TestNG测试集合xml 文件。
当然,为了能够运行测试,Maven 需要在项目中引人测试框架 JUnit 和 TestNG 的依赖。
9.3 跳过测试
# 运行打包
$ mvn package -DskipTests
当然,也可以在POM 中配置 maven-surefire-plugin 插件来提供该属性。但这是不推荐的做法,如果配置 POM
让项目长时间地跳过测试,则还要测试代码做什么呢?
配置插件跳过测试运行:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
# 运行打包
$ mvn package
实际上 maven-compiler-plugin 的 testCompile 目标和 maven-surefire-plugin 的 test 目标都提供了一个参数
skip用来跳过测试编译和测试运行,而这个参数对应的命令行表达式为 maven.test.skip。
9.4 动态指定要运行的测试用例
反复运行单个测试用例是日常开发中很常见的行为。例如,项目代码中有一个失败的测试用例,开发人员就会想要
再次运行这个测试以获得详细的错误报告,在修复该测试的过程中,开发人员也会反复运行它,以确认修复代码是
正确的。如果仅仅为了一个失败的测试用例而反复运行所有测试,未免太浪费时间了,当项目中测试的数目比较大
的时候,这种浪费尤为明显。
maven-surefire-plugin 提供了一个test参数让Maven用户能够在命令行指定要运行的测试用例。例如,如果只想
运行 HelloWorldTest,就可以使用如下命令:
# 运行
$ mvn test -Dtest=HelloWorldTest
这里test 参数的值是测试用例的类名,这行命令的效果就是只有 HelloWorldTest 这一个测试类得到运行。
maven-surefire-plugin的test 参数还支持高级一些的赋值方式,能让用户更灵活地指定需要运行的测试用例。
例如:
# 运行
$ mvn test -Dtest=Hello*Test
星号可以匹配零个或多个字符,上述命令会运行项目中所有类名以 Hello 开头、Test结尾的测试类。
除了星号匹配,还可以使用逗号指定多个测试用例:
# 运行
$ mvn test -Dtest=HelloWorldTest,AddTest
该命令的test 参数值是两个测试类名,它们之间用逗号隔开,其效果就是告诉 Maven只运行这两个测试类。
当然,也可以结合使用星号和逗号。例如:
# 运行
$ mvn test -Dtest=Hello*Test,AddTest
需要注意的是,上述几种从命令行动态指定测试类的方法都应该只是临时使用,如果长时间只运行项目的某几个测
试,那么测试就会慢慢失去其本来的意义。
test 参数的值必须匹配一个或者多个测试类,如果 maven-surefire-plugin 找不到任何匹配的测试类,就会报错并
导致构建失败。例如下面的命令没有匹配任何测试类:
# 运行
$ mvn test -Dtest
这样的命令会导致构建失败。
根据错误提示可以加上-DfaillfNoTests=false,告诉 maven-surefire-plugin 即使没有任何测试也不要报错:
# 运行
$ mvn test -Dtest -DfailIfNoTests=false
这样构建就能顺利执行完毕了。可以发现,实际上使用命令行参数-Dtest -DfaillfNoTests=false 是另外一种跳过测
试的方法。
我们看到,使用 test 参数用户可以从命令行灵活地指定要运行的测试类。可惜的是,maven-surefire-plugin 并没
有提供任何参数支持用户从命令行跳过指定的测试类,好在用户可以通过在POM 中配置 maven-surefire-plugin
排除特定的测试类。
9.5 包含与排除测试用例
上节介绍了一组命名模式,符合这一组模式的测试类将会自动执行。Maven 提倡约定优于配置原则,因此用户应
该尽量遵守这一组模式来为测试类命名。即便如此,maven-surefire-plugin 还是允许用户通过额外的配置来自定
义包含一些其他测试类,或者排除一些符合默认命名模式的测试类。
例如,由于历史原因,有些项目所有测试类名称都以Tests结尾,这样的名字不符合默认的3种模式,因此不会被自
动运行,用户可以通过下面的配置让 Maven 自动运行这些测试。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<includes>
<include>**/*Tests.java</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
$ mvn test
上述代码清单中使用了**/*Tests.java
来匹配所有以Tests 结尾的 Java类,两个星**
用来匹配任意路径,一个
星号 *
匹配除路径风格符外的0个或者多个字符。
类似地,也可以使用excludes元素排除一些符合默认命名模式的测试类,如下所示。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<excludes>
<exclude>**/*WorldTest.java</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
$ mvn test
上述代码清单排除了所有以WorldTest结尾的测试类,它们都符合默认的命名模式**/*Test.java,不过,有了
excludes 配置后,maven-surefire-plugin 将不再自动运行它们。
9.6 测试报告
除了命令行输出,Maven 用户可以使用 maven-surefire-plugin 等插件以文件的形式生成更丰富的测试报告。
9.6.1 基本的测试报告
默认情况下,maven-surefire-plugin 会在项目的 target/surefire-reports 目录下生成两种格式的错误报告:
-
简单文本格式
-
与 JUnit 兼容的 XML格式
org.apache.HelloWorldTest.txt
文件的内容:
-------------------------------------------------------------------------------
Test set: org.apache.HelloWorldTest
-------------------------------------------------------------------------------
Tests run: 2, Failures: 0, Errors: 0, Skipped: 1, Time elapsed: 0.001 sec
TEST-org.apache.HelloWorldTest.xml
文件的内容:
<?xml version="1.0" encoding="UTF-8" ?>
<testsuite tests="2" failures="0" name="org.apache.HelloWorldTest" time="0.001" errors="0" skipped="1">
......
<testcase classname="org.apache.HelloWorldTest" name="testSayHello1" time="0">
<skipped/>
</testcase>
<testcase classname="org.apache.HelloWorldTest" name="testSayHello" time="0.001"/>
</testsuite>
这样的报告对于获得信息足够了,XML格式的测试报告主要是为了支持工具的解析,如 Eelipse 的JUnit 插件可以
直接打开这样的报告。
由于这种XML格式已经成为了Java单元测试报告的事实标准,一些其他工具也能使用它们。例如,持续集成服务器
Hudson 就能使用这样的文件提供持续集成的测试报告。
以上展示了一些运行正确的测试报告,实际上,错误的报告更具价值。我们可以修改 SubTests 让一个测试失败:
package org.apache;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class SubTests {
@Test
public void testSayHello() {
int a = 20 - 10;
assertEquals(a, 20);
}
}
这时得到的简单文本报告会是这样:
-------------------------------------------------------------------------------
Test set: org.apache.SubTests
-------------------------------------------------------------------------------
Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.001 s <<< FAILURE! - in org.apache.SubTests
testSayHello(org.apache.SubTests) Time elapsed: 0 s <<< FAILURE!
java.lang.AssertionError: expected:<10> but was:<20>
at org.apache.SubTests.testSayHello(SubTests.java:12)
报告说明了哪个测试方法失败、哪个断言失败以及具体的堆栈信息,用户可以据此快速地寻找失败原因。该测试的
XML 格式报告用 Eclipse JUnit 插件打开。
从堆栈信息中可以看到,该测试是由 maven-surefire-plugin 发起的。
9.6.2 测试覆盖率报告
测试覆盖率是衡量项目代码质量的一个重要的参考指标。Cobertura是一个优秀的开源测试覆盖率统计工具:
http://cobertura.sourceforge.net/
Maven 通过 cobertura-maven-plugin 与之集成,用户可以使用简单的命令为Maven 项目生成测试覆盖率报告。
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<version>2.7</version>
</plugin>
例如,可以在该项目下运行如下命令生成报告:
$ mvn cobertura:cobertura
接着打开项目目录 target/site/cobertura/下的 index.html 文件,就能看到如图所示的测试覆盖率报告。
单击具体的类,还能看到精确到行的覆盖率报告:
9.7 运行TestNG测试
TestNG是Java社区中除JUnit 之外另一个流行的单元测试框架。NG 是 Next Generation的缩写,译为下一代。
TestNG在JUnit 的基础上增加了很多特性,读者可以访问其站点 http://testng.org/
获取更多信息。值得一提
的是,《Next Generation Java Testing》一书专门介绍TestNG和相关测试技巧。
使用 Maven 运行 TestNG十分方便。首先需要删除POM中的 JUnit 依赖,加入TestNG依赖:
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.10</version>
<scope>test</scope>
</dependency>
与 JUnit 类似,TestNG的依赖范围应为test。下一步需要将对JUnit 的类库引用更改成对 TestNG的类库引用。
下表给出了常用类库的对应关系。
JUnit 和 TestNG 的常用类库对应关系:
JUnit 类 | TestNG类 | 作用 |
---|---|---|
org.junit.Test | org.testng.annotations.Test | 标注方法为测试方法 |
org.junit.Assert | org.testng.Assert | 检查测试结果 |
org.junit.Before | org.testng.annotations.BeforeMethod | 标注方法在每个测试方法之前运行 |
org.junit.After | org.testng.annotations.AfterMethod | 标注方法在每个测试方法之后运行 |
org.junit.BeforeClass | org.testng.annotations.BeforeClass | 标注方法在所有测试方法之前运行 |
org.junit.AfterClass | org. testng.annotations.AfterClass | 标注方法在所有测试方法之后运行 |
将JUnit的类库引用改成TestNG之后,在命令行输入 mvn test,Maven 就会自动运行那些符合命名模式的测试
类。这一点与运行JUnit测试没有区别。
package org.apache;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
public class AddTest {
@Test
public void testSayHello() {
int a = 10 + 10;
assertEquals(a, 20);
}
}
package org.apache;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
public class SubTests {
@Test
public void testSayHello() {
int a = 20 - 10;
assertEquals(a, 10);
}
}
package org.apache;
import jdk.nashorn.internal.ir.annotations.Ignore;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
public class HelloWorldTest {
@BeforeMethod
public void testInit() {
System.out.println("init");
}
@Test
public void testSayHello() {
HelloWorld helloWorld = new HelloWorld();
String result = helloWorld.sayHello();
assertEquals("Hello Maven", result);
}
@Ignore
@Test
public void testSayHello1() {
HelloWorld helloWorld = new HelloWorld();
String result = helloWorld.sayHello();
assertEquals("Hello Maven", result);
}
}
$ mvn test
TestNG 允许用户使用一个名为 testng.xml 的文件来配置想要运行的测试集合。例如,可以在项目根目录下创建
一个testng.xml 文件,配置只运行 HelloWorldTest,如下所示。
<?xml version="1.0" encoding="UTF-8" ?>
<suite name="Suite1" verbose="1">
<test name="Regression1">
<classes>
<class name="org.apache.HelloWorldTest"></class>
</classes>
</test>
</suite>
同时再配置 maven-surefire-plugin 使用该 testng.xml,如下所示。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<suiteXmlFiles>testng.xml</suiteXmlFiles>
</configuration>
</plugin>
</plugins>
</build>
$ mvn test
TestNG较 JUnit 的一大优势在于它支持测试组的概念,如下的注解会将测试方法加入到两个测试组 util 和
medium 中:
@Test(groups={"util","medium"})
由于用户可以自由地标注方法所属的测试组,因此这种机制能让用户在方法级别对测试进行归类。这一点JUnit 无
法做到,它只能实现类级别的测试归类。
Maven 用户可以使用如下所示的配置运行一个或者多个TestNG测试组。
package org.apache;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
public class AddTest {
@Test(groups = {"util"})
public void testSayHello() {
int a = 10 + 10;
assertEquals(a, 20);
}
}
package org.apache;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
public class SubTests {
@Test(groups = {"util"})
public void testSayHello() {
int a = 20 - 10;
assertEquals(a, 10);
}
}
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<groups>util</groups>
</configuration>
</plugin>
</plugins>
</build>
$ mvn test
9.8 重用测试代码
优秀的程序员会像对待产品代码一样细心维护测试代码,尤其是那些供具体测试类继承的抽象类,它们能够简化测
试代码的编写。还有一些根据具体项目环境对测试框架的扩展,也会被大范围地重用。
在命令行运行mvn package的时候,Maven会将项目的主代码及资源文件打包,将其安装或部署到仓库之后,这
些代码就能为他人使用,从而实现Maven项目级别的重用。默认的打包行为是不会包含测试代码的,因此在使用
外部依赖的时候,其构件一般都不会包含测试代码。
然后,在项目内部重用某个模块的测试代码是很常见的需求,可能某个底层模块的测试代码中包含了一些常用的测
试工具类,或者一些高质量的测试基类供继承。这个时候Maven 用户就需要通过配置 maven-jar-plugin 将测试类
打包,如下所示。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
maven-jar-plugin 有两个目标,分别是 jar 和 test-jar,前者通过 Maven 的内置绑定在default 生命周期的
package 阶段运行,其行为就是对项目主代码进行打包,而后者并没有内置绑定,因此上述的插件配置显式声明
该目标来打包测试代码。通过查询该插件的具体信息可以了解到,test-jar的默认绑定生命周期阶段为package,
因此当运行mvn clean package后就会看到如下输出:
$ mvn clean package
会生成两个jar包:
hello-maven-1.0-SNAPSHOT-tests.jar
和 hello-maven-1.0-SNAPSHOT.jar
maven-jar-plugin的两个目标都得以执行,分别打包了项目主代码和测试代码。
现在,就可以通过依赖声明使用这样的测试包构件了,如下所示。
<dependency>
<groupId>hello-maven</groupId>
<artifactId>org.example</artifactId>
<version>1.0-SNAPSHOT</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
上述依赖声明中有一个特殊的元素 type,所有测试包构件都使用特殊的 test-jar打包类型。需要注意的是,这一类
型的依赖同样都使用 test依赖范围。
10、使用Maven构建web应用
10.1 显示指定Web项目的打包方式
<groupId>org.example</groupId>
<artifactId>hello-maven</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
如果不显式地指定 packaging,Maven 会使用默认的 jar 打包方式,从而导致无法正确打包 Web 项目。
10.2 指定测试资源
<build>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<filtering>true</filtering>
</testResource>
</testResources>
</build>
10.3 指定项目生成的主构件的名称
在一些Web 项目中,读者可能会看到 finalName 元素的配置。该元素用来标识项目生成的主构件的名称,该元素
的默认值已在超级 POM 中设定,值为 ${project.artifactld}-${project.version}
。
这样的主构件名称显然不利于部署,不管是测试环境还是最终产品环境,我们都不想在访问页面的时候输入冗长的
地址,因此我们会需要名字更为简洁的war包。这时可以如下所示配置 finalName元素:
<build>
<finalName>account</finalName>
</build>
经此配置后,项目生成的war包名称就会成为account.war,更方便部署。