Các phiên bản 2 của API kiểm thử Compose (createComposeRule, createAndroidComposeRule, runComposeUiTest, runAndroidComposeUiTest, v.v.) hiện đã có sẵn để cải thiện khả năng kiểm soát việc thực thi coroutine. Bản cập nhật này không sao chép toàn bộ nền tảng API; chỉ những API thiết lập môi trường kiểm thử mới được cập nhật.
Các API phiên bản 1 không được dùng nữa và bạn nên di chuyển sang các API mới. Việc di chuyển sẽ xác minh rằng các kiểm thử của bạn phù hợp với hành vi chuẩn của coroutine và tránh các vấn đề về khả năng tương thích trong tương lai. Để biết danh sách các API v1 không dùng nữa, hãy xem các mối liên kết API.
Những thay đổi này có trong androidx.compose.ui:ui-test-junit4:1.11.0-alpha03+ và androidx.compose.ui:ui-test:1.11.0-alpha03+.
Mặc dù các API phiên bản 1 dựa vào UnconfinedTestDispatcher, nhưng các API phiên bản 2 sử dụng StandardTestDispatcher theo mặc định cho thành phần đang chạy. Thay đổi này điều chỉnh hành vi kiểm thử Compose cho phù hợp với các API runTest tiêu chuẩn và cung cấp quyền kiểm soát rõ ràng đối với thứ tự thực thi coroutine.
Liên kết API
Khi nâng cấp lên API v2, bạn thường có thể sử dụng tính năng Tìm và thay thế để cập nhật các lần nhập gói và áp dụng các thay đổi mới về trình điều phối.
Hoặc bạn có thể yêu cầu Gemini thực hiện quy trình di chuyển sang phiên bản 2 của API kiểm thử Compose bằng câu lệnh sau:
Câu lệnh cho AI
Di chuyển từ API kiểm thử phiên bản 1 sang API kiểm thử phiên bản 2
Lời nhắc này sẽ sử dụng hướng dẫn này để di chuyển sang API kiểm thử phiên bản 2.
Migrate to Compose testing v2 APIs using the official
migration guide.Hãy sử dụng bảng sau để liên kết các API v1 không dùng nữa với các API v2 thay thế:
Không dùng nữa (phiên bản 1) |
Thay thế (phiên bản 2) |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Khả năng tương thích ngược và các trường hợp ngoại lệ
Các API v1 hiện tại đã ngừng hoạt động, nhưng hãy tiếp tục sử dụng UnconfinedTestDispatcher để duy trì hành vi hiện có và ngăn chặn các thay đổi gây lỗi.
Sau đây là trường hợp ngoại lệ duy nhất mà hành vi mặc định đã thay đổi:
Trình điều phối kiểm thử mặc định dùng để chạy thành phần trong lớp AndroidComposeUiTestEnvironment đã chuyển từ UnconfinedTestDispatcher sang StandardTestDispatcher. Điều này ảnh hưởng đến các trường hợp mà bạn tạo một thực thể bằng hàm khởi tạo hoặc lớp con AndroidComposeUiTestEnvironment và gọi hàm khởi tạo đó.
Thay đổi chính: Tác động đến quá trình thực thi coroutine
Điểm khác biệt chính giữa phiên bản 1 và phiên bản 2 của API là cách các coroutine được gửi đi:
- API phiên bản 1 (
UnconfinedTestDispatcher): Khi một coroutine được khởi chạy, coroutine đó sẽ thực thi ngay trên luồng hiện tại, thường hoàn tất trước khi dòng mã kiểm thử tiếp theo chạy. Không giống như hành vi trong quá trình phát hành chính thức, việc thực thi ngay lập tức này có thể vô tình che giấu các vấn đề về thời gian thực hoặc điều kiện xung đột sẽ xảy ra trong một ứng dụng đang hoạt động. - API phiên bản 2 (
StandardTestDispatcher): Khi một coroutine được khởi chạy, coroutine đó sẽ được xếp hàng đợi và không thực thi cho đến khi kiểm thử tiến hành rõ ràng đồng hồ ảo. Các API kiểm thử Compose tiêu chuẩn (chẳng hạn nhưwaitForIdle()) đã xử lý quá trình đồng bộ hoá này, vì vậy, hầu hết các kiểm thử dựa vào những API tiêu chuẩn này sẽ tiếp tục hoạt động mà không có thay đổi.
Các lỗi thường gặp và cách khắc phục
Nếu các kiểm thử của bạn không thành công sau khi nâng cấp lên phiên bản 2, thì có thể chúng sẽ có mẫu sau:
- Thất bại: Bạn khởi chạy một tác vụ (ví dụ: ViewModel tải dữ liệu), nhưng câu lệnh xác nhận của bạn thất bại ngay lập tức vì dữ liệu vẫn ở trạng thái "Đang tải".
- Nguyên nhân: Với API v2, các coroutine được xếp hàng đợi thay vì thực thi ngay lập tức. Tác vụ được xếp hàng nhưng chưa bao giờ thực sự chạy trước khi kết quả được kiểm tra.
- Khắc phục: Tăng thời gian một cách rõ ràng. Bạn phải cho trình điều phối phiên bản 2 biết rõ thời điểm thực thi công việc.
Phương pháp trước
Trong phiên bản 1, tác vụ này được khởi chạy và hoàn tất ngay lập tức. Trong phiên bản 2, mã sau đây sẽ không thành công vì loadData() chưa thực sự chạy.
// In v1, this launched and finished immediately.
viewModel.loadData()
// In v2, this fails because loadData() hasn't actually run yet!
assertEquals(Success, viewModel.state.value)
Phương pháp đề xuất
Sử dụng waitForIdle hoặc runOnIdle để thực thi các tác vụ trong hàng đợi trước khi xác nhận.
Lựa chọn 1: Sử dụng waitForIdle để chuyển đồng hồ cho đến khi giao diện người dùng ở trạng thái rảnh, xác minh rằng coroutine đã chạy.
viewModel.loadData()
// Explicitly run all queued tasks
composeTestRule.waitForIdle()
assertEquals(Success, viewModel.state.value)
Lựa chọn 2: Sử dụng runOnIdle để thực thi khối mã trên luồng giao diện người dùng sau khi giao diện người dùng chuyển sang trạng thái rảnh.
viewModel.loadData()
// Run the assertion after the UI is idle
composeTestRule.runOnIdle {
assertEquals(Success, viewModel.state.value)
}
Đồng bộ hoá thủ công
Trong các trường hợp liên quan đến việc đồng bộ hoá theo cách thủ công, chẳng hạn như khi tính năng tự động chuyển tiếp bị tắt, việc chạy một coroutine không dẫn đến việc thực thi ngay lập tức vì đồng hồ kiểm thử bị tạm dừng. Để thực thi các coroutine trong hàng đợi mà không cần chuyển đồng hồ ảo, hãy sử dụng API runCurrent(). Thao tác này chạy các tác vụ được lên lịch cho thời gian ảo hiện tại.
composeTestRule.mainClock.scheduler.runCurrent()
Ngược lại với waitForIdle() (đẩy nhanh đồng hồ kiểm thử cho đến khi giao diện người dùng ổn định), runCurrent() sẽ thực thi các tác vụ đang chờ xử lý trong khi vẫn duy trì thời gian ảo hiện tại. Hành vi này cho phép xác minh các trạng thái trung gian mà nếu không, sẽ bị bỏ qua nếu đồng hồ được chuyển sang trạng thái rảnh.
Trình lập lịch kiểm thử cơ bản được dùng trong môi trường kiểm thử sẽ được hiển thị. Bạn có thể dùng trình lập lịch này kết hợp với API runTest của Kotlin để đồng bộ hoá đồng hồ kiểm thử.
Di chuyển sang runComposeUiTest
Nếu đang sử dụng các API kiểm thử Compose cùng với API runTest Kotlin, bạn nên chuyển sang runComposeUiTest.
Phương pháp trước
Việc sử dụng createComposeRule cùng với runTest sẽ tạo ra 2 đồng hồ riêng biệt: một cho Compose và một cho phạm vi coroutine kiểm thử. Cấu hình này có thể buộc bạn đồng bộ hoá trình lập lịch kiểm thử theo cách thủ công.
@get:Rule val composeTestRule = createComposeRule() @Test fun testWithCoroutines() { composeTestRule.setContent { var status by remember { mutableStateOf("Loading...") } LaunchedEffect(Unit) { delay(1000) status = "Done!" } Text(text = status) } // NOT RECOMMENDED // Fails: runTest creates a new, separate scheduler. // Advancing time here does NOT advance the compose clock. // To fix this without migrating, you would need to share the scheduler // by passing 'composeTestRule.mainClock.scheduler' to runTest. runTest { composeTestRule.onNodeWithText("Loading...").assertIsDisplayed() advanceTimeBy(1000) composeTestRule.onNodeWithText("Done!").assertIsDisplayed() } }
Phương pháp đề xuất
API runComposeUiTest sẽ tự động thực thi khối kiểm thử trong phạm vi runTest riêng. Đồng hồ kiểm thử được đồng bộ hoá với môi trường Compose, nên bạn không cần quản lý trình lập lịch theo cách thủ công nữa.
@Test fun testWithCoroutines() = runComposeUiTest { setContent { var status by remember { mutableStateOf("Loading...") } LaunchedEffect(Unit) { delay(1000) status = "Done!" } Text(text = status) } onNodeWithText("Loading...").assertIsDisplayed() mainClock.advanceTimeBy(1000 + 16 /* Frame buffer */) onNodeWithText("Done!").assertIsDisplayed() } }