25、Gradle测试与代码检查:testTask配置、单元测试与插桩测试、JaCoCo覆盖率、Detekt静态分析、Lint配置
说实话,很多团队把测试和代码检查当成「上线前的例行公事」。点一下按钮,跑一遍,绿了就过。但在我眼里,这一章才是真正决定代码质量的分水岭。你想想看,一个项目跑了两三年,如果没有自动化的测试和静态分析,那代码库迟早变成「屎山」。今天我就把Gradle里测试和代码检查的整套玩法,掰开了讲给你听。
25.1 testTask配置:掌控测试执行的生命周期
Gradle里的测试任务,核心就是Test这个类型。每个sourceSet都会自动生成一个对应的Test任务,比如test对应src/test,testDebug对应src/testDebug。我个人习惯,拿到一个新项目,第一件事就是打开build.gradle.kts,看看测试任务的配置。
// 配置单元测试任务
tasks.named<Test>("test") {
useJUnitPlatform() // 使用JUnit5
testLogging {
events("passed", "skipped", "failed")
showStandardStreams = true
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
}
maxParallelForks = Runtime.getRuntime().availableProcessors() / 2
forkEvery = 100 // 每100个测试方法重启一次JVM
}
这里有个坑,我曾经遇到过:maxParallelForks设得太大,结果CI服务器上内存爆了。后来我学乖了,一般取CPU核心数的一半,再结合forkEvery控制JVM重启频率。说白了,测试并行度不是越大越好,得看你的测试有没有共享资源。
testFilter,可以只跑特定的测试类。比如filter { includeTestsMatching("*Login*") },调试时特别方便。
25.2 单元测试与插桩测试:两种测试,两种配置
Android项目里,测试分两大类:单元测试(Unit Test)和插桩测试(Instrumented Test)。单元测试跑在本地JVM上,速度快;插桩测试跑在真机或模拟器上,需要Android环境。
单元测试的配置,我刚才已经展示了。插桩测试呢?它对应的是connectedCheck任务链。你可以在defaultConfig里指定测试运行器:
android {
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
// 如果需要指定测试参数
testInstrumentationRunnerArguments["clearPackageData"] = "true"
}
}
嗯,这里要注意:插桩测试的依赖通常放在androidTestImplementation里,别放错了。我曾经见过一个同事把Espresso的依赖放到了testImplementation里,结果跑单元测试时疯狂报ClassNotFoundException。
| 测试类型 | 配置域 | 运行环境 | 典型任务 |
|---|---|---|---|
| 单元测试 | testImplementation |
本地JVM | test, testDebug |
| 插桩测试 | androidTestImplementation |
真机/模拟器 | connectedCheck, connectedDebugAndroidTest |
25.3 JaCoCo覆盖率:别让测试变成「假绿」
测试覆盖率这个东西,我一直觉得是「及格线」,不是「满分线」。80%的覆盖率不代表代码没bug,但20%的覆盖率一定说明你偷懒了。JaCoCo是Java/Kotlin生态里最主流的覆盖率工具,Gradle对它支持得很好。
启用JaCoCo很简单,先在build.gradle.kts里应用插件:
plugins {
id("jacoco")
}
// 配置JaCoCo版本
jacoco {
toolVersion = "0.8.11"
}
// 自定义覆盖率报告任务
tasks.register<JacocoReport>("jacocoTestReport") {
dependsOn("testDebugUnitTest")
reports {
xml.required.set(true)
html.required.set(true)
csv.required.set(false)
}
// 指定要分析的源码和类文件
sourceDirectories.setFrom(files("$projectDir/src/main/java"))
classDirectories.setFrom(
fileTree("$buildDir/tmp/kotlin-classes/debug") {
exclude("**/R.class", "**/BuildConfig.class")
}
)
executionData.setFrom(fileTree("$buildDir/jacoco") { include("*.exec") })
}
tmp/kotlin-classes下,Java的在intermediates/javac下。两个目录都要配进去。
跑完./gradlew jacocoTestReport,打开build/reports/jacoco/html/index.html,就能看到详细的覆盖率报告。我个人习惯在CI流水线里加一个覆盖率阈值检查,低于80%直接构建失败:
tasks.register<JacocoCoverageVerification>("jacocoCoverageVerification") {
violationRules {
rule {
limit {
minimum = "0.8".toBigDecimal()
}
}
}
}
25.4 Detekt静态分析:Kotlin代码的「照妖镜」
Detekt是专门为Kotlin设计的静态分析工具。它比Lint更懂Kotlin的语法特性,比如空安全、协程、密封类。我刚开始用Detekt时,被它报的几百个警告吓到了——原来我的Kotlin代码写得这么「Java风」。
配置Detekt也很简单:
plugins {
id("io.gitlab.arturbosch.detekt").version("1.23.5")
}
detekt {
buildUponDefaultConfig = true // 在默认规则基础上扩展
allRules = false // 不启用所有规则(有些太严格)
config.setFrom(files("$rootDir/config/detekt/detekt.yml"))
baseline = file("$rootDir/config/detekt/baseline.xml")
}
tasks.withType<Detekt>().configureEach {
reports {
html.required.set(true)
xml.required.set(true)
txt.required.set(false)
}
}
你可以在detekt.yml里自定义规则。比如我团队里就禁用了MagicNumber规则,因为业务代码里很多常量是合理的。Detekt的规则分几个层级:
- code-smell:代码坏味道,比如过长方法、过多参数
- complexity:复杂度相关,比如圈复杂度、嵌套深度
- style:代码风格,比如命名规范、空格缩进
- performance:性能相关,比如不必要的对象创建
- potential-bug:潜在bug,比如未处理的异常
detektBaseline),把现有问题标记为「已知」,然后逐步修复。否则团队会炸毛的。
25.5 Lint配置:Android原生的代码检查
Lint是Android SDK自带的静态分析工具,它主要检查Android相关的潜在问题:布局性能、资源冗余、权限声明、国际化等等。说实话,Lint和Detekt是互补的,一个管Android生态,一个管Kotlin语法。
配置Lint,通常在android块里:
android {
lint {
// 检查严重级别
checkReleaseBuilds = false
abortOnError = true
warningsAsErrors = true
// 忽略特定问题
disable.add("MissingTranslation")
disable.add("GoogleAppIndexingWarning")
// 自定义检查配置
lintConfig = file("$rootDir/config/lint/lint.xml")
}
}
在lint.xml里,你可以更精细地控制:
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<!-- 忽略所有模块的MissingTranslation -->
<issue id="MissingTranslation" severity="ignore" />
<!-- 将UnusedResources提升为error -->
<issue id="UnusedResources" severity="error" />
</lint>
跑./gradlew lint,报告会生成在build/reports/lint-results.html。我个人习惯把Lint和Detekt都集成到pre-commit钩子里,提交代码前先过一遍,省得CI上反复失败。
25.6 知识体系总览
这一章的内容比较多,我画了一张图帮你理清关系:
这张图把五个核心模块串起来了。从上到下,先配置测试任务,再区分单元测试和插桩测试,然后通过JaCoCo看覆盖率,最后用Detekt和Lint做静态分析。说白了,这就是一套完整的「代码质量流水线」。
好了,这一章的内容就到这里。测试和代码检查,说白了就是「防患于未然」。你投入的时间,最终都会在线上bug的数量上体现出来。记住,好的代码不是写出来的,是「测」和「查」出来的。
公众号:蓝海资料掘金营,微信deep3321