ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Gradle测试报错 No tests found?排查思路与修复方案

Gradle测试报错 No tests found?排查思路与修复方案 别慌这个报错我见过太多次了。Execution failed for task :xxxx-api:test. No tests found for given includes:光是Gradle疑难杂症列表里它就属于出场率最高的那一档。看起来像Gradle在跟你说“没找到测试”但绝大多数时候不是“真的没有测试”而是“测试在Gradle不认”。今天把这几年踩坑攒下来的排查思路、修复写法、团队规范一次性整理出来希望对刚接手Gradle工程或者CI里被这个红牌卡住的同学有点用。1. 错误现场与问题本质1.1 报错信息可以拆成三个部分先别急着改代码把报错拆开看。Execution failed for task :xxxx-api:test说明是xxxx-api模块的test任务执行失败冒号后面紧跟的No tests found for given includes: [模式]才是真正的原因。注意given includes这几个字这里的“includes”是Gradle测试扫描的过滤条件一般来自三个地方命令行里的--tests参数比如本地调试时执行过./gradlew test --tests com.example.FooTestbuild.gradle里test任务手动配置的include、filter { includeTestsMatching ... }IDEA或其它IDE点击“运行测试”时自动生成并透传给Gradle的过滤参数如果你在命令行敲一个并不存在的--tests模式或者IDE里选中的目标类有变化Gradle就会直接抛出这个错误。我以前在IDEA里运行某个测试然后改了测试类文件名再跑去命令行跑整个test任务报错也是它。所以第一步永远是先确认这个include是谁给的给了什么1.2 “找不到测试”的真实链路Gradle定位测试类不是直接去src/test/java里数.java文件而是走一条“编译 → 扫描 → 匹配 → 执行”的链路compileTestJava把测试源码编译为.class文件输出到build/classes/java/testtest任务从编译产物目录收集所有.class按includes模式过滤Gradle默认自带三种模式**/*Test.class、**/*Tests.class、**/*TestCase.class将匹配到的类交给测试引擎JUnit 4或JUnit Platform引擎再从中识别具体的Test方法链路里任何一个环节出错结果都可能是“No tests found”。换句话说这个报错不是单点原因是一类问题的统称。有人是编译目录里压根没有class文件有人是命名不对有人是JUnit引擎没接上还有人纯粹是命令行模式写错。下面这六类原因基本覆盖了我遇到过的所有情况。2. 最容易踩中的六类原因2.1 测试类命名不在默认include规则内Gradle对“哪些类算测试类”有一套默认匹配规则核心就是上面提过的三个模式以Test、Tests、TestCase结尾。也就是说UserServiceTest合法UserServiceTests合法UserServiceTestCase合法UserServiceCheck不行UserServiceIntegration不行TestUserService也不行很多团队从Maven迁移到Gradle时最容易踩这个坑。Maven Surefire的默认包含规则是**/Test*.java、**/*Test.java、**/*TestCase.java它支持Test开头的类名Gradle默认规则里没有**/Test*.class这一条所以TestUserService这种在Maven下能正常跑的测试类到了Gradle里就是透明人。另外类名匹配是大小写敏感的userServiceTest和UserServiceTest看起来差不多Gradle只认后者。这条原因虽然基础但排查成本最低建议第一个看。2.2 测试代码放错了目录命名没问题也要看文件放没放在Gradle约定的位置。Java插件的test任务只扫描src/test/java和src/test/resources这两个目录Kotlin插件则是src/test/kotlin。常见翻车场景测试类写进了src/main/java它会被编译到main的产物目录test任务根本不会扫描想区分单元测试和集成测试自己建了个src/integrationTest/java目录但没有配对应的sourceSets和Test任务项目是Java和Kotlin混编测试类放在src/test/kotlin但Java插件对src/test/java和src/test/kotlin的SourceSet处理不一致导致部分文件没进compileTestJava遇到怀疑是目录问题先执行./gradlew :xxxx-api:sourceSets在输出里找到test这个SourceSet看它的java.srcDirs到底指向哪。通常就能发现问题。2.3 测试框架引擎没有接上这一条是我遇到次数最多的尤其在新建模块的时候。Gradle本身不“认识”Test注解它只是把候选class文件交给测试引擎去执行。你的测试类如果用JUnit 5的org.junit.jupiter.api.Test写但test任务没有启用JUnit PlatformGradle默认按JUnit 4的路子去找运行器找了一圈发现没有结果就是零测试。典型配置长这样dependencies { testImplementation org.junit.jupiter:junit-jupiter:5.10.2 testRuntimeOnly org.junit.platform:junit-platform-launcher } test { // 这里如果忘了写 useJUnitPlatform() }依赖里有junit-jupiter但test块里没有useJUnitPlatform()等于引擎和任务完全对不上。还有一个和Java版本相关的坑Java 16以上如果只用junit-jupiter不带junit-platform-launcher有些Gradle版本会报Launcher相关异常表现出来也可能是一次“找不到测试”。所以我的建议是JUnit 5工程里useJUnitPlatform()和junit-platform-launcher这两个东西能写多早就写多早。2.4 命令行或脚本里的过滤条件写歪了这就是given includes的直接来源。常见错误写法./gradlew :xxxx-api:test --tests com.example.UserServiceTests模块里明明只有UserServiceTest类名多了一个sGradle按UserServiceTests去匹配一个都捞不到直接报错。还有人喜欢给--tests加.*后缀./gradlew :xxxx-api:test --tests com.example.UserServiceTest.*这种写法通常也匹配不到Gradle的过滤模式是匹配类名路径不是匹配方法。想跑某个包下的全部测试直接写--tests com.example.*或--tests com.example.**就行。*匹配当前包内的类名**还能往下穿子包这个通配符语义比Ant风格还好用。2.5 测试类根本没有被编译出来这条最隐蔽因为表面上“没有测试类”实际上是因为compileTestJava压根没有产出build/classes/java/test下的class文件。原因可能是src/test/java目录不存在或为空编译任务状态是NO-SOURCE某个模块的sourceSets配置被改过测试源码目录被移除了测试类自身依赖的类在测试编译期报编译错误但错误被某种方式掩盖了比如增量编译缓存异常遇到这种情况直接去产物目录翻一遍find build/classes/java/test -name *Test.class如果目录列表里没有预期的class文件基本可以断定是编译阶段的问题。再执行./gradlew :xxxx-api:compileTestJava --info看看编译任务是不是NO-SOURCE输出里会写得很清楚。2.6 测试类自身形态特殊还有一小部分情况问题出在测试类本身的写法上。比如类被定义成abstract测试引擎无法实例化直接跳过类不是public并且用的是JUnit 4JUnit 4要求测试类必须是public的方法上挂着Test但导的是org.junit.jupiter.api.Test而项目实际用的是JUnit 4引擎不认这个注解测试方法所在的类是非静态内部类默认扫描不会把它当候选测试类这类问题通常在IDE里跑是好的一上命令行就露馅。因为IDE会直接运行当前文件而Gradle需要按扫描规则自己去找。3. 十分钟定位法一套标准排查流程3.1 先看错误里的“given includes”是谁传的拿到报错先别动代码读完整输出。No tests found for given includes: [com.example.FooTest]里的com.example.FooTest就是你该对的第一条线索。如果是命令行传入的看下是不是自己手滑写错了如果是IDEA自动生成的看下是不是改了类名或包名之后没刷新运行配置如果是build.gradle里配的filter去把那一段改掉或注释掉再试。这一步能滤掉一大半问题。我见过有人CI脚本里--tests com.example.*的包名在某个模块里压根不存在每次全量构建必挂最后就是从报错里的include模式溯源改掉的。3.2 再确认编译产物里有没有测试类如果include模式没问题下一步去确认“目标是否存在”。./gradlew :xxxx-api:compileTestJava find build/classes/java/test -name *.class | head -20有class说明源码和编译链路正常问题在后续的扫描匹配没有class或数量不对问题在编译/源码目录这一层。这个命令比任何花里胡哨的日志分析都直接十秒钟定位到底该看哪一层。3.3 接着核对命名、注解与依赖确认class存在后依次回答三个问题类名是否以Test、Tests、TestCase结尾类里的Test方法是否真实存在还是只有BeforeAll这种生命周期注解类路径里有没有对应的测试引擎依赖依赖这一块可以直接输./gradlew :xxxx-api:dependencies --configuration testRuntimeClasspath看输出里有没有junit-jupiter-engine或者junit-vintage-engine。我遇到过一次测试类写了Test但依赖里只有junit-jupiter-api没有junit-jupiter-engine编译能过运行一个测试都找不到。3.4 用三个命令缩小问题范围写一条完整命令去跑大概率还会报错但换着缩范围试# 用完整类名 ./gradlew :xxxx-api:test --tests com.example.UserServiceTest # 用包名 ./gradlew :xxxx-api:test --tests com.example.* # 全包扫描 ./gradlew :xxxx-api:test --tests com.example.**如果从完整类名失败到包名成功说明类名或包路径不对如果包名也失败说明整个testSourceSet就没扫描到这些class。这种由精确到模糊的测试方法基本能把问题圈在一个很小的范围里。3.5 临时打开testLogging看现场到了这一步还没定位就打开testLogging看现场。在build.gradle的test块里临时配置test { useJUnitPlatform() testLogging { events started, passed, skipped, failed showExceptions true showCauses true showStackTraces true showStandardStreams true } }然后执行./gradlew :xxxx-api:test --info。如果Gradle识别了测试类但执行数为0日志里通常会有线索如果根本没进入执行阶段日志会明确告诉你扫描到的测试事件为零。这块配置记得在排查完后移除或收敛不然CI日志会刷得很吓人。4. 三个真实场景的修复过程4.1 场景一JUnit 5测试类被当成“路人甲”某次新模块提交的测试代码UserServiceTest写在src/test/java下类名合规注解也是org.junit.jupiter.api.Test依赖里也放了junit-jupiter但执行test任务依然报No tests found for given includes: [com.example.UserServiceTest]。排查到build.gradle时发现test块里只有几行systemProperty配置没有useJUnitPlatform()。因为这是从老工程拷贝出来的模板老工程模板是JUnit 4时代的写法。修复很简单test { useJUnitPlatform() }加上这一行之后再跑一次就正常了。这个模块之所以报错而不是“安静地跑0个测试”正是因为命令行带了--tests指定了单个测试类过滤条件命中但引擎不认。如果是裸跑test可能连失败都没有会被直接误判成“正常通过”。4.2 场景二--tests通配符与包名不匹配有次CI报错日志里的include模式是[com.example.api.**.*Test]一听就是个“复合形”写法。实际执行./gradlew :xxxx-api:test --tests com.example.api.**.*TestGradle把**.*Test当成了带点的文件名匹配结果自然命中不了。改成./gradlew :xxxx-api:test --tests com.example.api.**就能把该模块下所有测试都捞出来。这里给个经验--tests后面写包名或类名片段都可以但别把.class后缀、*Test这种文件通配符硬拼进去。想指定某类测试--tests com.example.api.UserControllerTest就是最稳的写法。4.3 场景三自定义测试任务与sourceSet错位一个工程里划了unitTest和integrationTest两个测试任务。integrationTest对应目录是src/integrationTest/java但新人建模块时只建了目录没配sourceSets于是CI执行./gradlew :xxxx-api:integrationTest --tests com.example.OrderIntegrationTest直接报No tests found。跑到build/classes/java/integrationTest一看目录是空的压根没编译。修复方案是在模块的build.gradle补上对应的源码集和测试任务sourceSets { integrationTest { java.srcDir $projectDir/src/integrationTest/java resources.srcDir $projectDir/src/integrationTest/resources compileClasspath sourceSets.main.output runtimeClasspath sourceSets.main.output } } configurations { integrationTestImplementation.extendsFrom testImplementation integrationTestRuntimeOnly.extendsFrom testRuntimeOnly } tasks.register(integrationTest, Test) { description Runs integration tests. group verification testClassesDirs sourceSets.integrationTest.output.classesDirs classpath sourceSets.integrationTest.runtimeClasspath shouldRunAfter(tasks.named(test)) }配好之后再执行compileIntegrationTestJava才会生成产物integrationTest任务也能扫到真正的测试类。5. 建团队规范时值得做的事5.1 把测试任务配置模板化“No tests found”最常见的原因就是配置不统一。每个新模块都要靠人肉记住加useJUnitPlatform()那迟早会漏。我建议在根工程里统一兜底subprojects { tasks.withType(Test).configureEach { useJUnitPlatform() testLogging { events failed, skipped, standardError, standardOut exceptionFormat full showStandardStreams false } failOnNoTests false } }所有子模块自动启用JUnit Platform测试日志风格也统一既减少漏配也方便排查。有人问failOnNoTests是干嘛的稍后单独讲。5.2 让CI尽早暴露“无测试”问题Gradle的Test任务有个属性叫failOnNoTests默认false。默认情况下一个模块如果有src/test/java但一个测试类都没有或者测试类全没被扫描到test任务可能以NO-SOURCE或0测试的结果“成功”结束CI是绿的但没人发现测试根本没跑。这比报错更危险。把failOnNoTests设成true会让“没有测试”或“扫描不到测试”变成显式失败test { failOnNoTests true }这样团队就会被迫处理“测试缺失”而不是放任不管。当然也要权衡如果确实有些模块没有单测且不打算补就得给这些模块单独关掉这个开关否则CI一直红着。我的习惯是框架模块和工具包模块关掉业务模块强制打开。5.3 把诊断经验沉淀成查表团队里新人遇到这个报错第一反应是满世界搜。不如直接把排查顺序写进文档先看include来源、再看编译产物、然后核命名和依赖、最后用--tests模糊匹配。配合第6节的速查表新人在五分钟内能自己解决大部分问题不用每次都艾特你。6. 常见问题速查表报错表现通常原因排查/修复建议命令行指定--tests后报No tests类名/包名/通配符写错先放大范围--tests com.example.*裸跑test任务报No testsbuild.gradle里的include/filter配置异常检查test块的include、filter { includeTestsMatching }新模块测试全部找不到忘记useJUnitPlatform()test块加useJUnitPlatform()依赖补junit-jupiter-engineIDEA能跑命令行跑不了IDE传入的过滤参数与命令行不同检查IDEA Run Configuration里的--tests参数build/classes/java/test下没classcompileTestJava没有产出检查sourceSets确认src/test/java存在且被识别测试类有Test但引擎不认导包错误或引擎依赖缺失确认注解是org.junit.jupiter.api.Test还是org.junit.Test多个自定义Test任务找不到类sourceSet和测试任务没配对用testClassesDirs和classpath显式指定Java 16环境异常缺少junit-platform-launcher增加testRuntimeOnly org.junit.platform:junit-platform-launcher这个表看着简单但基本覆盖了日常90%的场景。如果表格里没有你的情况再回头走一遍第3节的完整流程一定能在链路里找到断点。最后一点个人体会这个报错我前前后后处理过不下二十次九成以上不是代码问题而是配置或命名问题。遇到它真的不用慌记住一条主线Gradle要先编译出class再按include模式去匹配最后交给测试引擎执行断在哪一步就修哪一步。比起一遍遍刷--info日志我更推荐先去build/classes/java/test目录下看一眼有没有class文件十秒就能分成两派——是没编译出来还是没被扫描到。排查完再把配置模板化把useJUnitPlatform()这类基础设置固化到根工程里以后新模块基本不会再犯同样的错。如果非让我只留一个技巧那就是裸跑./gradlew :xxxx-api:test看状态再带--tests com.example.**跑一次两次结果对比一下问题定位立刻清楚一大半。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表