
別慌這個報錯我見過太多次了。Execution failed for task :xxxx-api:test. No tests found for given includes:光是Gradle疑難雜癥列表里它就屬于出場率最高的那一檔??雌饋硐馟radle在跟你說“沒找到測試”但絕大多數(shù)時候不是“真的沒有測試”而是“測試在Gradle不認”。今天把這幾年踩坑攢下來的排查思路、修復寫法、團隊規(guī)范一次性整理出來希望對剛接手Gradle工程或者CI里被這個紅牌卡住的同學有點用。1. 錯誤現(xiàn)場與問題本質1.1 報錯信息可以拆成三個部分先別急著改代碼把報錯拆開看。Execution failed for task :xxxx-api:test說明是xxxx-api模塊的test任務執(zhí)行失敗冒號后面緊跟的No tests found for given includes: [模式]才是真正的原因。注意given includes這幾個字這里的“includes”是Gradle測試掃描的過濾條件一般來自三個地方命令行里的--tests參數(shù)比如本地調試時執(zhí)行過./gradlew test --tests com.example.FooTestbuild.gradle里test任務手動配置的include、filter { includeTestsMatching ... }IDEA或其它IDE點擊“運行測試”時自動生成并透傳給Gradle的過濾參數(shù)如果你在命令行敲一個并不存在的--tests模式或者IDE里選中的目標類有變化Gradle就會直接拋出這個錯誤。我以前在IDEA里運行某個測試然后改了測試類文件名再跑去命令行跑整個test任務報錯也是它。所以第一步永遠是先確認這個include是誰給的給了什么1.2 “找不到測試”的真實鏈路Gradle定位測試類不是直接去src/test/java里數(shù).java文件而是走一條“編譯 → 掃描 → 匹配 → 執(zhí)行”的鏈路compileTestJava把測試源碼編譯為.class文件輸出到build/classes/java/testtest任務從編譯產物目錄收集所有.class按includes模式過濾Gradle默認自帶三種模式**/*Test.class、**/*Tests.class、**/*TestCase.class將匹配到的類交給測試引擎JUnit 4或JUnit Platform引擎再從中識別具體的Test方法鏈路里任何一個環(huán)節(jié)出錯結果都可能是“No tests found”。換句話說這個報錯不是單點原因是一類問題的統(tǒng)稱。有人是編譯目錄里壓根沒有class文件有人是命名不對有人是JUnit引擎沒接上還有人純粹是命令行模式寫錯。下面這六類原因基本覆蓋了我遇到過的所有情況。2. 最容易踩中的六類原因2.1 測試類命名不在默認include規(guī)則內Gradle對“哪些類算測試類”有一套默認匹配規(guī)則核心就是上面提過的三個模式以Test、Tests、TestCase結尾。也就是說UserServiceTest合法UserServiceTests合法UserServiceTestCase合法UserServiceCheck不行UserServiceIntegration不行TestUserService也不行很多團隊從Maven遷移到Gradle時最容易踩這個坑。Maven Surefire的默認包含規(guī)則是**/Test*.java、**/*Test.java、**/*TestCase.java它支持Test開頭的類名Gradle默認規(guī)則里沒有**/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任務根本不會掃描想?yún)^(qū)分單元測試和集成測試自己建了個src/integrationTest/java目錄但沒有配對應的sourceSets和Test任務項目是Java和Kotlin混編測試類放在src/test/kotlin但Java插件對src/test/java和src/test/kotlin的SourceSet處理不一致導致部分文件沒進compileTestJava遇到懷疑是目錄問題先執(zhí)行./gradlew :xxxx-api:sourceSets在輸出里找到test這個SourceSet看它的java.srcDirs到底指向哪。通常就能發(fā)現(xiàn)問題。2.3 測試框架引擎沒有接上這一條是我遇到次數(shù)最多的尤其在新建模塊的時候。Gradle本身不“認識”Test注解它只是把候選class文件交給測試引擎去執(zhí)行。你的測試類如果用JUnit 5的org.junit.jupiter.api.Test寫但test任務沒有啟用JUnit PlatformGradle默認按JUnit 4的路子去找運行器找了一圈發(fā)現(xiàn)沒有結果就是零測試。典型配置長這樣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相關異常表現(xiàn)出來也可能是一次“找不到測試”。所以我的建議是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目錄不存在或為空編譯任務狀態(tài)是NO-SOURCE某個模塊的sourceSets配置被改過測試源碼目錄被移除了測試類自身依賴的類在測試編譯期報編譯錯誤但錯誤被某種方式掩蓋了比如增量編譯緩存異常遇到這種情況直接去產物目錄翻一遍find build/classes/java/test -name *Test.class如果目錄列表里沒有預期的class文件基本可以斷定是編譯階段的問題。再執(zhí)行./gradlew :xxxx-api:compileTestJava --info看看編譯任務是不是NO-SOURCE輸出里會寫得很清楚。2.6 測試類自身形態(tài)特殊還有一小部分情況問題出在測試類本身的寫法上。比如類被定義成abstract測試引擎無法實例化直接跳過類不是public并且用的是JUnit 4JUnit 4要求測試類必須是public的方法上掛著Test但導的是org.junit.jupiter.api.Test而項目實際用的是JUnit 4引擎不認這個注解測試方法所在的類是非靜態(tài)內部類默認掃描不會把它當候選測試類這類問題通常在IDE里跑是好的一上命令行就露餡。因為IDE會直接運行當前文件而Gradle需要按掃描規(guī)則自己去找。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說明源碼和編譯鏈路正常問題在后續(xù)的掃描匹配沒有class或數(shù)量不對問題在編譯/源碼目錄這一層。這個命令比任何花里胡哨的日志分析都直接十秒鐘定位到底該看哪一層。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看現(xiàn)場到了這一步還沒定位就打開testLogging看現(xiàn)場。在build.gradle的test塊里臨時配置test { useJUnitPlatform() testLogging { events started, passed, skipped, failed showExceptions true showCauses true showStackTraces true showStandardStreams true } }然后執(zhí)行./gradlew :xxxx-api:test --info。如果Gradle識別了測試類但執(zhí)行數(shù)為0日志里通常會有線索如果根本沒進入執(zhí)行階段日志會明確告訴你掃描到的測試事件為零。這塊配置記得在排查完后移除或收斂不然CI日志會刷得很嚇人。4. 三個真實場景的修復過程4.1 場景一JUnit 5測試類被當成“路人甲”某次新模塊提交的測試代碼UserServiceTest寫在src/test/java下類名合規(guī)注解也是org.junit.jupiter.api.Test依賴里也放了junit-jupiter但執(zhí)行test任務依然報No tests found for given includes: [com.example.UserServiceTest]。排查到build.gradle時發(fā)現(xiàn)test塊里只有幾行systemProperty配置沒有useJUnitPlatform()。因為這是從老工程拷貝出來的模板老工程模板是JUnit 4時代的寫法。修復很簡單test { useJUnitPlatform() }加上這一行之后再跑一次就正常了。這個模塊之所以報錯而不是“安靜地跑0個測試”正是因為命令行帶了--tests指定了單個測試類過濾條件命中但引擎不認。如果是裸跑test可能連失敗都沒有會被直接誤判成“正常通過”。4.2 場景二--tests通配符與包名不匹配有次CI報錯日志里的include模式是[com.example.api.**.*Test]一聽就是個“復合形”寫法。實際執(zhí)行./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就是最穩(wěn)的寫法。4.3 場景三自定義測試任務與sourceSet錯位一個工程里劃了unitTest和integrationTest兩個測試任務。integrationTest對應目錄是src/integrationTest/java但新人建模塊時只建了目錄沒配sourceSets于是CI執(zhí)行./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)) }配好之后再執(zhí)行compileIntegrationTestJava才會生成產物integrationTest任務也能掃到真正的測試類。5. 建團隊規(guī)范時值得做的事5.1 把測試任務配置模板化“No tests found”最常見的原因就是配置不統(tǒng)一。每個新模塊都要靠人肉記住加useJUnitPlatform()那遲早會漏。我建議在根工程里統(tǒng)一兜底subprojects { tasks.withType(Test).configureEach { useJUnitPlatform() testLogging { events failed, skipped, standardError, standardOut exceptionFormat full showStandardStreams false } failOnNoTests false } }所有子模塊自動啟用JUnit Platform測試日志風格也統(tǒng)一既減少漏配也方便排查。有人問failOnNoTests是干嘛的稍后單獨講。5.2 讓CI盡早暴露“無測試”問題Gradle的Test任務有個屬性叫failOnNoTests默認false。默認情況下一個模塊如果有src/test/java但一個測試類都沒有或者測試類全沒被掃描到test任務可能以NO-SOURCE或0測試的結果“成功”結束CI是綠的但沒人發(fā)現(xiàn)測試根本沒跑。這比報錯更危險。把failOnNoTests設成true會讓“沒有測試”或“掃描不到測試”變成顯式失敗test { failOnNoTests true }這樣團隊就會被迫處理“測試缺失”而不是放任不管。當然也要權衡如果確實有些模塊沒有單測且不打算補就得給這些模塊單獨關掉這個開關否則CI一直紅著。我的習慣是框架模塊和工具包模塊關掉業(yè)務模塊強制打開。5.3 把診斷經驗沉淀成查表團隊里新人遇到這個報錯第一反應是滿世界搜。不如直接把排查順序寫進文檔先看include來源、再看編譯產物、然后核命名和依賴、最后用--tests模糊匹配。配合第6節(jié)的速查表新人在五分鐘內能自己解決大部分問題不用每次都艾特你。6. 常見問題速查表報錯表現(xiàn)通常原因排查/修復建議命令行指定--tests后報No tests類名/包名/通配符寫錯先放大范圍--tests com.example.*裸跑test任務報No testsbuild.gradle里的include/filter配置異常檢查test塊的include、filter { includeTestsMatching }新模塊測試全部找不到忘記useJUnitPlatform()test塊加useJUnitPlatform()依賴補junit-jupiter-engineIDEA能跑命令行跑不了IDE傳入的過濾參數(shù)與命令行不同檢查IDEA Run Configuration里的--tests參數(shù)build/classes/java/test下沒classcompileTestJava沒有產出檢查sourceSets確認src/test/java存在且被識別測試類有Test但引擎不認導包錯誤或引擎依賴缺失確認注解是org.junit.jupiter.api.Test還是org.junit.Test多個自定義Test任務找不到類sourceSet和測試任務沒配對用testClassesDirs和classpath顯式指定Java 16環(huán)境異常缺少junit-platform-launcher增加testRuntimeOnly org.junit.platform:junit-platform-launcher這個表看著簡單但基本覆蓋了日常90%的場景。如果表格里沒有你的情況再回頭走一遍第3節(jié)的完整流程一定能在鏈路里找到斷點。最后一點個人體會這個報錯我前前后后處理過不下二十次九成以上不是代碼問題而是配置或命名問題。遇到它真的不用慌記住一條主線Gradle要先編譯出class再按include模式去匹配最后交給測試引擎執(zhí)行斷在哪一步就修哪一步。比起一遍遍刷--info日志我更推薦先去build/classes/java/test目錄下看一眼有沒有class文件十秒就能分成兩派——是沒編譯出來還是沒被掃描到。排查完再把配置模板化把useJUnitPlatform()這類基礎設置固化到根工程里以后新模塊基本不會再犯同樣的錯。如果非讓我只留一個技巧那就是裸跑./gradlew :xxxx-api:test看狀態(tài)再帶--tests com.example.**跑一次兩次結果對比一下問題定位立刻清楚一大半。