mcpField
M
migrating-from-android-test-classes

migrating-from-android-test-classes

github.com/skydoves测试与质量33962 次安装22 周前更新
代码仓库

关于 migrating-from-android-test-classes

这项技能提供从已弃用的 Android 平台 android.test.* 测试类迁移到 AndroidX Test 的替换指南,面向维护旧 Android 测试代码的开发者。内容涵盖 JUnit4、测试规则及断言和 UI 操作的对应方案。

主要功能

  1. 01旧测试类逐项替换指南
  2. 02迁移到 AndroidJUnitRunner
  3. 03JUnit3 转换为 JUnit4
  4. 04替换断言与 UI 操作
  5. 05替换测试上下文和模拟类

适用场景

  1. 01清理使用 android.test.* 的旧模块
  2. 02将测试运行器迁移到 AndroidX
  3. 03把 Activity 测试改用 ActivityScenario
  4. 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.ServiceTestRulestartService(intent) / bindService(intent) 移至规则中(@get:Rule val serviceRule = ServiceTestRule());getService() → serviceRule.bindService(intent) 返回的 IBinder。
ProviderTestCase2<T>androidx.test.rule.provider.ProviderTestRuleProviderTestCase2 是 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.UiThreadTestandroidx.test.annotation.UiThreadTest只需更改导入;或替换为 getInstrumentation().runOnMainSync { … } / scenario.onActivity { … }(这本来就在主线程上运行)。
android.test.FlakyTestandroidx.test.filters.FlakyTest更改导入即可。
android.test.suitebuilder.annotation.SmallTest / MediumTest / LargeTest / Suppressandroidx.test.filters.SmallTest / MediumTest / LargeTest / Suppress更改导入即可;运行器会根据 AndroidX 注解读取 -e size small。
android.test.InstrumentationTestSuite、android.test.suitebuilder.TestSuiteBuilderJUnit4 @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 + MockContentProviderProviderTestRule(隔离的解析器),或来自 Robolectric/插桩上下文的真实 ContentResolver如果确实需要桩解析器,可用 Mockito 模拟 ContentResolver。
android.test.mock.MockPackageManagerMockito/MockK mock(PackageManager::class.java)平台 Javadoc 明确建议“使用 Mockito 等模拟框架”。
android.test.mock.MockResources / MockCursor / MockDialogInterface / MockApplication / MockServiceMockito/MockK 模拟对象,或来自测试上下文的真实实例情况相同——这些都是桩基类,现已被模拟框架取代。
android.test.AndroidTestRunner、android.test.PerformanceTestCase、android.test.RepetitiveTestAndroidJUnitRunner;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)。

平台自身的 @deprecated Javadoc 往往指向旧版“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.* → Espresso ViewAssertions;TouchUtils.* → Espresso ViewActions。

  • 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)——不要让类处于半迁移状态。
  • 绝不可相信平台 @deprecated Javadoc 中写明的目标(它经常指向已过时的 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/ 的区别;迁移后的类需要据此归类。

评论

支持 Markdown