Skip to content

自动化测试

字数统计:约 8000 字
难度等级:⭐⭐⭐
面试重要度:⭐⭐


目录

  1. 自动化测试基础
  2. 测试框架选择
  3. CI/CD 集成
  4. 测试策略
  5. 最佳实践
  6. 面试考点

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/*.xml

3.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 300s

4. 测试策略

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=true

6. 面试考点

6.1 基础概念

Q1: 什么是自动化测试?

答案要点:
- 使用脚本自动执行测试
- 对比预期和实际结果
- 减少人工干预
- 提高测试效率

Q2: 测试金字塔是什么?

答案要点:
- 单元测试(70%)- 底层,快速
- 集成测试(20%)- 中层
- E2E 测试(10%)- 顶层,慢
- 建议:多写单元测试,少写 E2E

6.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
- 考虑因素:技术栈、团队技能、项目需求

参考资料


本文完,感谢阅读!