26、Java库与应用程序构建:java-library插件、application插件、JUnit测试配置、JaCoCo覆盖率
这一章我们聊聊Gradle里最常用的几个插件。说白了,就是怎么把你的Java代码打包成库或者跑起来,顺便把测试和覆盖率也安排上。
我个人习惯把项目分成两类:一类是给别人用的库,一类是自己跑的应用。Gradle正好提供了两个对应的插件——java-library和application。它们俩的底层都依赖java插件,但各自有侧重点。
java-library插件:构建可复用的Java库
如果你在写一个SDK、工具类或者中间件,那java-library就是你的首选。它和java插件最大的区别在于——它引入了api和implementation这两个配置项。
我在项目中遇到过不少团队把依赖全写成implementation,结果下游引用方编译报错。为什么呢?因为implementation的依赖不会泄漏给消费者。你想想看,如果你的库内部用了Guava,但对外暴露的接口参数类型是Guava的类,那消费者就必须能访问到Guava。这时候就得用api。
plugins {
id 'java-library'
}
dependencies {
api 'com.google.guava:guava:32.1.3-jre'
implementation 'org.slf4j:slf4j-api:2.0.9'
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
}
嗯,这里要注意:api会让依赖传递出去,implementation则不会。这不仅仅是编译速度的问题——它直接决定了你的库的依赖管理是否干净。
核心区别:
api:依赖会暴露给消费者,编译期可见implementation:依赖仅在模块内部可见,消费者不需要知道
application插件:让Java程序跑起来
如果你写的是一个可执行的Java应用,比如一个Spring Boot服务或者一个命令行工具,那application插件就派上用场了。它帮你搞定两件事:打包可运行的发行包,以及提供gradle run命令直接启动。
plugins {
id 'application'
}
application {
mainClass = 'com.example.Main'
}
dependencies {
implementation 'org.slf4j:slf4j-api:2.0.9'
}
我曾经在一个微服务项目里用application插件配合distZip任务,直接生成一个包含所有依赖的zip包,部署到服务器上解压就能跑。省去了手动拷贝jar包的麻烦。
你还可以通过applicationDefaultJvmArgs设置默认JVM参数,比如内存大小或者GC策略。这样团队里所有人都用同一套启动参数,避免「我本地能跑啊」的尴尬。
application {
applicationDefaultJvmArgs = ['-Xmx512m', '-XX:+UseG1GC']
}
JUnit测试配置:从入门到实战
测试是Java项目里绕不开的一环。Gradle对JUnit的支持非常成熟,你只需要在test源码集里写测试类,然后执行gradle test就行。
但这里有个坑——我见过不少项目测试跑不过,原因是测试报告路径被覆盖了,或者测试日志被吞了。我个人习惯在build.gradle里显式配置一下测试任务:
tasks.named('test') {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
showStandardStreams = true
}
reports {
html.required.set(true)
junitXml.required.set(true)
}
}
这样配置之后,你跑gradle test就能在终端看到每个测试的通过/失败状态,同时HTML报告会生成在build/reports/tests/test目录下。打开那个HTML文件,你可以看到每个测试类的详细结果,包括失败堆栈。
小技巧:如果你用JUnit 5,记得加上useJUnitPlatform()。否则Gradle默认用JUnit 4的引擎,你的@Test注解可能不会被识别。
JaCoCo覆盖率:看看你的测试到底覆盖了多少
测试写完了,但你知道哪些代码没被覆盖到吗?JaCoCo就是干这个的。它和Gradle集成非常简单,加个插件配置就行。
plugins {
id 'jacoco'
}
jacoco {
toolVersion = "0.8.11"
}
tasks.named('test') {
finalizedBy jacocoTestReport
}
tasks.named('jacocoTestReport') {
dependsOn tasks.named('test')
reports {
xml.required.set(true)
html.required.set(true)
}
}
跑完gradle test之后,再执行gradle jacocoTestReport,就会在build/reports/jacoco/test/html下生成一个漂亮的HTML报告。你可以点开看看,哪些类、哪些方法、甚至哪些行没有被覆盖到。
我记得有一次在代码评审时,同事说「这个分支逻辑我测过了」,结果JaCoCo报告一出来,那个分支压根没走到。从那以后,我们团队就把覆盖率报告作为CI流水线的必过环节。
注意:JaCoCo默认只统计test任务执行的测试。如果你有集成测试或者其他自定义测试任务,需要额外配置JaCoCo的executionData。
知识体系结构图
下面这张图展示了本章涉及的核心插件和它们之间的关系:
实战:一个完整的Java库项目
把上面这些知识点串起来,一个典型的build.gradle长这样:
plugins {
id 'java-library'
id 'jacoco'
}
group = 'com.example'
version = '1.0.0'
repositories {
mavenCentral()
}
dependencies {
api 'com.google.guava:guava:32.1.3-jre'
implementation 'org.slf4j:slf4j-api:2.0.9'
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
}
tasks.named('test') {
useJUnitPlatform()
finalizedBy jacocoTestReport
}
tasks.named('jacocoTestReport') {
dependsOn tasks.named('test')
reports {
xml.required.set(true)
html.required.set(true)
}
}
跑一下gradle build,Gradle会自动执行编译、测试、生成JaCoCo报告。你可以在build/reports目录下找到所有产出。
避坑指南:我曾经在升级Gradle版本时忘了更新JaCoCo版本,结果报告生成失败。建议把jacoco.toolVersion显式指定,不要依赖默认值。
好了,这一章的内容就到这里。你可以在项目里试试这些配置,看看api和implementation的区别,跑一下gradle test和gradle jacocoTestReport,感受一下Gradle的自动化构建能力。