1. Giới thiệu
Trong lớp học lập trình Điều hướng giữa các màn hình bằng Compose, bạn đã tìm hiểu cách thêm tính năng điều hướng vào ứng dụng Compose bằng thành phần Jetpack Navigation trong Compose.
Người dùng có thể di chuyển qua các màn hình và thực hiện nhiều thao tác trên ứng dụng Cupcake. Ứng dụng này mang đến cơ hội tuyệt vời để bạn có thể trau dồi kỹ năng kiểm thử tự động! Trong lớp học lập trình này, bạn sẽ viết một số bài kiểm thử giao diện người dùng cho ứng dụng Cupcake và tìm hiểu cách tiếp cận để tối đa hoá phạm vi của kiểm thử.
Điều kiện tiên quyết
- Quen thuộc với ngôn ngữ Kotlin gồm loại hàm, lambda và hàm phạm vi
- Hoàn thành lớp học lập trình Điều hướng giữa các màn hình bằng Compose
Kiến thức bạn sẽ học được
- Kiểm thử thành phần Jetpack Navigation bằng Compose.
- Tạo trạng thái giao diện người dùng nhất quán cho từng kiểm thử giao diện người dùng.
- Tạo các hàm trợ giúp để kiểm thử.
Sản phẩm bạn sẽ tạo ra
- Các kiểm thử giao diện người dùng cho ứng dụng Cupcake
Bạn cần có
- Phiên bản mới nhất của Android Studio
- Kết nối Internet để tải đoạn mã khởi đầu xuống
2. Tải đoạn mã khởi đầu xuống
- Trong Android Studio, hãy mở thư mục
basic-android-kotlin-compose-training-cupcake
. - Mở mã nguồn ứng dụng Cupcake trong Android Studio.
3. Thiết lập ứng dụng Cupcake để kiểm thử giao diện người dùng
Thêm phần phụ thuộc androidTest
Công cụ bản dựng Gradle cho phép bạn thêm phần phụ thuộc cho những mô-đun cụ thể. Chức năng này ngăn việc biên dịch các phần phụ thuộc một cách không cần thiết. Bạn đã quen thuộc với cấu hình implementation
khi đưa các phần phụ thuộc vào một dự án. Bạn đã sử dụng từ khoá này để nhập các phần phụ thuộc vào tệp build.gradle.kts
của mô-đun ứng dụng. Cung cấp phần phụ thuộc cho tất cả các nhóm tài nguyên trong mô-đun đó bằng cách sử dụng từ khoá implementation
; cho đến thời điểm này của khoá học, bạn đã tích luỹ được kinh nghiệm với các nhóm tài nguyên main
, test
và androidTest
.
Các bài kiểm thử giao diện người dùng nằm trong nhóm tài nguyên riêng của chúng có tên là androidTest
. Các phần phụ thuộc chỉ dành cho mô-đun này không cần phải được biên dịch cho các mô-đun khác, chẳng hạn như mô-đun main
chứa mã nguồn ứng dụng. Khi thêm phần phụ thuộc chỉ dành cho các bài kiểm thử giao diện người dùng, hãy sử dụng từ khoá androidTestImplementation
để khai báo phần phụ thuộc đó trong tệp build.gradle.kts
của mô-đun ứng dụng. Việc này đảm bảo là các phần phụ thuộc của kiểm thử giao diện người dùng chỉ được biên dịch khi bạn chạy các bài kiểm thử giao diện người dùng.
Hãy hoàn tất các bước sau để thêm phần phụ thuộc cần thiết cho việc viết mã kiểm thử giao diện người dùng:
- Mở tệp
build.gradle.kts(Module :app)
. - Thêm các phần phụ thuộc sau đây vào phần
dependencies
của tệp:
androidTestImplementation(platform("androidx.compose:compose-bom:2023.05.01"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
androidTestImplementation("androidx.navigation:navigation-testing:2.6.0")
androidTestImplementation("androidx.test.espresso:espresso-intents:3.5.1")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
Tạo thư mục kiểm thử giao diện người dùng
- Nhấp chuột phải vào thư mục
src
trong khung hiển thị dự án rồi chọn New > Directory (Mới > Thư mục).
- Chọn lựa chọn androidTest/java.
Tạo gói kiểm thử
- Nhấp chuột phải vào thư mục
androidTest/java
trong cửa sổ dự án rồi chọn New > Package (Mới > Gói).
- Đặt tên cho gói này là com.example.cupcake.test.
Tạo lớp kiểm thử điều hướng
Trong thư mục test
, hãy tạo một lớp Kotlin mới có tên là CupcakeScreenNavigationTest
.
4. Thiết lập máy chủ điều hướng
Ở một lớp học lập trình trước đó, bạn đã biết là các quy trình kiểm thử giao diện người dùng trong Compose cần có quy tắc kiểm thử Compose. Việc kiểm thử thành phần Jetpack Navigation cũng tương tự như vậy. Tuy nhiên, việc kiểm thử thành phần điều hướng đòi hỏi một số thiết lập bổ sung thông qua quy tắc kiểm thử Compose.
Khi kiểm thử thành phần Điều hướng trong Compose, bạn sẽ không có quyền truy cập vào cùng NavHostController
như đã thực hiện trong mã ứng dụng. Tuy nhiên, bạn có thể sử dụng TestNavHostController
và định cấu hình quy tắc kiểm thử bằng trình điều khiển điều hướng này. Ở phần này, bạn tìm hiểu cách định cấu hình và sử dụng lại quy tắc kiểm thử cho các hoạt động kiểm thử điều hướng.
- Trong
CupcakeScreenNavigationTest.kt
, hãy tạo một quy tắc kiểm thử bằng cách dùngcreateAndroidComposeRule
và truyềnComponentActivity
dưới dạng tham số kiểu.
import androidx.activity.ComponentActivity
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import org.junit.Rule
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
Để đảm bảo ứng dụng điều hướng đến đúng vị trí, bạn cần tham chiếu đến một thực thể TestNavHostController
nhằm kiểm tra tuyến điều hướng của máy chủ điều hướng khi ứng dụng thực hiện các thao tác để điều hướng.
- Tạo thực thể
TestNavHostController
dưới dạng biếnlateinit
. Trong Kotlin, từ khoálateinit
được dùng để khai báo một thuộc tính có thể được khởi tạo sau khi đối tượng được khai báo.
import androidx.navigation.testing.TestNavHostController
private lateinit var navController: TestNavHostController
Tiếp theo, hãy chỉ định thành phần kết hợp mà bạn muốn dùng cho các bài kiểm thử giao diện người dùng.
- Tạo một phương thức có tên là
setupCupcakeNavHost()
. - Trong phương thức
setupCupcakeNavHost()
, hãy gọi phương thứcsetContent()
trên quy tắc kiểm thử Compose mà bạn đã tạo. - Bên trong hàm lambda được truyền vào phương thức
setContent()
, hãy gọi thành phần kết hợpCupcakeApp()
.
import com.example.cupcake.CupcakeApp
fun setupCupcakeNavHost() {
composeTestRule.setContent {
CupcakeApp()
}
}
Bây giờ, bạn cần tạo đối tượng TestNavHostContoller
trong lớp kiểm thử. Bạn sẽ sử dụng đối tượng này sau để xác định trạng thái điều hướng vì ứng dụng sử dụng trình điều khiển để điều hướng nhiều màn hình trong ứng dụng Cupcake.
- Thiết lập máy chủ điều hướng bằng hàm lambda bạn đã tạo trước đó. Chạy biến
navController
đã tạo, đăng ký một trình điều hướng và truyềnTestNavHostController
đó vào thành phần kết hợpCupcakeApp
.
import androidx.compose.ui.platform.LocalContext
fun setupCupcakeNavHost() {
composeTestRule.setContent {
navController = TestNavHostController(LocalContext.current).apply {
navigatorProvider.addNavigator(ComposeNavigator())
}
CupcakeApp(navController = navController)
}
}
Mọi kiểm thử trong lớp CupcakeScreenNavigationTest
đều liên quan đến hoạt động kiểm thử một thành phần điều hướng. Do đó, mỗi lượt kiểm thử phụ thuộc vào đối tượng TestNavHostController
bạn đã tạo. Thay vì phải gọi hàm setupCupcakeNavHost()
theo cách thủ công cho mỗi hoạt động kiểm thử để thiết lập trình điều khiển điều hướng, bạn có thể cài đặt ở chế độ tự động bằng cách sử dụng chú thích @Before
do thư viện junit cung cấp. Khi một phương thức được chú thích bằng @Before
, phương thức đó sẽ chạy trước mọi phương thức được chú thích bằng @Test
.
- Thêm chú thích
@Before
vào phương thứcsetupCupcakeNavHost()
.
import org.junit.Before
@Before
fun setupCupcakeNavHost() {
composeTestRule.setContent {
navController = TestNavHostController(LocalContext.current).apply {
navigatorProvider.addNavigator(ComposeNavigator())
}
CupcakeApp(navController = navController)
}
}
5. Viết mã kiểm thử điều hướng
Xác minh đích bắt đầu
Nhớ lại khi xây dựng ứng dụng Cupcake, bạn đã tạo một lớp enum
có tên là CupcakeScreen
chứa các hằng số để chỉ định cách điều hướng của ứng dụng.
CupcakeScreen.kt
/**
* enum values that represent the screens in the app
*/
enum class CupcakeScreen(@StringRes val title: Int) {
Start(title = R.string.app_name),
Flavor(title = R.string.choose_flavor),
Pickup(title = R.string.choose_pickup_date),
Summary(title = R.string.order_summary)
}
Tất cả các ứng dụng có giao diện người dùng đều có một màn hình chính ở dạng nào đó. Đối với ứng dụng Cupcake, màn hình đó là Start Order Screen (Màn hình bắt đầu đặt hàng). Bộ điều khiển điều hướng trong thành phần kết hợp CupcakeApp
sử dụng mục Start
của enum CupcakeScreen
để xác định thời điểm điều hướng đến màn hình này. Khi ứng dụng khởi động, nếu chưa có tuyến đích nào thì tuyến đích của máy chủ điều hướng sẽ được đặt thành CupcakeScreen.Start.name
.
Trước tiên, bạn cần viết một hoạt động kiểm thử để xác minh rằng Start Order Screen (Màn hình bắt đầu đặt hàng) là tuyến đích hiện tại khi ứng dụng khởi động.
- Tạo một hàm có tên là
cupcakeNavHost_verifyStartDestination()
rồi chú thích hàm đó bằng@Test
.
import org.junit.Test
@Test
fun cupcakeNavHost_verifyStartDestination() {
}
Bây giờ, bạn phải xác nhận tuyến đích đến ban đầu của trình điều khiển điều hướng là Start Order Screen(Màn hình bắt đầu đặt hàng).
- Xác nhận tên tuyến dự kiến (trong trường hợp này là
CupcakeScreen.Start.name
) là tuyến đích của mục nhập trong ngăn xếp lui hiện tại của trình điều khiển điều hướng.
import org.junit.Assert.assertEquals
...
@Test
fun cupcakeNavHost_verifyStartDestination() {
assertEquals(CupcakeScreen.Start.name, navController.currentBackStackEntry?.destination?.route)
}
Tạo các phương thức trình trợ giúp
Việc kiểm thử giao diện người dùng thường yêu cầu lặp lại các bước để đặt giao diện người dùng vào trạng thái mà một phần giao diện người dùng cụ thể có thể được kiểm thử. Giao diện người dùng tuỳ chỉnh cũng có thể yêu cầu các câu nhận định phức tạp yêu cầu nhiều dòng mã. Câu nhận định đã viết trong phần trước cần có nhiều mã và bạn cũng nhiều lần sử dụng câu nhận định đó khi kiểm thử tính năng điều hướng trong ứng dụng Cupcake. Trong những trường hợp như vậy, việc viết các phương thức trợ giúp trong hoạt động kiểm thử sẽ giúp bạn không phải viết những đoạn mã trùng lặp.
Đối với mỗi kiểm thử của thành phần điều hướng mà bạn viết, hãy sử dụng thuộc tính name
của các mục enum CupcakeScreen
để kiểm tra xem tuyến đích hiện tại của trình điều khiển điều hướng có chính xác hay không. Hãy viết một hàm trợ giúp mà bạn có thể gọi bất cứ khi nào cần đưa ra một câu nhận định tương tự.
Hoàn thành các bước sau để tạo hàm trợ giúp này:
- Tạo một tệp Kotlin trống trong thư mục
test
với tênScreenAssertions
.
- Thêm một hàm tiện ích vào lớp
NavController
có tên làassertCurrentRouteName()
và truyền một chuỗi cho tên tuyến dự kiến trong chữ ký phương thức.
fun NavController.assertCurrentRouteName(expectedRouteName: String) {
}
- Trong hàm này, hãy xác nhận là
expectedRouteName
bằng với tuyến đích của mục nhập trong ngăn xếp lui hiện tại của trình điều khiển điều hướng.
import org.junit.Assert.assertEquals
...
fun NavController.assertCurrentRouteName(expectedRouteName: String) {
assertEquals(expectedRouteName, currentBackStackEntry?.destination?.route)
}
- Mở tệp CupcakeScreenNavigationTest rồi sửa đổi hàm
cupcakeNavHost_verifyStartDestination()
để dùng hàm mở rộng mới thay vì câu nhận định dài.
@Test
fun cupcakeNavHost_verifyStartDestination() {
navController.assertCurrentRouteName(CupcakeScreen.Start.name)
}
Một số chương trình kiểm thử cũng yêu cầu tương tác với các thành phần giao diện người dùng. Trong lớp học lập trình này, thường thì bạn sẽ sử dụng tài nguyên chuỗi để tìm các thành phần đó. Bạn có thể dùng tài nguyên chuỗi để truy cập một thành phần kết hợp bằng phương thức Context.getString()
(tham khảo về phương thức này tại đây). Khi viết chương trình kiểm thử giao diện người dùng trong Compose, quy trình triển khai phương thức này sẽ có dạng như sau:
composeTestRule.onNodeWithText(composeTestRule.activity.getString(R.string.my_string)
Đây là hướng dẫn chi tiết, và bạn có thể đơn giản hoá hướng dẫn này bằng cách thêm hàm mở rộng.
- Tạo một tệp mới trong gói
com.example.cupcake.test
tên là ComposeRuleExtensions.kt. Hãy đảm bảo rằng đây là một tệp Kotlin thuần tuý.
- Thêm đoạn mã sau đây vào tệp đó.
import androidx.activity.ComponentActivity
import androidx.annotation.StringRes
import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.ext.junit.rules.ActivityScenarioRule
fun <A : ComponentActivity> AndroidComposeTestRule<ActivityScenarioRule<A>, A>.onNodeWithStringId(
@StringRes id: Int
): SemanticsNodeInteraction = onNodeWithText(activity.getString(id))
Hàm mở rộng này cho phép bạn giảm số lượng mã phải viết khi tìm một thành phần giao diện người dùng theo tài nguyên chuỗi của thành phần đó. Thay vì viết:
composeTestRule.onNodeWithText(composeTestRule.activity.getString(R.string.my_string)
Giờ thì bạn có thể sử dụng hướng dẫn sau đây:
composeTestRule.onNodeWithStringId(R.string.my_string)
Xác minh rằng màn hình Start (Bắt đầu) không có nút Up (Lên)
Thiết kế ban đầu của ứng dụng Cupcake không có nút Up (Lên) trên thanh công cụ của màn hình Start (Bắt đầu).
Màn hình Start (Bắt đầu) thiếu nút vì đó là màn hình đầu tiên, nên không có màn hình nào để di chuyển Lên. Làm theo các bước sau để tạo một hàm xác nhận màn hình Start (Bắt đầu) không có nút Up (Lên):
- Tạo một hàm có tên là
cupcakeNavHost_verifyBackNavigationNotShownOnStartOrderScreen()
rồi chú thích hàm đó bằng@Test
.
@Test
fun cupcakeNavHost_verifyBackNavigationNotShownOnStartOrderScreen() {
}
Trong ứng dụng Cupcake, nút Up (Lên) có nội dung mô tả được đặt thành chuỗi trong tài nguyên R.string.back_button
.
- Tạo một biến trong hàm kiểm thử bằng giá trị của tài nguyên
R.string.back_button
.
@Test
fun cupcakeNavHost_verifyBackNavigationNotShownOnStartOrderScreen() {
val backText = composeTestRule.activity.getString(R.string.back_button)
}
- Xác nhận là một nốt có phần mô tả nội dung này không tồn tại trên màn hình.
@Test
fun cupcakeNavHost_verifyBackNavigationNotShownOnStartOrderScreen() {
val backText = composeTestRule.activity.getString(R.string.back_button)
composeTestRule.onNodeWithContentDescription(backText).assertDoesNotExist()
}
Xác minh điều hướng đến màn hình Flavor (Hương vị)
Nhấp vào một trong các nút trên màn hình Start (Bắt đầu) để kích hoạt phương thức hướng dẫn trình điều khiển điều hướng di chuyển đến màn hình Flavor (Hương vị).
Trong gói kiểm thử này, bạn viết một lệnh để khi nhấp vào nút sẽ kích hoạt thao tác điều hướng và xác minh tuyến đích đến là màn hình Flavor (Hương vị).
- Tạo một hàm có tên là
cupcakeNavHost_clickOneCupcake_navigatesToSelectFlavorScreen()
rồi chú thích hàm đó bằng@Test
.
@Test
fun cupcakeNavHost_clickOneCupcake_navigatesToSelectFlavorScreen(){
}
- Tìm nút One Cupcake theo mã tài nguyên chuỗi rồi thực hiện thao tác nhấp vào nút đó.
import com.example.cupcake.R
...
@Test
fun cupcakeNavHost_clickOneCupcake_navigatesToSelectFlavorScreen() {
composeTestRule.onNodeWithStringId(R.string.one_cupcake)
.performClick()
}
- Xác nhận tên tuyến hiện tại là tên màn hình Hương vị.
@Test
fun cupcakeNavHost_clickOneCupcake_navigatesToSelectFlavorScreen() {
composeTestRule.onNodeWithStringId(R.string.one_cupcake)
.performClick()
navController.assertCurrentRouteName(CupcakeScreen.Flavor.name)
}
Viết thêm các phương thức trợ giúp
Ứng dụng Cupcake có quy trình điều hướng chủ yếu là tuyến tính. Khi nhấp vào nút Cancel (Huỷ), bạn chỉ có thể điều hướng trong ứng dụng theo một hướng. Do đó, bạn có thể tìm thấy mã lặp lại để điều hướng đến các khu vực mình muốn kiểm thử trong khi kiểm thử các màn hình ứng dụng một cách kỹ càng. Tình huống này cần sử dụng thêm các phương thức trợ giúp để bạn chỉ phải viết mã đó một lần.
Giờ đây khi đã kiểm thử tính năng điều hướng đến màn hình Hương vị, bạn hãy tạo một phương thức điều hướng đến màn hình Hương vị để không phải lặp lại mã đó cho các kiểm thử sau này.
- Tạo một phương thức có tên là
navigateToFlavorScreen()
.
private fun navigateToFlavorScreen() {
}
- Viết một lệnh để tìm nút One Cupcake (1 bánh nướng) và thực hiện thao tác nhấp như bạn đã làm trong phần trước.
private fun navigateToFlavorScreen() {
composeTestRule.onNodeWithStringId(R.string.one_cupcake)
.performClick()
}
Hãy nhớ là bạn sẽ không thể nhấp vào nút Next (Tiếp theo) trên màn hình hương vị cho đến khi bạn chọn một hương vị. Phương thức này chỉ dùng để chuẩn bị cho giao diện người dùng để điều hướng. Sau khi gọi phương thức này, giao diện người dùng phải ở trạng thái mà nút Next (Tiếp theo) có thể nhấp được.
- Tìm một nút trong giao diện người dùng bằng chuỗi
R.string.chocolate
và thực hiện thao tác nhấp vào nút đó để chọn.
private fun navigateToFlavorScreen() {
composeTestRule.onNodeWithStringId(R.string.one_cupcake)
.performClick()
composeTestRule.onNodeWithStringId(R.string.chocolate)
.performClick()
}
Xem liệu bạn có thể viết các phương thức trợ giúp để điều hướng đến màn hình Pickup (Lấy hàng) và màn hình Summary (Tóm tắt) hay không. Hãy thử làm bài tập này trước khi xem giải pháp.
Hãy sử dụng mã sau để thực hiện việc này:
private fun getFormattedDate(): String {
val calendar = Calendar.getInstance()
calendar.add(java.util.Calendar.DATE, 1)
val formatter = SimpleDateFormat("E MMM d", Locale.getDefault())
return formatter.format(calendar.time)
}
private fun navigateToPickupScreen() {
navigateToFlavorScreen()
composeTestRule.onNodeWithStringId(R.string.next)
.performClick()
}
private fun navigateToSummaryScreen() {
navigateToPickupScreen()
composeTestRule.onNodeWithText(getFormattedDate())
.performClick()
composeTestRule.onNodeWithStringId(R.string.next)
.performClick()
}
Khi kiểm thử các màn hình ngoài màn hình Bắt đầu, bạn cần lập kế hoạch kiểm thử chức năng của nút Mũi tên lên để đảm bảo chức năng này giúp chuyển hướng đến màn hình trước. Cân nhắc việc tạo một chức năng trợ giúp để tìm và nhấp vào nút Up (Mũi tên lên).
private fun performNavigateUp() {
val backText = composeTestRule.activity.getString(R.string.back_button)
composeTestRule.onNodeWithContentDescription(backText).performClick()
}
Tối đa hoá phạm vi (mức độ bao phủ) của kiểm thử
Bộ kiểm thử của một ứng dụng nên kiểm thử được nhiều chức năng của ứng dụng nhất có thể. Một bộ kiểm thử giao diện người dùng lý tưởng là có thể kiểm thử 100% chức năng của giao diện người dùng. Trên thực tế, khó có thể đạt được phạm vi (mức độ bao phủ) của chương trình kiểm thử này vì nhiều yếu tố bên ngoài ứng dụng có thể ảnh hưởng đến giao diện người dùng, chẳng hạn như kích thước riêng biệt của một số thiết bị, các phiên bản hệ điều hành Android cũng như ứng dụng bên thứ ba có thể ảnh hưởng đến những ứng dụng khác trên điện thoại.
Một cách để tối đa hoá phạm vi (mức độ bao phủ) của chương trình kiểm thử là viết nội dung cùng với tính năng khi bạn thêm những tính năng này vào chương trình kiểm thử. Bằng cách đó, bạn tránh được việc bỏ quá xa các tính năng mới và phải quay lại để nắm được mọi tình huống có thể xảy ra. Tại thời điểm này, Cupcake là một ứng dụng khá nhỏ và bạn đã kiểm thử một phần đáng kể tính năng điều hướng của ứng dụng! Tuy nhiên, có nhiều trạng thái điều hướng hơn để kiểm thử.
Hãy xem liệu bạn có thể viết mã kiểm thử để xác minh các trạng thái điều hướng sau đây hay không. Hãy thử tự mình triển khai trước khi xem giải pháp.
- Chuyển đến màn hình Bắt đầu bằng cách nhấp vào nút Mũi tên lên trên màn hình Hương vị
- Chuyển đến màn hình Bắt đầu bằng cách nhấp vào nút Cancel (Huỷ) trên màn hình Hương vị
- Chuyển đến màn hình Nhận hàng
- Chuyển đến màn hình Hương vị bằng cách nhấp vào nút Mũi tên lên trên màn hình Lấy hàng
- Chuyển đến màn hình Bắt đầu bằng cách nhấp vào nút Cancel (Huỷ) trên màn hình Hương vị
- Chuyển đến màn hình Tóm tắt
- Chuyển đến màn hình Start (Bắt đầu) bằng cách nhấp vào nút Cancel (Huỷ) trên màn hình Summary (Tóm tắt)
@Test
fun cupcakeNavHost_clickNextOnFlavorScreen_navigatesToPickupScreen() {
navigateToFlavorScreen()
composeTestRule.onNodeWithStringId(R.string.next)
.performClick()
navController.assertCurrentRouteName(CupcakeScreen.Pickup.name)
}
@Test
fun cupcakeNavHost_clickBackOnFlavorScreen_navigatesToStartOrderScreen() {
navigateToFlavorScreen()
performNavigateUp()
navController.assertCurrentRouteName(CupcakeScreen.Start.name)
}
@Test
fun cupcakeNavHost_clickCancelOnFlavorScreen_navigatesToStartOrderScreen() {
navigateToFlavorScreen()
composeTestRule.onNodeWithStringId(R.string.cancel)
.performClick()
navController.assertCurrentRouteName(CupcakeScreen.Start.name)
}
@Test
fun cupcakeNavHost_clickNextOnPickupScreen_navigatesToSummaryScreen() {
navigateToPickupScreen()
composeTestRule.onNodeWithText(getFormattedDate())
.performClick()
composeTestRule.onNodeWithStringId(R.string.next)
.performClick()
navController.assertCurrentRouteName(CupcakeScreen.Summary.name)
}
@Test
fun cupcakeNavHost_clickBackOnPickupScreen_navigatesToFlavorScreen() {
navigateToPickupScreen()
performNavigateUp()
navController.assertCurrentRouteName(CupcakeScreen.Flavor.name)
}
@Test
fun cupcakeNavHost_clickCancelOnPickupScreen_navigatesToStartOrderScreen() {
navigateToPickupScreen()
composeTestRule.onNodeWithStringId(R.string.cancel)
.performClick()
navController.assertCurrentRouteName(CupcakeScreen.Start.name)
}
@Test
fun cupcakeNavHost_clickCancelOnSummaryScreen_navigatesToStartOrderScreen() {
navigateToSummaryScreen()
composeTestRule.onNodeWithStringId(R.string.cancel)
.performClick()
navController.assertCurrentRouteName(CupcakeScreen.Start.name)
}
6. Viết đoạn mã kiểm thử cho màn hình Order (Đơn đặt hàng)
Thành phần điều hướng chỉ là một thành phần trong chức năng của ứng dụng Cupcake. Người dùng cũng tương tác với từng màn hình ứng dụng. Bạn cần xác minh nội dung xuất hiện cũng như thao tác được thực hiện trên những màn hình này có thể mang lại kết quả chính xác. SelectOptionScreen là một phần quan trọng của ứng dụng.
Ở phần này, bạn sẽ viết một mã kiểm thử để xác minh là nội dung trên màn hình này đã được thiết lập chính xác.
Kiểm thử nội dung màn hình Chọn hương vị
- Tạo một lớp mới bên trong thư mục
app/src/androidTest
có tên làCupcakeOrderScreenTest
, nơi chứa các tệp kiểm thử khác của bạn.
- Trong lớp này, hãy tạo một
AndroidComposeTestRule
.
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
- Tạo một hàm có tên là
selectOptionScreen_verifyContent()
rồi chú thích hàm đó bằng@Test
.
@Test
fun selectOptionScreen_verifyContent() {
}
Cuối cùng, trong hàm này, bạn sẽ thiết lập nội dung quy tắc Compose thành SelectOptionScreen
. Cách làm trên đảm bảo rằng thành phần kết hợp SelectOptionScreen
sẽ chạy ngay nên bạn không cần phải điều hướng. Tuy nhiên, màn hình này yêu cầu 2 tham số: tổng phụ và một danh sách các lựa chọn về hương vị.
- Hãy tạo tổng phụ và một danh sách các tuỳ chọn về hương vị để truyền vào màn hình.
@Test
fun selectOptionScreen_verifyContent() {
// Given list of options
val flavors = listOf("Vanilla", "Chocolate", "Hazelnut", "Cookie", "Mango")
// And subtotal
val subtotal = "$100"
}
- Đặt nội dung thành thành phần kết hợp
SelectOptionScreen
bằng các giá trị bạn vừa tạo.
Lưu ý rằng phương pháp này tương tự như việc khởi chạy một thành phần kết hợp trong MainActivity
. Điểm khác biệt duy nhất là MainActivity
gọi thành phần kết hợp CupcakeApp
và ở đây là bạn đang gọi thành phần kết hợp SelectOptionScreen
. Việc có thể thay đổi thành phần kết hợp được chạy từ setContent()
cho phép bạn chạy các thành phần kết hợp cụ thể thay vì yêu cầu chương trình kiểm thử đi theo các bước thông qua ứng dụng một cách rõ ràng để đến vị trí bạn muốn kiểm thử. Cách tiếp cận trên giúp ngăn chặn các hoạt động kiểm thử không đạt ở những vùng mã không liên quan đến quá trình kiểm thử hiện tại.
@Test
fun selectOptionScreen_verifyContent() {
// Given list of options
val flavors = listOf("Vanilla", "Chocolate", "Hazelnut", "Cookie", "Mango")
// And subtotal
val subtotal = "$100"
// When SelectOptionScreen is loaded
composeTestRule.setContent {
SelectOptionScreen(subtotal = subtotal, options = flavors)
}
}
Ở thời điểm này của quá trình kiểm thử, ứng dụng sẽ chạy thành phần kết hợp SelectOptionScreen
và bạn có thể tương tác thông qua hướng dẫn kiểm thử.
- Lặp lại qua danh sách
flavors
và bảo đảm mỗi mục của chuỗi trong danh sách được hiển thị trên màn hình. - Dùng phương thức
onNodeWithText()
để tìm văn bản trên màn hình, đồng thời sử dụng phương thứcassertIsDisplayed()
để xác minh văn bản đã xuất hiện trong ứng dụng.
@Test
fun selectOptionScreen_verifyContent() {
// Given list of options
val flavors = listOf("Vanilla", "Chocolate", "Hazelnut", "Cookie", "Mango")
// And subtotal
val subtotal = "$100"
// When SelectOptionScreen is loaded
composeTestRule.setContent {
SelectOptionScreen(subtotal = subtotal, options = flavors)
}
// Then all the options are displayed on the screen.
flavors.forEach { flavor ->
composeTestRule.onNodeWithText(flavor).assertIsDisplayed()
}
}
- Sử dụng cùng một kỹ thuật để xác minh rằng ứng dụng có cho thấy văn bản và trình bày đúng chuỗi của tổng giá tiền trên màn hình. Tìm kiếm mã tài nguyên
R.string.subtotal_price
và giá trị tổng giá tiền chính xác trên màn hình, sau đó xác nhận việc ứng dụng sẽ cho thấy giá trị này.
import com.example.cupcake.R
...
@Test
fun selectOptionScreen_verifyContent() {
// Given list of options
val flavors = listOf("Vanilla", "Chocolate", "Hazelnut", "Cookie", "Mango")
// And subtotal
val subtotal = "$100"
// When SelectOptionScreen is loaded
composeTestRule.setContent {
SelectOptionScreen(subtotal = subtotal, options = flavors)
}
// Then all the options are displayed on the screen.
flavors.forEach { flavor ->
composeTestRule.onNodeWithText(flavor).assertIsDisplayed()
}
// And then the subtotal is displayed correctly.
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(
R.string.subtotal_price,
subtotal
)
).assertIsDisplayed()
}
Hãy nhớ là nút Next (Tiếp theo) sẽ không hoạt động cho đến khi bạn chọn một mục. Quy trình kiểm thử này chỉ xác minh nội dung trên màn hình, vì vậy, bước kiểm tra cuối cùng là nút Next (Tiếp theo) bị vô hiệu hoá.
- Tìm nút Next (Tiếp theo) bằng cách sử dụng cùng một phương pháp để tìm nút theo mã tài nguyên chuỗi. Tuy nhiên, thay vì xác minh rằng ứng dụng hiển thị các nút, hãy sử dụng phương thức
assertIsNotEnabled()
.
@Test
fun selectOptionScreen_verifyContent() {
// Given list of options
val flavors = listOf("Vanilla", "Chocolate", "Hazelnut", "Cookie", "Mango")
// And subtotal
val subtotal = "$100"
// When SelectOptionScreen is loaded
composeTestRule.setContent {
SelectOptionScreen(subtotal = subtotal, options = flavors)
}
// Then all the options are displayed on the screen.
flavors.forEach { flavor ->
composeTestRule.onNodeWithText(flavor).assertIsDisplayed()
}
// And then the subtotal is displayed correctly.
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(
R.string.subtotal_price,
subtotal
)
).assertIsDisplayed()
// And then the next button is disabled
composeTestRule.onNodeWithStringId(R.string.next).assertIsNotEnabled()
}
Tối đa hoá phạm vi (mức độ bao phủ) của kiểm thử
Việc kiểm thử nội dung màn hình Chọn hương vị chỉ kiểm tra một khía cạnh của một màn hình đơn. Bạn có thể viết thêm một số bài kiểm thử để tăng phạm vi sử dụng (mức độ bao phủ) của mã. Hãy thử tự viết các kiểm thử sau đây trước khi tải mã giải pháp xuống.
- Xác minh nội dung của màn hình Bắt đầu.
- Xác minh nội dung trên màn hình Summary (Tóm tắt).
- Đảm bảo nút Next (Tiếp theo) đang bật khi bạn chọn một lựa chọn trên màn hình Choose Flavor (Chọn hương vị).
Khi viết chương trình kiểm thử, nhớ rằng chức năng trợ giúp có thể làm giảm số lượng mã cần viết!
7. Lấy mã giải pháp
Để tải xuống mã cho lớp học lập trình đã kết thúc, bạn có thể sử dụng lệnh git này:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-cupcake.git
Ngoài ra, bạn có thể tải kho lưu trữ xuống dưới dạng tệp zip, sau đó giải nén và mở trong Android Studio.
Nếu bạn muốn xem mã giải pháp, hãy xem mã đó trên GitHub.
8. Tóm tắt
Xin chúc mừng! Bạn đã tìm hiểu cách kiểm thử thành phần Điều hướng Jetpack. Bạn cũng đã tìm hiểu một số kỹ năng cơ bản để viết chương trình kiểm thử giao diện người dùng, chẳng hạn như viết các phương thức trình trợ giúp có thể sử dụng lại, cách tận dụng setContent()
để viết chương trình kiểm thử một cách ngắn gọn, cách thiết lập chương trình kiểm thử bằng chú thích @Before
cũng như tìm hiểu cách tối đa hoá phạm vi (mức độ bao phủ) của chương trình kiểm thử. Trong quá trình xây dựng các ứng dụng Android, hãy nhớ viết các chương trình kiểm thử cùng với mã tính năng!