Appearance
自动化测试
字数统计:约 8000 字
难度等级:⭐⭐⭐
面试重要度:⭐⭐
目录
1. 自动化测试基础
1.1 测试金字塔
/\
/ \
/ E2E \ ← 端到端测试(10%)
/--------\
/Integration\ ← 集成测试(20%)
/--------------\
/ Unit Tests \ ← 单元测试(70%)
/____________________\1.2 自动化测试类型
kotlin
// 1. 单元测试
@Test
fun `unit test - fast, isolated`() {
val result = 2 + 2
assertEquals(4, result)
}
// 2. 集成测试
@Test
fun `integration test - multiple components`() {
val repository = Repository(database, api)
val users = repository.getUsers()
assertTrue(users.isNotEmpty())
}
// 3. E2E 测试
@Test
fun `E2E test - full user flow`() {
// 启动应用 → 登录 → 浏览 → 退出
launchApp()
login("admin", "password")
browseContent()
logout()
}1.3 自动化测试好处
好处:
- 快速反馈(几分钟内知道结果)
- 可重复执行(每次提交都运行)
- 减少人工测试成本
- 发现回归问题
- 文档作用(测试即文档)
局限性:
- 无法替代探索性测试
- 无法测试用户体验
- 维护成本
- 初始投入时间2. 测试框架选择
2.1 JUnit + Espresso
kotlin
@RunWith(AndroidJUnit4::class)
class EspressoTest {
@get:Rule
val activityRule = ActivityScenarioRule(MainActivity::class.java)
@Test
fun testLogin() {
onView(withId(R.id.username)).perform(typeText("admin"))
onView(withId(R.id.password)).perform(typeText("password"))
onView(withId(R.id.loginButton)).perform(click())
onView(withId(R.id.homeScreen)).check(matches(isDisplayed()))
}
}2.2 Compose Testing
kotlin
class ComposeTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun testComposeUI() {
composeTestRule.setContent {
AppTheme {
LoginScreen()
}
}
composeTestRule
.onNodeWithText("Login")
.performClick()
composeTestRule
.onNodeWithTag("homeScreen")
.assertIsDisplayed()
}
}2.3 第三方框架
gradle
dependencies {
// Kaspresso - Espresso 增强
androidTestImplementation 'com.kaspersky.android-components:kaspresso:1.5.0'
// Kakao - Espresso DSL
androidTestImplementation 'com.agoda.kakao:kk:2.2.0'
// Detox - React Native
// implementation 'detox:detox:+'
// Appium - 跨平台
// implementation 'io.appium:java-client:+'
}3. CI/CD 集成
3.1 GitHub Actions
yaml
# .github/workflows/android-test.yml
name: Android Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Run Unit Tests
run: ./gradlew test
- name: Run Instrumented Tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 30
script: ./gradlew connectedAndroidTest
- name: Upload Test Results
uses: actions/upload-artifact@v3
if: always()
with:
name: test-results
path: '**/build/reports/tests/'3.2 GitLab CI
yaml
# .gitlab-ci.yml
stages:
- test
- deploy
unit_tests:
stage: test
image: openjdk:17
script:
- ./gradlew test
artifacts:
reports:
junit: build/test-results/test/*.xml
instrumented_tests:
stage: test
image: reactiveraven/gradle-android:latest
script:
- ./gradlew connectedAndroidTest
artifacts:
when: always
reports:
junit: build/outputs/androidTest-results/connected/*.xml3.3 Firebase Test Lab
gradle
// 配置 Firebase Test Lab
apply plugin: 'com.google.firebase.testlab'
firebaseTestLab {
test {
type = "instrumentation"
appApkPath = "app/build/outputs/apk/debug/app-debug.apk"
testApkPath = "app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk"
device = [
{ model = "Pixel5", version = "30", locale = "en", orientation = "portrait" },
{ model = "Nexus6", version = "25", locale = "en", orientation = "portrait" }
]
}
}bash
# 运行 Firebase Test Lab
gcloud firebase test android run \
--type instrumentation \
--app app-debug.apk \
--test app-debug-androidTest.apk \
--device model=Pixel5,version=30,locale=en,orientation=portrait \
--timeout 300s4. 测试策略
4.1 测试优先级
kotlin
// P0 - 核心功能(必须自动化)
@Test
fun `P0 - login should work`() {}
@Test
fun `P0 - payment should process`() {}
@Test
fun `P0 - data should save`() {}
// P1 - 重要功能(应该自动化)
@Test
fun `P1 - search should return results`() {}
@Test
fun `P1 - filter should work`() {}
// P2 - 次要功能(可选自动化)
@Test
fun `P2 - settings should display`() {}4.2 测试覆盖范围
建议覆盖:
- 所有业务逻辑(100%)
- 所有 API 调用(100%)
- 关键用户路径(100%)
- 错误处理(80%+)
- 边界条件(80%+)
- UI 组件(关键页面)
不需要覆盖:
- 简单的 getter/setter
- 框架代码
- 一次性脚本4.3 测试数据管理
kotlin
// 测试数据工厂
object TestDataFactory {
fun createValidUser(): User = User(
id = 1,
username = "test",
email = "test@example.com"
)
fun createInvalidUser(): User = User(
id = 0,
username = "",
email = "invalid"
)
fun createUsers(count: Int): List<User> =
(1..count).map { createValidUser().copy(id = it) }
}
// 测试数据库
@JvmStatic
@After
fun cleanupDatabase() {
database.clearAllTables()
}4.4 测试环境配置
kotlin
// 测试专用配置
class TestConfig {
companion object {
const val BASE_URL = "http://localhost:8080"
const val TIMEOUT = 5000L
const val MOCK_DATA = true
}
}
// Hilt 测试模块
@TestInstallIn(components = [UnitTestComponent::class])
@Module
class TestModule {
@Provides
fun provideApiService(): ApiService {
return MockApiService()
}
@Provides
fun provideDatabase(): AppDatabase {
return Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
AppDatabase::class.java
).build()
}
}5. 最佳实践
5.1 测试命名规范
kotlin
// ✅ 好的命名 - 清晰表达意图
@Test
fun `login with valid credentials should succeed`() {}
@Test
fun `login with invalid password should show error`() {}
@Test
fun `getUsers when network fails should return cached data`() {}
// ❌ 不好的命名
@Test
fun testLogin() {}
@Test
fun test1() {}
@Test
fun testGetUsers() {}5.2 测试结构
kotlin
// AAA 模式
@Test
fun `test with AAA pattern`() {
// Arrange - 准备
val user = TestDataFactory.createValidUser()
every { repository.getUser(any()) } returns user
// Act - 执行
val result = viewModel.getUser(1)
// Assert - 断言
assertEquals(user, result)
}
// Given-When-Then
@Test
fun `test with GWT pattern`() {
// Given
val user = TestDataFactory.createValidUser()
// When
val result = viewModel.getUser(1)
// Then
assertEquals(user, result)
}5.3 测试维护
kotlin
// ✅ 好的实践 - 易于维护
class LoginPageTest {
private val page = LoginPage()
@Test
fun `successful login`() {
page
.enterUsername("admin")
.enterPassword("password")
.clickLogin()
.verifySuccess()
}
}
// ❌ 不好的实践 - 难以维护
@Test
fun testLogin() {
onView(withId(R.id.edit1)).perform(typeText("admin"))
onView(withId(R.id.edit2)).perform(typeText("password"))
onView(withId(R.id.button1)).perform(click())
onView(withId(R.id.text1)).check(matches(isDisplayed()))
}5.4 测试速度优化
gradle
// 1. 并行执行测试
android {
testOptions {
unitTests.all {
maxParallelForks = 4
}
}
}
// 2. 使用模拟器快照
// avdmanager create avd -n test -k "system-images;android-30;google_apis;x86"
// 3. 只运行受影响的测试
// ./gradlew test --tests "*UserServiceTest*"
// 4. 使用 Gradle Build Cache
// org.gradle.caching=true6. 面试考点
6.1 基础概念
Q1: 什么是自动化测试?
答案要点:
- 使用脚本自动执行测试
- 对比预期和实际结果
- 减少人工干预
- 提高测试效率Q2: 测试金字塔是什么?
答案要点:
- 单元测试(70%)- 底层,快速
- 集成测试(20%)- 中层
- E2E 测试(10%)- 顶层,慢
- 建议:多写单元测试,少写 E2E6.2 实战问题
Q3: 如何集成到 CI/CD?
yaml
# GitHub Actions 示例
- name: Run Tests
run: ./gradlew test connectedAndroidTest
- name: Upload Results
uses: actions/upload-artifact@v3
with:
name: test-results
path: build/reports/tests/Q4: 如何选择测试框架?
答案要点:
- View 系统:Espresso
- Jetpack Compose:Compose Testing
- 跨应用:UI Automator
- 跨平台:Appium
- 考虑因素:技术栈、团队技能、项目需求参考资料
本文完,感谢阅读!