别再傻傻分不清!Android Studio里androidTest和test文件夹到底怎么用?(附实战代码对比)

张开发
2026/4/18 19:53:07 15 分钟阅读

分享文章

别再傻傻分不清!Android Studio里androidTest和test文件夹到底怎么用?(附实战代码对比)
Android测试实战指南androidTest与test目录的深度解析与最佳实践刚接触Android开发的工程师们面对项目中自动生成的androidTest和test目录时常常会陷入困惑——这两个看似相似的测试目录究竟有什么区别为什么同样的测试逻辑有时放在test能通过放到androidTest却报错今天我们就来彻底解决这个困扰无数开发者的难题。1. 理解测试目录的本质区别在Android项目的标准结构中androidTest和test目录虽然都用于存放测试代码但它们的定位和执行环境有着根本性的差异。理解这一点是正确使用它们的前提。test目录也称为Unit Test目录用于编写本地单元测试这些测试运行在JVM上不需要Android设备或模拟器。它们的特点是执行速度快毫秒级不依赖Android框架适合测试纯Java/Kotlin逻辑// test目录下的典型单元测试示例 class CalculatorTest { Test fun testAddition() { val result Calculator().add(2, 3) assertEquals(5, result) } }androidTest目录用于编写仪器化测试这些测试需要运行在真实的Android设备或模拟器上。它们的特点是执行速度较慢秒级可以访问Android系统API适合测试UI交互和集成场景// androidTest目录下的典型仪器化测试示例 RunWith(AndroidJUnit4::class) class LoginActivityTest { get:Rule val activityRule ActivityScenarioRule(LoginActivity::class.java) Test fun testLoginButtonClick() { onView(withId(R.id.loginButton)).perform(click()) onView(withId(R.id.welcomeText)).check(matches(isDisplayed())) } }提示一个简单的判断标准是——如果你的测试需要Android上下文如Activity、资源文件等就应该放在androidTest中如果只是测试纯业务逻辑放在test中更合适。2. 实战场景下的目录选择策略2.1 ViewModel测试test目录的完美用例ViewModel是Android架构组件中的核心部分它负责准备和管理UI相关的数据。由于ViewModel不直接依赖Android框架我们可以完全在test目录中测试它。// 在test目录测试ViewModel class UserViewModelTest { private lateinit var viewModel: UserViewModel private val mockRepository mockUserRepository() Before fun setup() { viewModel UserViewModel(mockRepository) } Test fun loadUser should update userData() { val testUser User(id 1, name Test) whenever(mockRepository.getUser(1)).thenReturn(testUser) viewModel.loadUser(1) assertEquals(testUser, viewModel.userData.value) } }这种测试的优势在于执行速度极快无需启动Android运行时可以轻松模拟依赖使用Mockito等框架适合TDD测试驱动开发流程2.2 Activity和UI测试androidTest的专属领域当测试涉及到UI组件如Activity、Fragment时我们必须使用androidTest目录因为这些组件需要Android运行环境。// 在androidTest目录测试Activity RunWith(AndroidJUnit4::class) class MainActivityTest { get:Rule val activityRule ActivityScenarioRule(MainActivity::class.java) Test fun testNavigationDrawerOpen() { // 打开导航抽屉 onView(withId(R.id.drawerButton)).perform(click()) // 验证导航菜单项可见 onView(withText(Settings)).check(matches(isDisplayed())) } }对于这类测试我们通常会使用Espresso用于同步UI测试UI Automator用于跨应用测试FragmentScenario用于隔离测试Fragment2.3 边界案例Robolectric的折中方案有时候我们需要测试一些依赖Android环境但又希望快速执行的代码这时可以使用Robolectric——一个在JVM上模拟Android环境的测试框架。// 在test目录使用Robolectric测试Android相关代码 RunWith(RobolectricTestRunner::class) class SharedPreferencesHelperTest { private lateinit var sharedPreferencesHelper: SharedPreferencesHelper private lateinit var context: Context Before fun setUp() { context ApplicationProvider.getApplicationContext() sharedPreferencesHelper SharedPreferencesHelper(context) } Test fun testSaveAndReadData() { sharedPreferencesHelper.saveString(key, value) assertEquals(value, sharedPreferencesHelper.getString(key)) } }虽然Robolectric提供了便利但它有一些限制不支持所有Android API模拟行为可能与真实设备有差异配置相对复杂3. 配置与依赖管理正确的依赖配置是保证测试能够运行的关键。Android项目中的build.gradle文件需要分别配置test和androidTest的依赖。dependencies { // test目录依赖 testImplementation junit:junit:4.13.2 testImplementation org.mockito:mockito-core:4.5.1 testImplementation org.robolectric:robolectric:4.9 // androidTest目录依赖 androidTestImplementation androidx.test.ext:junit:1.1.3 androidTestImplementation androidx.test.espresso:espresso-core:3.4.0 androidTestImplementation androidx.test:runner:1.4.0 }依赖配置的常见错误包括将androidTest专用依赖放在testImplementation中忘记添加必要的测试运行器如AndroidJUnitRunner版本冲突导致测试无法运行注意Android Gradle插件会自动为不同测试源集test和androidTest创建独立的classpath这意味着它们的依赖是完全隔离的。4. 测试执行与报告分析4.1 运行测试的不同方式在Android Studio中你可以通过多种方式运行测试运行单个测试点击测试方法旁边的运行图标运行类中的所有测试点击测试类旁边的运行图标运行目录下的所有测试右键点击test或androidTest目录选择Run Tests对于持续集成环境通常使用Gradle命令# 运行所有test目录的测试 ./gradlew testDebugUnitTest # 运行所有androidTest目录的测试 ./gradlew connectedDebugAndroidTest4.2 理解测试报告无论使用哪种方式运行测试Android Studio和Gradle都会生成详细的测试报告test目录测试报告位置app/build/reports/tests/testDebugUnitTest/androidTest目录测试报告位置app/build/reports/androidTests/connected/报告通常包括通过/失败的测试数量每个测试的执行时间失败测试的堆栈跟踪截图对于UI测试测试报告示例结构 build/ └── reports/ ├── tests/ │ └── testDebugUnitTest/ │ ├── index.html # 主报告文件 │ ├── css/ │ └── js/ └── androidTests/ └── connected/ ├── index.html # 主报告文件 ├── screenshots/ # UI测试截图 ├── css/ └── js/4.3 提高测试效率的技巧过滤测试使用注解过滤要运行的测试RunWith(AndroidJUnit4::class) LargeTest class LargeTestsSuite { // 这些测试只在执行大型测试套件时运行 }参数化测试避免重复代码RunWith(Parameterized::class) class CalculatorParamTest( private val a: Int, private val b: Int, private val expected: Int ) { Test fun testAdd() { assertEquals(expected, Calculator().add(a, b)) } companion object { JvmStatic Parameterized.Parameters fun data() listOf( arrayOf(1, 1, 2), arrayOf(2, 3, 5), arrayOf(0, 0, 0) ) } }测试覆盖率使用JaCoCo生成覆盖率报告android { buildTypes { debug { testCoverageEnabled true } } }5. 高级测试策略与架构建议随着项目规模扩大测试代码的组织和维护变得至关重要。以下是一些高级实践5.1 测试代码的组织结构好的测试代码应该像生产代码一样精心组织。推荐的结构是src/ ├── androidTest/ │ ├── java/ │ │ └── com.example.app/ │ │ ├── activity/ │ │ ├── fragment/ │ │ └── espresso/ ├── test/ │ ├── java/ │ │ └── com.example.app/ │ │ ├── domain/ │ │ ├── data/ │ │ └── viewmodel/ └── main/ └── java/ └── com.example.app/5.2 测试金字塔实践遵循测试金字塔原则构建健康的测试套件测试类型数量比例执行速度适合目录单元测试70%非常快test集成测试20%中等androidTestUI/端到端测试10%慢androidTest5.3 测试替身的使用策略在不同测试层级使用适当的测试替身测试类型常用测试替身适用目录单元测试Mockito, 自定义Faketest集成测试真实实现, 部分MockandroidTestUI测试真实实现, 测试专用APIandroidTest// 在test目录使用Mockito Test fun repository should fetch data from network() { val mockApi mockApiService() val repository UserRepository(mockApi) whenever(mockApi.getUser(any())).thenReturn(TestData.user) repository.fetchUser(1) verify(mockApi).getUser(1) }在实际项目中我经常看到开发者将所有测试都放在androidTest中结果导致测试运行缓慢开发效率低下。通过合理分配测试类型到不同的目录可以显著提升开发体验。

更多文章