Kiểm thử ứng dụng Cupcake

Sử dụng bộ sưu tập để sắp xếp ngăn nắp các trang Lưu và phân loại nội dung dựa trên lựa chọn ưu tiên của bạn.

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 bằng thành phần Điều hướng Jetpack 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 vị của kiểm thử.

Điều kiện tiên quyết

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 mã khởi động xuống

2. Tải mã khởi động

  1. Trong Android Studio, hãy mở thư mục basic-android-kotlin-compose-training-cupcake.
  2. Mở mã ứ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 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, testandroidTest.

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ã ứ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 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:

  1. Mở tệp app/build.gradle.
  2. Thêm các phần phụ thuộc sau đây vào phần phụ thuộc của tệp:
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.5.0'
androidTestImplementation "androidx.navigation:navigation-testing:2.5.3"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"

Tạo thư mục cho kiểm thử giao diện người dùng

  1. Nhấp chuột phải vào thư mục src trong chế độ xem dự án rồi chọn New (Mới) > Directory (Thư mục).

288ebc5eae5fba2e.png

  1. Chọn tuỳ chọn androidTest/java.

12ec57f9d9e907de.png

Tạo gói kiểm thử

  1. Nhấp chuột phải vào thư mục androidTest/java trong cửa sổ dự án rồi chọn New (Mới) > Package (Gói).

5104da51d0529eb7.png

  1. Đặt tên cho gói này là com.example.cupcake.test. da0e0458fdcb1cfc.png

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 với tên là CupcakeScreenNavigationTest.

7336dbed4e215b68.png

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 Điều hướng Jetpack 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.

  1. Trong CupcakeScreenNavigationTest.kt, hãy tạo một quy tắc kiểm thử bằng cách dùng createAndroidComposeRule và truyền ComponentActivity dưới dạng loại tham số.
@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.

  1. Tạo bản sao TestNavHostController dưới dạng biến lateinit. 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.
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.

  1. Tạo một phương thức có tên là setupCupcakeNavHost().
  2. Trong phương thức setupCupcakeNavHost(), hãy gọi phương thức setContent() trên quy tắc kiểm thử Compose mà bạn đã tạo.
  3. 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ợp 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.

  1. 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ền TestNavHostController đó vào thành phần kết hợp CupcakeApp.
fun setupCupcakeNavHost() {
   composeTestRule.setContent {
       navController =
           TestNavHostController(LocalContext.current)
       navController.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.

  1. Thêm chú thích @Before vào phương thức setupCupcakeNavHost().
@Before
fun setupCupcakeNavHost() {
   composeTestRule.setContent {
       navController =
           TestNavHostController(LocalContext.current)
       navController.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à Cupcake 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ả ứ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à 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 Cupcake.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.

  1. Tạo một hàm có tên là cupcakeNavHost_verifyStartDestination() rồi chú thích hàm đó bằng @Test.
@Test
fun cupcakeNavHost_verifyStartDestination() {
}

Bây giờ, bạn phải xác nhận tuyến ban đầu của trình điều khiển điều hướng là màn hình Bắt đầu đặt hàng.

  1. 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 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 chức năng 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:

  1. Tạo một tệp Kotlin trống trong thư mục test có tên là ScreenAssertions.

ac62e5b9b8153027.png

  1. Thêm một hàm mở rộng vào lớp NavController 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) {

}
  1. 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)
}
  1. 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.

  1. 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ý.

82762f66f890cf1b.png

  1. Thêm 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 Bắt đầu không có nút Mũi tên lên

Thiết kế ban đầu của ứng dụng Cupcake không có nút Up (Mũi tên lên) trên thanh công cụ của màn hình Start (Bắt đầu).

7675f1aa302f998f.png

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 (Mũi tên lên):

  1. 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 Mũi tên lên có nội dung mô tả được đặt thành chuỗi trong tài nguyên R.string.back_button.

  1. 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)
}
  1. 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 Hương vị

Nhấp vào một trong các nút trên màn hình Bắt đầu sẽ kích hoạt mộ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ị)

bd1a5fed3a48b174.png

Trong hoạt động 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 là màn hình Flavor (Hương vị).

  1. 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(){
}
  1. 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 đó.
@Test
fun cupcakeNavHost_clickOneCupcake_navigatesToSelectFlavorScreen() {
   composeTestRule.onNodeWithStringId(R.string.one_cupcake)
       .performClick()
}
  1. 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.

  1. Tạo một phương thức có tên là navigateToFlavorScreen().
private fun navigateToFlavorScreen() {
}
  1. 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.

  1. 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.

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 mã kiểm thử cho màn hình Đơ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ị

  1. 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.

b8d85fba1fabedef.png

  1. Trong lớp này, hãy tạo một AndroidComposeTestRule.
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
  1. 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ố: một là tổng giá tiền và một là danh sách các lựa chọn hương vị.

  1. Tạo một danh sách các hương vị và một tổng giá trị thành tiền để truyền vào màn hình.
@Test
fun selectOptionScreen_verifyContent() {
   // Given list of options
   val flavours = listOf("Vanilla", "Chocolate", "Hazelnut", "Cookie", "Mango")
   // And sub total
   val subTotal = "$100"
}
  1. Thiết lập nội dung thành thành phần kết hợp SelectOptionScreen bằng CupcakeTheme, sử dụng các giá trị bạn vừa tạo.

Lưu ý là 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 flavours = listOf("Vanilla", "Chocolate", "Hazelnut", "Cookie", "Mango")
   // And sub total
   val subTotal = "$100"

   // When SelectOptionScreen is loaded
   composeTestRule.setContent {
       CupcakeTheme {
           SelectOptionScreen(subtotal = subTotal, options = flavours)
       }
   }
}

Ở 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ử.

  1. Lặp lại qua danh sách flavours 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.
  2. 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ức assertIsDisplayed() để xác minh văn bản đã xuất hiện trong ứng dụng.
@Test
fun selectOptionScreen_verifyContent() {
   // Given list of options
   val flavours = listOf("Vanilla", "Chocolate", "Hazelnut", "Cookie", "Mango")
   // And sub total
   val subTotal = "$100"

   // When SelectOptionScreen is loaded
   composeTestRule.setContent {
       CupcakeTheme {
           SelectOptionScreen(subtotal = subTotal, options = flavours)
       }
   }

   // Then all the options are displayed on the screen.
   flavours.forEach { flavour ->
       composeTestRule.onNodeWithText(flavour).assertIsDisplayed()
   }
}
  1. 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.
@Test
fun selectOptionScreen_verifyContent() {
   // Given list of options
   val flavours = listOf("Vanilla", "Chocolate", "Hazelnut", "Cookie", "Mango")
   // And sub total
   val subTotal = "$100"

   // When SelectOptionScreen is loaded
   composeTestRule.setContent {
       CupcakeTheme {
           SelectOptionScreen(subtotal = subTotal, options = flavours)
       }
   }

   // Then all the options are displayed on the screen.
   flavours.forEach { flavour ->
       composeTestRule.onNodeWithText(flavour).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á.

  1. 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 flavours = listOf("Vanilla", "Chocolate", "Hazelnut", "Cookie", "Mango")
   // And sub total
   val subTotal = "$100"

   // When SelectOptionScreen is loaded
   composeTestRule.setContent {
       CupcakeTheme {
           SelectOptionScreen(subtotal = subTotal, options = flavours)
       }
   }

   // Then all the options are displayed on the screen.
   flavours.forEach { flavour ->
       composeTestRule.onNodeWithText(flavour).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. Mã giải pháp

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!