如何在Android应用代码(MainActivity)中调用Instrumented Tests
问题:通过应用自身UI触发Instrumented测试用例
常规调用方式
Android平台上,Instrumented Tests通常通过以下方式调用:
- 在Android Studio中右键点击androidTest目录下的测试类执行
- 在CMD命令行执行命令:
adb shell am instrument -w com.example.testing/androidx.test.runner.AndroidJUnitRunner
需求
希望通过应用自身UI的按钮点击事件,触发androidTest目录下指定的测试用例(点击buttonRunTest1执行testCaseForButton1,点击buttonRunTest2执行testCaseForButton2)。
现有代码
androidTest目录下的测试类代码
package com.example.myapplication import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.uiautomator.UiDevice import org.junit.Test import org.junit.runner.RunWith import org.junit.Assert.* @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { @Test fun testCaseForButton1() { val appContext = InstrumentationRegistry.getInstrumentation().targetContext // 测试步骤 assertEquals("com.example.myapplication", appContext.packageName) } @Test fun testCaseForButton2() { val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) // 测试步骤 assertEquals("com.sec.android.app.launcher", device.launcherPackageName) } }
MainActivity代码片段
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<Button>(R.id.buttonRunTest1) .setOnClickListener { // 需要在这里实现调用testCaseForButton1的逻辑 } findViewById<Button>(R.id.buttonRunTest2) .setOnClickListener { // 需要在这里实现调用testCaseForButton2的逻辑 } }
已尝试的无效方法
方法1:直接执行am instrument命令
fun startTest(){ val packageName = "com.example.myapplication" val testRunnerClass = "androidx.test.runner.AndroidJUnitRunner" val command = "am instrument -w $packageName/$testRunnerClass" Runtime.getRuntime().exec(command) }
方法2:反射调用Instrumentation方法
fun startTest(){ val instrumentation = InstrumentationRegistry.getInstrumentation() val paramTypes = arrayOf(ComponentName::class.java, Bundle::class.java) val method = instrumentation.javaClass.getDeclaredMethod("startInstrumentation", *paramTypes) }
现有配置
build.gradle(defaultConfig)
defaultConfig { applicationId "com.example.myapplication" minSdk 28 targetSdk 32 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" }
依赖配置
dependencies { ... testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' implementation 'androidx.test.uiautomator:uiautomator:2.2.0' implementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test:rules:1.5.0' }
可行解决方案
Instrumented测试默认运行在独立于主应用的进程中,主应用进程无法直接调用测试类代码(会报"No instrumentation registered! Must run under a registering instrumentation"错误),必须通过启动instrumentation进程并指定单个测试方法的方式实现需求。
按钮点击事件实现代码
修改MainActivity的按钮点击逻辑,在子线程中执行带参数的am instrument命令,指定要运行的测试类和方法:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 触发testCaseForButton1的按钮 findViewById<Button>(R.id.buttonRunTest1).setOnClickListener { Thread { runSpecificTest("com.example.myapplication.ExampleInstrumentedTest#testCaseForButton1") }.start() } // 触发testCaseForButton2的按钮 findViewById<Button>(R.id.buttonRunTest2).setOnClickListener { Thread { runSpecificTest("com.example.myapplication.ExampleInstrumentedTest#testCaseForButton2") }.start() } } /** * 执行指定的Instrumented测试用例 * @param testSpec 格式:包名.测试类名#测试方法名 */ private fun runSpecificTest(testSpec: String) { try { val packageName = "com.example.myapplication" val testRunner = "androidx.test.runner.AndroidJUnitRunner" // 构造带参数的am instrument命令,指定单个测试用例 val command = "am instrument -w -e class $testSpec $packageName/$testRunner" val process = Runtime.getRuntime().exec(command) // 读取测试输出(可选,用于调试或展示结果) val inputReader = BufferedReader(InputStreamReader(process.inputStream)) val errorReader = BufferedReader(InputStreamReader(process.errorStream)) var line: String? while (inputReader.readLine().also { line = it } != null) { Log.d("TestResult", line ?: "") } while (errorReader.readLine().also { line = it } != null) { Log.e("TestError", line ?: "") } // 等待测试进程结束 val exitCode = process.waitFor() Log.d("TestExit", "测试结束,退出码:$exitCode") } catch (e: Exception) { e.printStackTrace() Log.e("TestRunner", "执行测试失败:${e.message}") } }
注意事项
- 必须在子线程执行:避免主线程阻塞导致ANR
- 测试路径要准确:
testSpec的格式必须是「完整包名.测试类名#测试方法名」,不能写错 - 仅支持Debug版本:该方式需要应用处于可调试状态,Release版本可能会因为权限或代码混淆无法正常运行
- 权限要求:无需额外权限,
am instrument命令属于系统命令,应用自身调用无需特殊权限
内容的提问来源于stack exchange,提问作者GBN
相关产品推荐
相关产品推荐

