关于 migrating-from-android-test-classes
这项技能提供从已弃用的 Android 平台 android.test.* 测试类迁移到 AndroidX Test 的替换指南,面向维护旧 Android 测试代码的开发者。内容涵盖 JUnit4、测试规则及断言和 UI 操作的对应方案。
主要功能
- 01旧测试类逐项替换指南
- 02迁移到 AndroidJUnitRunner
- 03JUnit3 转换为 JUnit4
- 04替换断言与 UI 操作
- 05替换测试上下文和模拟类
适用场景
- 01清理使用 android.test.* 的旧模块
- 02将测试运行器迁移到 AndroidX
- 03把 Activity 测试改用 ActivityScenario
- 04替换 MoreAsserts 和 TouchUtils
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ skydoves/ android-testing-skills --skill migrating-from-android-test-classes安装后在 Claude Code 中输入/migrating-from-android-test-classes确认已加载。
详细介绍
迁移离开 android.test.*——从平台的旧版测试类迁移到 AndroidX Test
Android SDK 中附带的 android.test.* 软件包(InstrumentationTestCase、ActivityInstrumentationTestCase2、AndroidTestCase、ServiceTestCase、MoreAsserts、TouchUtils、android.test.mock.*,……)已标记为 @Deprecated——平台自己的 Javadoc 会将每个类指向 AndroidX Test。旧代码库仍在使用这些类,它们也确实阻碍了 JUnit4、Espresso、ActivityScenario 和 AndroidX runner 的使用。本指南提供逐类替换对照表和机械迁移模式。
何时使用本指南
- IDE 将
android.test.InstrumentationTestCase/AndroidTestCase/ActivityInstrumentationTestCase2/ServiceTestCase/MoreAsserts/ViewAsserts/TouchUtils/android.test.mock.*标记为@Deprecated。 - 某个模块的
testInstrumentationRunner是android.test.InstrumentationTestRunner,且测试在AndroidJUnitRunner下表现异常(或无法被发现)。 - 用户询问如何替换某个特定的旧基类:“在
TestCase之外使用getInstrumentation()”、“不使用ActivityInstrumentationTestCase2时如何使用getActivity()/setActivityIntent()”、“不使用AndroidTestCase时如何使用getContext()”。 - 某个测试继承了
junit.framework.TestCase(JUnit3 风格:重写setUp()、使用testFoo()命名、从junit.framework.Assert调用assertEquals),且团队希望迁移到 JUnit4。 - 用户提到
MoreAsserts.assertContentsInAnyOrder/assertEquals(int[], int[])/assertContainsRegex、ViewAsserts.assertOnScreen、TouchUtils.tapView/dragQuarterScreenDown,或android.test.suitebuilder.annotation.SmallTest。
何时不使用本指南
- 代码库已经使用 AndroidX Test,问题是关于如何使用它(runner、
ActivityScenario、Espresso、ServiceTestRule)——直接查看../../../instrumentation/runner/running-instrumented-tests-with-androidjunit4/SKILL.md、../../../instrumentation/scenarios/launching-activities-with-activityscenario/SKILL.md、../../../instrumentation/espresso/writing-espresso-tests/SKILL.md。 - 问题是关于
kotlin.test断言(assertEquals、assertFailsWith)——使用../../../kotlin/kotlin-test/writing-tests-with-kotlin-test/SKILL.md。(kotlin.test是替代MoreAsserts调用的有效选择之一。) - 涉及的旧类是 Robolectric shadow,或纯 JVM 代码中的非 Android JUnit3
TestCase——对于 JVM/Robolectric 部分,使用../../../jvm-tests/robolectric/using-robolectric-correctly/SKILL.md和../../../jvm-tests/runner/configuring-junit4-on-android/SKILL.md。 - 用户想要编写新测试——完全不要从
android.test.*入手,直接使用 AndroidX 指南。
前置条件
- 测试类路径中包含 AndroidX Test——具体依赖坐标对照表(
androidx.test:core、:runner、:rules、androidx.test.ext:junit)请参阅../../../instrumentation/runner/running-instrumented-tests-with-androidjunit4/SKILL.md。对于宿主/JVM 测试,请参阅../../../jvm-tests/runner/configuring-junit4-on-android/SKILL.md。 - 类路径中包含 JUnit4(
junit:junit:4.13.2,或通过kotlin("test")引入)。旧类采用 JUnit3 风格(extends TestCase);迁移目标是 JUnit4(@Test+@RunWith)。 - 对于任何曾使用
TouchUtils/ViewAsserts/ActivityInstrumentationTestCase2进行 UI 操作的测试,都需要 Espresso(androidx.test.espresso:espresso-core)。
替换对照表
旧版 android.test.*(已弃用) | 替代方案 | 备注 |
|---|---|---|
InstrumentationTestRunner(testInstrumentationRunner) | androidx.test.runner.AndroidJUnitRunner | 修改 android.defaultConfig.testInstrumentationRunner。旧版运行器无法正确运行 JUnit4 或 Espresso。 |
InstrumentationTestCase | @RunWith(AndroidJUnit4::class) + InstrumentationRegistry.getInstrumentation() | getInstrumentation() → androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()。injectInstrumentation(...) 已移除。sendKeys/runTestOnUiThread → getInstrumentation().runOnMainSync { … } 或 @UiThreadTest。 |
AndroidTestCase | @RunWith(AndroidJUnit4::class) + ApplicationProvider.getApplicationContext() | getContext() → ApplicationProvider.getApplicationContext<Context>()。getTestContext()(测试 APK 的上下文)→ InstrumentationRegistry.getInstrumentation().context。 |
ApplicationTestCase<T> | @RunWith(AndroidJUnit4::class),通过 ApplicationProvider.getApplicationContext() 操作 Application | 没有一一对应的基类——现在 Application 只是一个上下文;直接对其进行断言即可。 |
ActivityInstrumentationTestCase2<T>、ActivityUnitTestCase<T>、SingleLaunchActivityTestCase<T> | ActivityScenario<T> / @get:Rule ActivityScenarioRule<T>(UI 测试使用 Espresso) | getActivity() → scenario.onActivity { activity -> … }。setActivityIntent(intent) → ActivityScenario.launch<T>(intent)。getInstrumentation().waitForIdleSync() → Espresso 自动同步。参见 ../../../instrumentation/scenarios/launching-activities-with-activityscenario/SKILL.md。 |
| (自定义的 Fragment 测试,如有) | FragmentScenario / launchFragmentInContainer | ../../../instrumentation/scenarios/launching-fragments-with-fragmentscenario/SKILL.md。 |
ServiceTestCase<T> | androidx.test.rule.ServiceTestRule | startService(intent) / bindService(intent) 移至规则中(@get:Rule val serviceRule = ServiceTestRule());getService() → serviceRule.bindService(intent) 返回的 IBinder。 |
ProviderTestCase2<T> | androidx.test.rule.provider.ProviderTestRule | ProviderTestCase2 是 JUnit3 风格的形式;ProviderTestRule.Builder(MyProvider::class.java, AUTHORITY).build() 是 JUnit4 的做法,并且仍会提供隔离的 ContentResolver。 |
LoaderTestCase | (Loader 本身已弃用) | 先将 Loader 迁移为 ViewModel + 协程/Flow,然后使用 runTest / Turbine 测试。 |
MoreAsserts(assertContentsInAnyOrder、assertEquals(int[], int[])、assertEmpty、assertNotEqual、assertContainsRegex、checkEqualsAndHashCodeMethods) | Google Truth(assertThat(list).containsExactly(...)、.isEmpty()、.containsMatch(regex))、Hamcrest,或用于有序数组的 kotlin.test.assertContentEquals | 平台 Javadoc 建议“使用 Hamcrest”;Truth 的可读性更好。androidx.test.ext:truth 提供 Android 专用的 Truth subject。 |
ViewAsserts(assertOnScreen、assertHorizontalCenterAligned、assertBottomAligned、…) | Espresso ViewMatchers + ViewAssertions(matches(isCompletelyDisplayed())、布局关系自定义匹配器) | 参见 ../../../instrumentation/espresso/writing-espresso-tests/SKILL.md。 |
TouchUtils(tapView、clickView、longClickView、dragQuarterScreenDown、scrollToBottom、dragViewToTop) | Espresso ViewActions(click()、longClick()、swipeUp()/swipeDown()、scrollTo()、GeneralSwipeAction) | 旧版方法需要有效的 Instrumentation 和 Activity 引用;Espresso 操作在 onView(...).perform(...) 中运行。 |
android.test.UiThreadTest | androidx.test.annotation.UiThreadTest | 只需更改导入;或替换为 getInstrumentation().runOnMainSync { … } / scenario.onActivity { … }(这本来就在主线程上运行)。 |
android.test.FlakyTest | androidx.test.filters.FlakyTest | 更改导入即可。 |
android.test.suitebuilder.annotation.SmallTest / MediumTest / LargeTest / Suppress | androidx.test.filters.SmallTest / MediumTest / LargeTest / Suppress | 更改导入即可;运行器会根据 AndroidX 注解读取 -e size small。 |
android.test.InstrumentationTestSuite、android.test.suitebuilder.TestSuiteBuilder | JUnit4 @RunWith(Suite::class) + @Suite.SuiteClasses([...]),或直接让 AndroidJUnitRunner 根据 @SmallTest/包名发现测试 | 配置好 AndroidX 运行器后,通常不再需要手动构建测试套件。 |
android.test.mock.MockContext | 真实上下文(ApplicationProvider.getApplicationContext())、JVM 上 Robolectric 的上下文,或用于严格隔离的 mock(Context::class.java)(Mockito/MockK) | MockContext 几乎所有方法都会抛出 UnsupportedOperationException——它是一个手动编写桩代码的基类,而模拟框架能做得更好。 |
android.test.mock.MockContentResolver + MockContentProvider | ProviderTestRule(隔离的解析器),或来自 Robolectric/插桩上下文的真实 ContentResolver | 如果确实需要桩解析器,可用 Mockito 模拟 ContentResolver。 |
android.test.mock.MockPackageManager | Mockito/MockK mock(PackageManager::class.java) | 平台 Javadoc 明确建议“使用 Mockito 等模拟框架”。 |
android.test.mock.MockResources / MockCursor / MockDialogInterface / MockApplication / MockService | Mockito/MockK 模拟对象,或来自测试上下文的真实实例 | 情况相同——这些都是桩基类,现已被模拟框架取代。 |
android.test.AndroidTestRunner、android.test.PerformanceTestCase、android.test.RepetitiveTest | AndroidJUnitRunner;androidx.benchmark(Microbenchmark/Macrobenchmark);自定义 JUnit4 @Rule 或直接使用循环 | PerformanceTestCase 早于 androidx.benchmark——真正的性能测试应使用后者。 |
extends junit.framework.TestCase + 来自 junit.framework.Assert 的 assertEquals(JUnit3 风格) | @RunWith(AndroidJUnit4::class) + @Test 方法 + org.junit.Assert.* / kotlin.test.* | 方法名不再需要以 test 为前缀;setUp/tearDown 重写改为 @Before/@After(或 @BeforeTest/@AfterTest)。 |
平台自身的
@deprecatedJavadoc 往往指向旧版“Android Testing Support Library”/android.support.test.*名称(例如ActivityTestRule)。这些名称后来变为androidx.test.*,其中一些此后又被弃用——ActivityTestRule→ActivityScenario。请迁移到上表中的当前目标,而不是照搬平台 Javadoc 中的字面内容。
工作流
-
1. 先切换运行器。 设置
android.defaultConfig.testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"并添加 AndroidX Test 依赖。在完成这一步之前,JUnit4/Espresso/ActivityScenario都无法运行。(../../../instrumentation/runner/running-instrumented-tests-with-androidjunit4/SKILL.md。) -
2. 一次转换一个测试类,从 JUnit3 转到 JUnit4。 移除
extends …TestCase,添加@RunWith(AndroidJUnit4::class),为测试方法添加@Test,将setUp()/tearDown()重写改为@Before/@After(不调用super),并可按需将testFoo重命名为foo。将每个旧版访问器替换为表中的对应 AndroidX 实现。 -
3. 用显式的 AndroidX 入口替代基类提供的功能。
getContext()→ApplicationProvider.getApplicationContext();getInstrumentation()→InstrumentationRegistry.getInstrumentation();getActivity()→ActivityScenario+onActivity { };getService()→ServiceTestRule.bindService(...)。 -
4. 替换断言辅助工具。
MoreAsserts.*→ Truth/kotlin.test;ViewAsserts.*→ EspressoViewAssertions;TouchUtils.*→ EspressoViewActions。 -
5. 替换模拟上下文。 对于继承
android.test.mock.MockContext/MockResources/MockPackageManager的对象,尽可能改用真实测试上下文,否则使用 Mockito/MockK 模拟对象。(../../../jvm-tests/mocking/mocking-with-mockito/SKILL.md。) -
6. 删除旧版导入,并确认不再有任何代码引用
android.test.*或android.test.mock.*。 模块通过编译且没有任何import android.test.行,即表示清理完成。
模式
模式:ActivityInstrumentationTestCase2 → ActivityScenario
// WRONG — deprecated base class
class LoginActivityTest : ActivityInstrumentationTestCase2<LoginActivity>(LoginActivity::class.java) {
fun testTitleShown() {
val activity = activity // getActivity()
assertEquals("Sign in", activity.title)
}
}
// WRONG because: android.test.ActivityInstrumentationTestCase2 is @Deprecated; it ties the test to
// the legacy InstrumentationTestRunner, blocks JUnit4/Espresso, and getActivity() leaks the Activity
// off the main thread.
// RIGHT — ActivityScenario + JUnit4
@RunWith(AndroidJUnit4::class)
class LoginActivityTest {
@get:Rule val scenario = ActivityScenarioRule(LoginActivity::class.java)
@Test fun titleShown() {
scenario.scenario.onActivity { activity ->
assertEquals("Sign in", activity.title)
}
onView(withId(R.id.title)).check(matches(withText("Sign in"))) // or assert via Espresso
}
}
模式:AndroidTestCase → @RunWith(AndroidJUnit4) + ApplicationProvider
// WRONG
class FormatterTest : AndroidTestCase() {
fun testCurrency() {
val s = CurrencyFormatter(context).format(1099) // getContext()
assertEquals("$10.99", s)
}
}
// WRONG because: android.test.AndroidTestCase is @Deprecated; getContext() and the TestCase shape
// are the legacy world.
// RIGHT
@RunWith(AndroidJUnit4::class)
class FormatterTest {
private val context: Context = ApplicationProvider.getApplicationContext()
@Test fun currency() {
assertEquals("$10.99", CurrencyFormatter(context).format(1099))
}
}
模式:MoreAsserts / TouchUtils 调用
// WRONG
MoreAsserts.assertContentsInAnyOrder(ids, 3L, 1L, 2L) // android.test.MoreAsserts (deprecated)
TouchUtils.tapView(this, listView) // android.test.TouchUtils (deprecated)
ViewAsserts.assertOnScreen(rootView, listView) // android.test.ViewAsserts (deprecated)
// RIGHT
assertThat(service.ids()).containsExactly(1L, 2L, 3L) // Google Truth (order-insensitive)
onView(withId(R.id.list)).perform(click()) // Espresso ViewActions
onView(withId(R.id.list)).check(matches(isCompletelyDisplayed())) // Espresso ViewAssertions
模式:android.test.mock.MockContext 子类 → 真实上下文或 Mockito
// WRONG
class FakeContext : MockContext() { // android.test.mock.MockContext (deprecated)
override fun getPackageName() = "com.example"
// every other method throws UnsupportedOperationException
}
// RIGHT — a real test context if the SUT just reads from it
val context: Context = ApplicationProvider.getApplicationContext()
// RIGHT — a mock if you must control specific calls in isolation
val context = mock<Context> { on { packageName } doReturn "com.example" }
强制规则
- 绝不可使用任何
android.test.*或android.test.mock.*类添加新测试——这些类都已标记为@Deprecated。新测试使用 AndroidX Test(AndroidJUnit4、ActivityScenario、ServiceTestRule、ProviderTestRule、Espresso)以及org.junit/kotlin.test断言。 - 必须在迁移任何测试类之前(或作为迁移的第一步),将
testInstrumentationRunner切换为androidx.test.runner.AndroidJUnitRunner——旧版InstrumentationTestRunner无法运行 JUnit4/Espresso。 - 必须将
getActivity()/setActivityIntent()替换为ActivityScenario(launch、onActivity { }),而不是替换为ActivityTestRule——ActivityTestRule本身也已弃用。 - 必须将
android.test.mock.MockContext/MockResources/MockPackageManager/MockContentResolver子类替换为真实的测试上下文(当 SUT 只从中读取数据时),否则使用 Mockito/MockK mock——绝不要从MockContext派生新的手写存根。 - 必须将 JUnit3 结构(
extends TestCase、testFoo()、setUp()/tearDown()重写、junit.framework.Assert)转换为 JUnit4(@RunWith(AndroidJUnit4)、@Test、@Before/@After、org.junit.Assert/kotlin.test)——不要让类处于半迁移状态。 - 绝不可相信平台
@deprecatedJavadoc 中写明的目标(它经常指向已过时的android.support.test.*API);应迁移到此技能表格中的当前 AndroidX 目标。 - **首选:**每次更改完整迁移一个测试类,并先提交 runner 切换;模块处于半迁移状态(部分类使用
android.test.*,部分使用 AndroidX)会难以理解。 - **首选:**使用 Google Truth(Android subject 使用
androidx.test.ext:truth)替代 Hamcrest,作为MoreAsserts的替代方案——失败消息更清晰。
验证
-
git grep -n 'import android.test\.' -- '*.java' '*.kt'无匹配结果(或仅限于已跟踪且有合理说明的保留项列表)。 - 没有模块将
testInstrumentationRunner设为android.test.InstrumentationTestRunner;所有模块都使用androidx.test.runner.AndroidJUnitRunner。 - 没有测试类
extends ActivityInstrumentationTestCase2/AndroidTestCase/InstrumentationTestCase/ServiceTestCase/ProviderTestCase2/junit.framework.TestCase。 - 不再存在
MoreAsserts./ViewAsserts./TouchUtils.引用。 - 不再存在
android.test.mock.MockContext/MockResources/MockPackageManager/MockContentResolver的子类。 - 迁移后的插桩测试可通过
./gradlew :module:connectedDebugAndroidTest运行;迁移后的宿主测试可通过./gradlew :module:testDebugUnitTest运行。
参考资料
- developer.android.com/training/testing — AndroidX Test 概览;所有
android.test.*内容的迁移目标。 - developer.android.com/reference/android/test/package-summary —
android.test软件包参考,其中每个类都标记为已弃用,并指明了替代方案。 frameworks/base/test-base/src/android/test/AndroidTestCase.java、InstrumentationTestCase.java、UiThreadTest.java、FlakyTest.java(AOSP)— 带有@Deprecated的类,其 Javadoc 指向InstrumentationRegistry/androidx.test.annotation.UiThreadTest/androidx.test.filters.FlakyTest。frameworks/base/test-runner/src/android/test/ActivityInstrumentationTestCase2.java、ServiceTestCase.java、ProviderTestCase2.java、InstrumentationTestRunner.java、MoreAsserts.java、ViewAsserts.java、TouchUtils.java(AOSP)— 已弃用的运行器/基类;其 Javadoc 将ActivityTestRule/ServiceTestRule/AndroidJUnitRunner/Hamcrest/Espresso 列为替代方案。frameworks/base/test-mock/src/android/test/mock/MockContext.java、MockPackageManager.java、MockContentResolver.java、MockResources.java、MockCursor.java(AOSP)— 存根上下文基类;MockPackageManager的 Javadoc 明确写道:“use a mocking framework like Mockito”。- 跨集合:
../../../instrumentation/runner/running-instrumented-tests-with-androidjunit4/SKILL.md—AndroidJUnitRunner/ AndroidX Test 坐标矩阵,应先了解。 - 跨集合:
../../../instrumentation/scenarios/launching-activities-with-activityscenario/SKILL.md—ActivityScenario/ActivityScenarioRule,是ActivityInstrumentationTestCase2的替代方案。 - 跨集合:
../../../instrumentation/scenarios/launching-fragments-with-fragmentscenario/SKILL.md—FragmentScenario,适用于任何自定义的 Fragment 测试基类。 - 跨集合:
../../../instrumentation/espresso/writing-espresso-tests/SKILL.md— Espresso 的ViewMatchers/ViewActions/ViewAssertions,是ViewAsserts/TouchUtils的替代方案。 - 跨集合:
../../../jvm-tests/mocking/mocking-with-mockito/SKILL.md— 使用 Mockito 模拟Context/PackageManager/Resources,是android.test.mock.*的替代方案。 - 跨集合:
../../../jvm-tests/robolectric/using-robolectric-correctly/SKILL.md— 在 JVM 上使用真实的 Android 存根,是模拟上下文的替代方案。 - 跨集合:
../../../kotlin/kotlin-test/writing-tests-with-kotlin-test/SKILL.md—kotlin.test断言(assertEquals、assertContentEquals、assertFailsWith),是MoreAsserts和junit.framework.Assert的一种有效替代方案。 - 跨集合:
../../../fundamentals/strategies/organizing-test-source-sets/SKILL.md—src/test/与src/androidTest/的区别;迁移后的类需要据此归类。