Android Studio中TestNG+Gradle测试任务构建成功但未执行
测试框架未执行测试问题排查与修复
问题背景
基于Java、TestNG、Appium、Gradle搭建的Android测试项目,运行自定义testRun任务时构建显示成功,但实际未执行任何测试,Gradle报告更新但无测试结果,使用Android模拟器运行测试。
项目结构
project-root │ ├── .gradle ├── .idea ├── app │ ├── apk │ ├── build │ ├── reports │ ├── screenshots │ └── src │ ├── main │ │ ├── java │ │ │ ├── com │ │ │ │ ├── utils │ │ │ │ │ └── (some classes) │ │ │ │ └── instagrid │ │ │ │ └── application │ │ │ │ └── (some classes - test steps) │ │ │ └── res │ │ └── test │ │ ├── java │ │ │ └── regression │ │ │ ├── (your test classes) │ │ │ └── regression.xml │ │ └── res ├── gradle └── (other project files)
关键配置文件
regression.xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd"> <suite name="instagrid" preserve-order="true"> <test name="Regression"> <classes> <class name="regression.LandingPageTests"/> <class name="regression.RegistrationTests"/> <class name="regression.LoginTests"/> <class name="regression.ProfileTests"/> <class name="regression.BatteryDetailsTests"/> <class name="regression.MapTests"/> <class name="regression.TranslationsTests"/> <class name="regression.HomePageTests"/> <!--<class name="regression.PairingTests"/>--> </classes> </test> </suite>
build.gradle
plugins { id 'com.android.application' } apply from: "testsetup.gradle" android { namespace 'com.example.instagrid' compileSdk 33 defaultConfig { applicationId "com.example.instagrid" minSdk 33 targetSdk 33 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } sourceSets { main.java.srcDirs = ['src/main/java'] main.resources.srcDirs = ['src/main/res'] test.java.srcDirs = ['src/test/java'] test.resources.srcDirs = ['src/test/res'] } } dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.8.0' implementation 'io.appium:java-client:7.3.0' implementation 'org.testng:testng:7.4.0' testImplementation project(path: ':app') androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' // Other dependencies implementation 'org.jsoup:jsoup:1.16.1' } task smokeTests(type: Test) { useTestNG() { useDefaultListeners = true includeGroups 'smoke' } filter { includeTestsMatching 'regression.xml' } }
testsetup.gradle
import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.gradle.api.tasks.testing.logging.TestLogEvent ext { env = 'uat' defaultTestPackage = "regression" } tasks.withType(Test) { classpath += files ( android.sourceSets.main.java.srcDirs, android.sourceSets.test.java.srcDirs ) useTestNG() { includeGroups project.hasProperty("groups") ? "$groups" : "none" } testLogging { showStandardStreams = true events = [TestLogEvent.STARTED, TestLogEvent.FAILED, TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.STANDARD_OUT, TestLogEvent.STANDARD_ERROR] exceptionFormat = TestExceptionFormat.FULL showCauses = true showExceptions = true showStackTraces = true showStandardStreams = true afterTest { desc, result -> println "\nTest executed ${desc.name} [${desc.className}] with result: ${result.resultType}" } afterSuite { desc, result -> if (!desc.parent) { // will match the outermost suite def output = "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} successes, ${result.failedTestCount} failures, ${result.skippedTestCount} skipped)" def startItem = '| ', endItem = ' |' def repeatLength = startItem.length() + output.length() + endItem.length() println('\n' + ('-' * repeatLength) + '\n' + startItem + output + endItem + '\n' + ('-' * repeatLength)) } } } enableAssertions = true scanForTestClasses = false options { systemProperties(System.getProperties()) systemProperty 'env', System.properties['env'] ?: "${env}" } } def generateTestRunArgs() { def resultDir = "$projectDir/app/reports" def suiteDir = "$projectDir/src/test/java/regression" def regressionXML = "$projectDir/src/test/java/regression.xml" def args = ["-d", resultDir, "-configfailurepolicy", "continue", regressionXML] if (project.hasProperty("defaultListeners")) { args += ["-usedefaultlisteners", "true"] } else { args += ["-usedefaultlisteners", "false", "-listener", "testng.listeners.TestExecListener"] } if (project.hasProperty("groups")) { args += ["-groups", "$groups"] } if (project.hasProperty("testclasses")) { args += ["-testclass", "$testclasses"] } if (project.hasProperty("testmethods")) { args += ["-methods", "$testmethods"] } if (project.hasProperty("parallel")) { args += ["-parallel", "$parallel"] } else { args += ["-parallel", "methods"] } if (project.hasProperty("threads")) { args += ["-threadcount", "$threads"] args += ["-suitethreadpoolsize", "$threads"] } else { args += ["-threadcount", "1"] args += ["-suitethreadpoolsize", "1"] } if (project.hasProperty("dataproviderthreads")) { args += ["-dataproviderthreadcount", "$dataproviderthreads"] } else { args += ["-dataproviderthreadcount", "1"] } return args } /** * Using the default gradle test task (limited options) * Usage: * $ gradle clean testRun --tests FeatureATest */ task testRun(type: Test) { dependsOn "clean" classpath += files( android.sourceSets.main.java.srcDirs, android.sourceSets.test.java.srcDirs ) scanForTestClasses = true forkEvery = 1 useTestNG() { useDefaultListeners = true includeGroups 'smoke' } testClassesDirs = files("src/test/java/regression") doLast { def args = generateTestRunArgs() println "TestNG args: " + args println "System property env value: ${System.properties.env}" println "env default: $env" } } /** * Using the default gradle test task (limited options) * Usage: * $ gradle testGroups -Pgroups=main * $ gradle testGroups -Pgroups=main,unit */ task testGroups(type: Test) { dependsOn "clean", "classes", "testClasses" useTestNG() { includeGroups project.hasProperty("groups") ? "$groups" : "none" } testLogging.events "passed", "skipped", "failed" testLogging.showStandardStreams = true testLogging.exceptionFormat = "full" }
执行结果
./gradlew clean testRun -Pgroups=smoke > Task :app:testRun -------------------------------------------------------------------- | Results: SUCCESS (0 tests, 0 successes, 0 failures, 0 skipped) | -------------------------------------------------------------------- TestNG args: [-d, D:\Workspace\instagrid\app/app/reports, -configfailurepolicy, continue, D:\Workspace\instagrid\app/src/test/java/regression.xml, -usedefaultlisteners, false, -listener, testng.listeners.TestExecListener, -groups, smoke, -parallel, methods, -threadcount, 1, -suitethreadpoolsize, 1, -dataproviderthreadcount, 1] System property env value: null env default: uat
核心原因分析
- 依赖配置错误:TestNG和Appium依赖使用
implementation而非testImplementation,导致测试类无法在测试类路径中访问这些库。 - 测试类路径与目录配置错误:
testRun任务中testClassesDirs指向源码目录src/test/java/regression,而非编译后的class文件目录;classpath仅添加了源码目录,未包含依赖库和编译后的主代码/测试代码class文件。 - 全局Test任务配置冲突:
tasks.withType(Test)中设置了scanForTestClasses = false,覆盖了testRun任务中scanForTestClasses = true的配置,导致Gradle不扫描测试类。 - TestNG套件XML未被加载:
generateTestRunArgs方法生成了TestNG参数,但未传递给Gradle的Test任务,Test任务默认不会使用该参数,而是依赖自身的suiteXmlFiles配置。 - 系统属性传递问题:
systemProperty 'env'未正确传递,导致测试代码可能无法获取环境变量。
修复步骤
1. 修正依赖配置
修改build.gradle中的依赖,将TestNG和Appium改为testImplementation:
dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.8.0' // 改为testImplementation testImplementation 'io.appium:java-client:7.3.0' testImplementation 'org.testng:testng:7.4.0' testImplementation project(path: ':app') androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' // Other dependencies implementation 'org.jsoup:jsoup:1.16.1' }
2. 调整全局Test任务配置
修改testsetup.gradle中的全局配置,移除scanForTestClasses = false,并补充编译后的class目录到类路径:
tasks.withType(Test) { classpath += files ( android.sourceSets.main.java.srcDirs, android.sourceSets.test.java.srcDirs, // 添加编译后的class目录 android.sourceSets.main.output.classesDirs, android.sourceSets.test.output.classesDirs ) useTestNG() { includeGroups project.hasProperty("groups") ? "$groups" : "none" } testLogging { // 保留原有日志配置... } enableAssertions = true // 移除全局的scanForTestClasses设置,让具体任务自行配置 // scanForTestClasses = false options { systemProperties(System.getProperties()) systemProperty 'env', project.findProperty('env') ?: env } }
3. 修复testRun任务配置
更新testsetup.gradle中的testRun任务,指向正确的class目录并加载TestNG套件XML:
task testRun(type: Test) { dependsOn "clean", "testClasses" // 依赖testClasses确保测试代码已编译 classpath = sourceSets.test.runtimeClasspath // 使用默认测试类路径,包含所有依赖和编译后的类 scanForTestClasses = true forkEvery = 1 useTestNG() { useDefaultListeners = true includeGroups project.hasProperty("groups") ? "$groups" : "smoke" // 加载TestNG套件XML suiteXmlFiles = files("$projectDir/src/test/java/regression/regression.xml") } // 指向编译后的测试类目录 testClassesDirs = sourceSets.test.output.classesDirs doLast { def args = generateTestRunArgs() println "TestNG args: " + args println "System property env value: ${System.properties['env']}" println "env default: $env" } }
4. 确保测试类符合要求
- 测试类位于
src/test/java/regression目录下,类名与regression.xml中的一致。 - 测试方法添加
@Test(groups = "smoke")注解,确保被includeGroups 'smoke'匹配。
验证结果
执行命令./gradlew clean testRun -Pgroups=smoke,预期输出会显示测试执行的详细日志,结果统计中包含实际执行的测试数量:
./gradlew clean testRun -Pgroups=smoke > Task :app:testRun Test executed testLandingPage [regression.LandingPageTests] with result: SUCCESS ... -------------------------------------------------------------------- | Results: SUCCESS (5 tests, 5 successes, 0 failures, 0 skipped) | -------------------------------------------------------------------- TestNG args: [...] System property env value: uat env default: uat
内容的提问来源于stack exchange,提问作者automationguru
相关产品推荐
相关产品推荐

