Kiến thức cơ bản về kiểm thử

Lớp học lập trình này nằm trong khoá học Kotlin nâng cao cho Android. Bạn sẽ nhận được nhiều giá trị nhất qua khoá học này nếu thực hiện các lớp học lập trình theo trình tự, nhưng đó không phải là yêu cầu bắt buộc. Tất cả các lớp học lập trình của khoá học đều được liệt kê trên trang đích của lớp học lập trình Kiến thức nâng cao về cách tạo ứng dụng Android bằng Kotlin.

Giới thiệu

Khi triển khai tính năng đầu tiên của ứng dụng đầu tiên, có thể bạn đã chạy mã để xác minh rằng mã đó hoạt động như mong đợi. Bạn đã thực hiện một thử nghiệm, mặc dù đó là một thử nghiệm thủ công. Khi tiếp tục thêm và cập nhật các tính năng, có lẽ bạn cũng tiếp tục chạy mã và xác minh rằng mã hoạt động. Nhưng việc thực hiện thủ công mỗi lần như vậy sẽ rất mệt mỏi, dễ mắc lỗi và không thể mở rộng quy mô.

Máy tính có khả năng mở rộng và tự động hoá tuyệt vời! Vì vậy, các nhà phát triển ở cả công ty lớn và nhỏ đều viết các bài kiểm thử tự động. Đây là những bài kiểm thử do phần mềm chạy và bạn không cần phải vận hành ứng dụng theo cách thủ công để xác minh mã hoạt động.

Trong loạt lớp học lập trình này, bạn sẽ tìm hiểu cách tạo một tập hợp các kiểm thử (còn gọi là bộ kiểm thử) cho một ứng dụng thực tế.

Lớp học lập trình đầu tiên này trình bày những kiến thức cơ bản về kiểm thử trên Android, bạn sẽ viết các bài kiểm thử đầu tiên và tìm hiểu cách kiểm thử LiveDataViewModel.

Kiến thức bạn cần có

Bạn cần thông thạo:

Kiến thức bạn sẽ học được

Bạn sẽ tìm hiểu về các chủ đề sau:

  • Cách viết và chạy bài kiểm thử đơn vị trên Android
  • Cách sử dụng phương pháp Phát triển dựa trên kiểm thử
  • Cách chọn kiểm thử đo lường và kiểm thử cục bộ

Bạn sẽ tìm hiểu về các thư viện và khái niệm mã sau đây:

Bạn sẽ thực hiện

  • Thiết lập, chạy và diễn giải cả kiểm thử cục bộ lẫn kiểm thử đo lường trong Android.
  • Viết các bài kiểm thử đơn vị trong Android bằng JUnit4 và Hamcrest.
  • Viết các kiểm thử LiveDataViewModel đơn giản.

Trong loạt lớp học lập trình này, bạn sẽ làm việc với ứng dụng TO-DO Notes. Ứng dụng này cho phép bạn viết ra các việc cần làm và hiển thị chúng trong một danh sách. Sau đó, bạn có thể đánh dấu là đã hoàn thành hoặc chưa hoàn thành, lọc hoặc xoá các mục đó.

Ứng dụng này được viết bằng Kotlin, có nhiều màn hình, sử dụng các thành phần Jetpack và tuân theo cấu trúc trong Hướng dẫn về cấu trúc ứng dụng. Khi tìm hiểu cách kiểm thử ứng dụng này, bạn sẽ có thể kiểm thử các ứng dụng sử dụng cùng một thư viện và cấu trúc.

Để bắt đầu, hãy tải mã xuống:

Tải tệp Zip xuống

Ngoài ra, bạn còn có thể sao chép kho lưu trữ GitHub cho mã:

$ git clone https://github.com/googlecodelabs/android-testing.git
$ cd android-testing
$ git checkout starter_code

Trong nhiệm vụ này, bạn sẽ chạy ứng dụng và khám phá cơ sở mã.

Bước 1: Chạy ứng dụng mẫu

Sau khi tải ứng dụng TO-DO xuống, hãy mở ứng dụng này trong Android Studio và chạy ứng dụng. Thao tác này sẽ biên dịch. Khám phá ứng dụng bằng cách làm như sau:

  • Tạo một việc cần làm mới bằng nút hành động nổi dấu cộng. Trước tiên, hãy nhập tiêu đề, sau đó nhập thông tin bổ sung về việc cần làm. Lưu bằng nút hành động nổi có dấu kiểm màu xanh lục.
  • Trong danh sách việc cần làm, hãy nhấp vào tiêu đề của việc cần làm mà bạn vừa hoàn thành và xem màn hình chi tiết của việc cần làm đó để xem phần còn lại của nội dung mô tả.
  • Trong danh sách hoặc trên màn hình chi tiết, hãy đánh dấu vào hộp đánh dấu của nhiệm vụ đó để đặt trạng thái thành Đã hoàn thành.
  • Quay lại màn hình công việc, mở trình đơn bộ lọc và lọc công việc theo trạng thái Đang hoạt độngĐã hoàn thành.
  • Mở ngăn điều hướng rồi nhấp vào Thống kê.
  • Quay lại màn hình tổng quan, rồi trong trình đơn ngăn điều hướng, hãy chọn Xoá việc đã hoàn thành để xoá tất cả việc cần làm có trạng thái Đã hoàn thành

Bước 2: Khám phá mã ứng dụng mẫu

Ứng dụng TO-DO dựa trên mẫu kiểm thử và cấu trúc Architecture Blueprints phổ biến (sử dụng phiên bản cấu trúc phản ứng của mẫu). Ứng dụng tuân theo cấu trúc trong Hướng dẫn về cấu trúc ứng dụng. Ứng dụng này sử dụng ViewModel với các Mảnh, một kho lưu trữ và Room. Nếu bạn đã quen thuộc với bất kỳ ví dụ nào dưới đây, thì ứng dụng này có cấu trúc tương tự:

Điều quan trọng hơn là bạn phải hiểu được cấu trúc chung của ứng dụng thay vì hiểu sâu về logic ở một lớp bất kỳ.

Sau đây là thông tin tóm tắt về các gói bạn sẽ thấy:

Gói: com.example.android.architecture.blueprints.todoapp

.addedittask

Màn hình thêm hoặc chỉnh sửa việc cần làm: Mã lớp giao diện người dùng để thêm hoặc chỉnh sửa việc cần làm.

.data

Lớp dữ liệu: Lớp này xử lý lớp dữ liệu của các tác vụ. Thư mục này chứa cơ sở dữ liệu, mạng và mã kho lưu trữ.

.statistics

Màn hình số liệu thống kê: Mã lớp giao diện người dùng cho màn hình số liệu thống kê.

.taskdetail

Màn hình thông tin về công việc: Mã lớp giao diện người dùng cho một công việc.

.tasks

Màn hình tác vụ: Mã lớp giao diện người dùng cho danh sách tất cả các tác vụ.

.util

Lớp tiện ích: Các lớp dùng chung được dùng trong nhiều phần của ứng dụng, chẳng hạn như bố cục làm mới bằng thao tác vuốt được dùng trên nhiều màn hình.

Lớp dữ liệu (.data)

Ứng dụng này có một lớp mạng mô phỏng trong gói remote và một lớp cơ sở dữ liệu trong gói local. Để đơn giản, trong dự án này, lớp mạng được mô phỏng chỉ bằng một HashMap có độ trễ, thay vì gửi các yêu cầu mạng thực.

DefaultTasksRepository điều phối hoặc làm trung gian giữa lớp mạng và lớp cơ sở dữ liệu, đồng thời là lớp trả về dữ liệu cho lớp giao diện người dùng.

Lớp giao diện người dùng ( .addedittask, .statistics, .taskdetail, .tasks)

Mỗi gói lớp giao diện người dùng đều chứa một mảnh và một mô hình khung hiển thị, cùng với mọi lớp khác cần thiết cho giao diện người dùng (chẳng hạn như một bộ chuyển đổi cho danh sách việc cần làm). TaskActivity là hoạt động chứa tất cả các mảnh.

Điều hướng

Hoạt động điều hướng cho ứng dụng do thành phần Điều hướng kiểm soát. Giá trị này được xác định trong tệp nav_graph.xml. Hoạt động điều hướng được kích hoạt trong các mô hình hiển thị bằng cách sử dụng lớp Event; các mô hình hiển thị cũng xác định những đối số cần truyền. Các mảnh này theo dõi Event và thực hiện thao tác điều hướng thực tế giữa các màn hình.

Trong nhiệm vụ này, bạn sẽ chạy các kiểm thử đầu tiên.

  1. Trong Android Studio, hãy mở ngăn Project (Dự án) rồi tìm 3 thư mục sau:
  • com.example.android.architecture.blueprints.todoapp
  • com.example.android.architecture.blueprints.todoapp (androidTest)
  • com.example.android.architecture.blueprints.todoapp (test)

Các thư mục này được gọi là tập hợp nguồn. Nhóm tài nguyên là các thư mục chứa mã nguồn cho ứng dụng của bạn. Nhóm tài nguyên có màu xanh lục (androidTesttest) chứa các bài kiểm thử của bạn. Khi tạo một dự án Android mới, theo mặc định, bạn sẽ nhận được 3 nhóm tài nguyên sau. Các loại chiến dịch phụ đó là:

  • main: Chứa mã ứng dụng của bạn. Mã này được chia sẻ giữa tất cả các phiên bản ứng dụng mà bạn có thể tạo (còn gọi là biến thể bản dựng)
  • androidTest: Chứa các chương trình kiểm thử được gọi là kiểm thử đo lường.
  • test: Chứa các chương trình kiểm thử còn được gọi là chương trình kiểm thử cục bộ.

Sự khác biệt giữa kiểm thử cục bộkiểm thử đo lường nằm ở cách chúng được chạy.

Kiểm thử cục bộ (nhóm tài nguyên test)

Các kiểm thử này chạy cục bộ trên JVM của máy phát triển và không yêu cầu trình mô phỏng hoặc thiết bị thực. Do đó, chúng chạy nhanh nhưng độ trung thực thấp hơn, tức là chúng hoạt động ít giống như trong thế giới thực.

Trong Android Studio, các bài kiểm thử cục bộ được biểu thị bằng biểu tượng hình tam giác màu xanh lục và màu đỏ.

Kiểm thử đo lường (nhóm tài nguyên androidTest)

Các kiểm thử này chạy trên các thiết bị Android thực hoặc được mô phỏng, vì vậy, chúng phản ánh những gì sẽ xảy ra trong thực tế, nhưng cũng chậm hơn nhiều.

Trong Android Studio, các kiểm thử đo lường được biểu thị bằng một biểu tượng Android có hình tam giác màu xanh lục và màu đỏ.

Bước 1: Chạy thử nghiệm cục bộ

  1. Mở thư mục test cho đến khi bạn tìm thấy tệp ExampleUnitTest.kt.
  2. Nhấp chuột phải vào đó rồi chọn Run ExampleUnitTest (Chạy ExampleUnitTest).

Bạn sẽ thấy kết quả sau trong cửa sổ Run (Chạy) ở cuối màn hình:

  1. Lưu ý các dấu kiểm màu xanh lục và mở rộng kết quả kiểm thử để xác nhận rằng một kiểm thử có tên addition_isCorrect đã đạt. Rất vui khi biết phép cộng hoạt động như mong đợi!

Bước 2: Khiến thử nghiệm thất bại

Dưới đây là bài kiểm thử mà bạn vừa chạy.

ExampleUnitTest.kt

// A test class is just a normal class
class ExampleUnitTest {

   // Each test is annotated with @Test (this is a Junit annotation)
   @Test
   fun addition_isCorrect() {
       // Here you are checking that 4 is the same as 2+2
       assertEquals(4, 2 + 2)
   }
}

Lưu ý rằng các kiểm thử

  • là một lớp trong một trong các nhóm tài nguyên kiểm thử.
  • chứa các hàm bắt đầu bằng chú giải @Test (mỗi hàm là một kiểm thử riêng lẻ).
  • thường chứa các câu khẳng định.

Android sử dụng thư viện kiểm thử JUnit để kiểm thử (trong lớp học lập trình này là JUnit4). Cả các câu lệnh khẳng định và chú thích @Test đều có trong JUnit.

Câu nhận định là cốt lõi của kiểm thử. Đây là một câu lệnh mã kiểm tra xem mã hoặc ứng dụng của bạn có hoạt động như mong đợi hay không. Trong trường hợp này, câu khẳng định là assertEquals(4, 2 + 2), câu này kiểm tra xem 4 có bằng 2 + 2 hay không.

Để xem một kiểm thử không thành công trông như thế nào, hãy thêm một câu nhận định mà bạn có thể dễ dàng thấy là sẽ không thành công. Thao tác này sẽ kiểm tra để đảm bảo rằng 3 bằng 1+1.

  1. Thêm assertEquals(3, 1 + 1) vào kiểm thử addition_isCorrect.

ExampleUnitTest.kt

class ExampleUnitTest {

   // Each test is annotated with @Test (this is a Junit annotation)
   @Test
   fun addition_isCorrect() {
       assertEquals(4, 2 + 2)
       assertEquals(3, 1 + 1) // This should fail
   }
}
  1. Chạy kiểm thử.
  1. Trong kết quả kiểm thử, bạn sẽ thấy một dấu X bên cạnh chương trình kiểm thử.

  1. Ngoài ra, hãy lưu ý:
  • Một câu nhận định không thành công sẽ khiến toàn bộ quy trình kiểm thử không thành công.
  • Bạn sẽ thấy giá trị dự kiến (3) so với giá trị thực tế được tính (2).
  • Bạn sẽ được chuyển đến dòng của câu lệnh xác nhận không thành công (ExampleUnitTest.kt:16).

Bước 3: Chạy một kiểm thử được đo lường

Các kiểm thử đo lường nằm trong nhóm tài nguyên androidTest.

  1. Mở nhóm tài nguyên androidTest.
  2. Chạy kiểm thử có tên là ExampleInstrumentedTest.

ExampleInstrumentedTest

@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
    @Test
    fun useAppContext() {
        // Context of the app under test.
        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
        assertEquals("com.example.android.architecture.blueprints.reactive",
            appContext.packageName)
    }
}

Không giống như kiểm thử cục bộ, kiểm thử này chạy trên một thiết bị (trong ví dụ dưới đây là một điện thoại Pixel 2 được mô phỏng):

Nếu có một thiết bị được đính kèm hoặc một trình mô phỏng đang chạy, bạn sẽ thấy chương trình kiểm thử chạy trên trình mô phỏng.

Trong nhiệm vụ này, bạn sẽ viết các kiểm thử cho getActiveAndCompleteStats. Lớp này sẽ tính toán tỷ lệ phần trăm số liệu thống kê về số lượng nhiệm vụ đang hoạt động và đã hoàn thành cho ứng dụng của bạn. Bạn có thể xem những con số này trên màn hình số liệu thống kê của ứng dụng.

Bước 1: Tạo một lớp kiểm thử

  1. Trong bộ nguồn main, trong todoapp.statistics, hãy mở StatisticsUtils.kt.
  2. Tìm hàm getActiveAndCompletedStats.

StatisticsUtils.kt

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

   val totalTasks = tasks!!.size
   val numberOfActiveTasks = tasks.count { it.isActive }
   val activePercent = 100 * numberOfActiveTasks / totalTasks
   val completePercent = 100 * (totalTasks - numberOfActiveTasks) / totalTasks

   return StatsResult(
       activeTasksPercent = activePercent.toFloat(),
       completedTasksPercent = completePercent.toFloat()
   )
  
}

data class StatsResult(val activeTasksPercent: Float, val completedTasksPercent: Float)

Hàm getActiveAndCompletedStats chấp nhận một danh sách các tác vụ và trả về một StatsResult. StatsResult là một lớp dữ liệu chứa 2 số, tỷ lệ phần trăm số nhiệm vụ đã hoàn thành và tỷ lệ phần trăm số nhiệm vụ đang hoạt động.

Android Studio cung cấp cho bạn các công cụ để tạo các chương trình kiểm thử giả giúp bạn triển khai các hoạt động kiểm thử cho hàm này.

  1. Nhấp chuột phải vào getActiveAndCompletedStats rồi chọn Generate (Tạo) > Test (Kiểm thử).

Hộp thoại Create Test (Tạo kiểm thử) sẽ mở ra:

  1. Thay đổi Tên lớp: thành StatisticsUtilsTest (thay vì StatisticsUtilsKtTest; sẽ tốt hơn nếu không có KT trong tên lớp kiểm thử).
  2. Giữ nguyên các chế độ cài đặt mặc định còn lại. JUnit 4 là thư viện kiểm thử phù hợp. Gói đích đến là chính xác (nó phản ánh vị trí của lớp StatisticsUtils) và bạn không cần đánh dấu vào bất kỳ hộp kiểm nào (thao tác này chỉ tạo thêm mã, nhưng bạn sẽ viết kiểm thử từ đầu).
  3. Nhấn OK

Hộp thoại Choose Destination Directory (Chọn thư mục đích) sẽ mở ra:

Bạn sẽ thực hiện một bài kiểm thử cục bộ vì hàm của bạn đang thực hiện các phép tính toán học và sẽ không bao gồm bất kỳ mã cụ thể nào của Android. Vì vậy, bạn không cần chạy bài kiểm thử này trên một thiết bị thực hoặc thiết bị mô phỏng.

  1. Chọn thư mục test (không phải androidTest) vì bạn sẽ viết các bài kiểm thử cục bộ.
  2. Nhấp vào OK.
  3. Lưu ý rằng lớp StatisticsUtilsTest đã được tạo trong test/statistics/.

Bước 2: Viết hàm kiểm thử đầu tiên

Bạn sẽ viết một kiểm thử để kiểm tra:

  • nếu không có nhiệm vụ nào đã hoàn thành và có một nhiệm vụ đang hoạt động,
  • tỷ lệ phần trăm các thử nghiệm đang hoạt động là 100%,
  • và tỷ lệ phần trăm số nhiệm vụ đã hoàn thành là 0%.
  1. Mở StatisticsUtilsTest.
  2. Tạo một hàm có tên là getActiveAndCompletedStats_noCompleted_returnsHundredZero.

StatisticsUtilsTest.kt

class StatisticsUtilsTest {

    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
        // Create an active task

        // Call your function

        // Check the result
    }
}
  1. Thêm chú giải @Test phía trên tên hàm để cho biết đó là một hàm kiểm thử.
  2. Tạo danh sách việc cần làm.
// Create an active task 
val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
  1. Gọi getActiveAndCompletedStats bằng các tác vụ này.
// Call your function
val result = getActiveAndCompletedStats(tasks)
  1. Kiểm tra để đảm bảo result là giá trị bạn mong đợi bằng cách sử dụng các câu khẳng định.
// Check the result
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)

Sau đây là mã hoàn chỉnh.

StatisticsUtilsTest.kt

class StatisticsUtilsTest {

    @Test
    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {

        // Create an active task (the false makes this active)
        val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
        // Call your function
        val result = getActiveAndCompletedStats(tasks)

        // Check the result
        assertEquals(result.completedTasksPercent, 0f)
        assertEquals(result.activeTasksPercent, 100f)
    }
}
  1. Chạy kiểm thử (Nhấp chuột phải vào StatisticsUtilsTest rồi chọn Chạy).

Nó sẽ vượt qua:

Bước 3: Thêm phần phụ thuộc Hamcrest

Vì các kiểm thử của bạn đóng vai trò là tài liệu về những gì mã của bạn thực hiện, nên sẽ rất tốt nếu các kiểm thử này dễ đọc. So sánh 2 khẳng định sau:

assertEquals(result.completedTasksPercent, 0f)

// versus

assertThat(result.completedTasksPercent, `is`(0f))

Câu khẳng định thứ hai giống với câu của con người hơn nhiều. Thư viện này được viết bằng một khung xác nhận có tên là Hamcrest. Một công cụ hữu ích khác để viết các câu khẳng định dễ đọc là Thư viện Truth. Bạn sẽ sử dụng Hamcrest trong lớp học lập trình này để viết các câu lệnh khẳng định.

  1. Mở build.grade (Module: app) rồi thêm phần phụ thuộc sau.

app/build.gradle

dependencies {
    // Other dependencies
    testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
}

Thông thường, bạn sử dụng implementation khi thêm một phần phụ thuộc, nhưng ở đây bạn đang sử dụng testImplementation. Khi đã sẵn sàng chia sẻ ứng dụng với mọi người, tốt nhất là bạn không nên làm tăng kích thước APK bằng bất kỳ mã kiểm thử hoặc phần phụ thuộc nào trong ứng dụng. Bạn có thể chỉ định xem một thư viện có nên được đưa vào mã chính hay mã kiểm thử bằng cách sử dụng cấu hình gradle. Các cấu hình phổ biến nhất là:

  • implementation – Phần phụ thuộc có trong tất cả các nhóm tài nguyên, bao gồm cả nhóm tài nguyên kiểm thử.
  • testImplementation – Phần phụ thuộc chỉ có trong nhóm nguồn kiểm thử.
  • androidTestImplementation – Phần phụ thuộc chỉ có trong nhóm tài nguyên androidTest.

Cấu hình bạn sử dụng sẽ xác định vị trí có thể dùng phần phụ thuộc. Nếu bạn viết:

testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"

Điều này có nghĩa là Hamcrest sẽ chỉ có trong nhóm tài nguyên kiểm thử. Việc này cũng đảm bảo rằng Hamcrest sẽ không có trong ứng dụng cuối cùng của bạn.

Bước 4: Sử dụng Hamcrest để viết các câu khẳng định

  1. Cập nhật kiểm thử getActiveAndCompletedStats_noCompleted_returnsHundredZero() để sử dụng assertThat của Hamcrest thay vì assertEquals.
// REPLACE
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)

// WITH
assertThat(result.activeTasksPercent, `is`(100f))
assertThat(result.completedTasksPercent, `is`(0f))

Xin lưu ý rằng bạn có thể sử dụng lệnh nhập import org.hamcrest.Matchers.`is` nếu được nhắc.

Bài kiểm thử cuối cùng sẽ có dạng như mã bên dưới.

StatisticsUtilsTest.kt

import com.example.android.architecture.blueprints.todoapp.data.Task
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is`
import org.junit.Test

class StatisticsUtilsTest {

    @Test
    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero {

        // Create an active tasks (the false makes this active)
        val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
        // Call your function
        val result = getActiveAndCompletedStats(tasks)

        // Check the result
        assertThat(result.activeTasksPercent, `is`(100f))
        assertThat(result.completedTasksPercent, `is`(0f))

    }
}
  1. Chạy kiểm thử đã cập nhật để xác nhận rằng kiểm thử vẫn hoạt động!

Lớp học lập trình này không hướng dẫn bạn mọi thứ về Hamcrest. Vì vậy, nếu muốn tìm hiểu thêm, hãy xem hướng dẫn chính thức.

Đây là một nhiệm vụ không bắt buộc để luyện tập.

Trong nhiệm vụ này, bạn sẽ viết thêm các bài kiểm thử bằng JUnit và Hamcrest. Bạn cũng sẽ viết các bài kiểm thử bằng cách sử dụng một chiến lược bắt nguồn từ phương pháp lập trình Phát triển dựa trên kiểm thử. Phát triển dựa trên kiểm thử (TDD) là một trường phái tư tưởng lập trình cho rằng thay vì viết mã tính năng trước, bạn nên viết các kiểm thử trước. Sau đó, bạn viết mã tính năng với mục tiêu vượt qua các kiểm thử.

Bước 1. Viết mã kiểm thử

Viết bài kiểm thử khi bạn có danh sách việc cần làm thông thường:

  1. Nếu có một việc cần làm đã hoàn tất và không có việc cần làm nào đang hoạt động, thì tỷ lệ phần trăm activeTasks phải là 0f và tỷ lệ phần trăm việc cần làm đã hoàn tất phải là 100f .
  2. Nếu có 2 việc cần làm đã hoàn thành và 3 việc cần làm đang hoạt động, thì tỷ lệ phần trăm hoàn thành sẽ là 40f và tỷ lệ phần trăm đang hoạt động sẽ là 60f.

Bước 2. Viết một chương trình kiểm thử cho lỗi

Mã cho getActiveAndCompletedStats như đã viết có một lỗi. Lưu ý cách nó không xử lý đúng những gì xảy ra nếu danh sách trống hoặc rỗng. Trong cả hai trường hợp này, cả hai tỷ lệ phần trăm đều phải bằng 0.

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

   val totalTasks = tasks!!.size
   val numberOfActiveTasks = tasks.count { it.isActive }
   val activePercent = 100 * numberOfActiveTasks / totalTasks
   val completePercent = 100 * (totalTasks - numberOfActiveTasks) / totalTasks

   return StatsResult(
       activeTasksPercent = activePercent.toFloat(),
       completedTasksPercent = completePercent.toFloat()
   )
  
}

Để sửa mã và viết các kiểm thử, bạn sẽ sử dụng phương pháp phát triển dựa trên kiểm thử. Phát triển dựa trên kiểm thử tuân theo các bước sau.

  1. Viết chương trình kiểm thử bằng cấu trúc Given, When, Then và có tên tuân theo quy ước.
  2. Xác nhận kiểm thử không thành công.
  3. Viết mã tối thiểu để kiểm thử thành công.
  4. Lặp lại cho tất cả các bài kiểm tra!

Thay vì bắt đầu bằng việc sửa lỗi, trước tiên, bạn sẽ bắt đầu bằng việc viết các kiểm thử. Sau đó, bạn có thể xác nhận rằng bạn có các kiểm thử giúp bạn không vô tình đưa những lỗi này vào lại trong tương lai.

  1. Nếu có danh sách trống (emptyList()), thì cả hai tỷ lệ phần trăm đều phải là 0f.
  2. Nếu xảy ra lỗi khi tải các việc cần làm, danh sách sẽ là null và cả hai tỷ lệ phần trăm đều phải là 0f.
  3. Chạy các kiểm thử và xác nhận rằng các kiểm thử đó không thành công:

Bước 3. Khắc phục lỗi

Giờ đây, khi đã có các kiểm thử, hãy sửa lỗi.

  1. Khắc phục lỗi trong getActiveAndCompletedStats bằng cách trả về 0f nếu tasksnull hoặc trống:
internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

    return if (tasks == null || tasks.isEmpty()) {
        StatsResult(0f, 0f)
    } else {
        val totalTasks = tasks.size
        val numberOfActiveTasks = tasks.count { it.isActive }
        StatsResult(
            activeTasksPercent = 100f * numberOfActiveTasks / tasks.size,
            completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size
        )
    }
}
  1. Chạy lại các bài kiểm thử và xác nhận rằng tất cả các bài kiểm thử hiện đã đạt!

Bằng cách tuân theo TDD và viết các kiểm thử trước, bạn đã giúp đảm bảo rằng:

  • Chức năng mới luôn có các kiểm thử liên kết; do đó, các kiểm thử của bạn đóng vai trò là tài liệu về những gì mã của bạn thực hiện.
  • Các kiểm thử của bạn sẽ kiểm tra kết quả chính xác và ngăn chặn các lỗi mà bạn đã gặp phải.

Giải pháp: Viết thêm các kiểm thử

Sau đây là tất cả các kiểm thử và mã tính năng tương ứng.

StatisticsUtilsTest.kt

class StatisticsUtilsTest {

    @Test
    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero {
        val tasks = listOf(
            Task("title", "desc", isCompleted = false)
        )
        // When the list of tasks is computed with an active task
        val result = getActiveAndCompletedStats(tasks)

        // Then the percentages are 100 and 0
        assertThat(result.activeTasksPercent, `is`(100f))
        assertThat(result.completedTasksPercent, `is`(0f))
    }

    @Test
    fun getActiveAndCompletedStats_noActive_returnsZeroHundred() {
        val tasks = listOf(
            Task("title", "desc", isCompleted = true)
        )
        // When the list of tasks is computed with a completed task
        val result = getActiveAndCompletedStats(tasks)

        // Then the percentages are 0 and 100
        assertThat(result.activeTasksPercent, `is`(0f))
        assertThat(result.completedTasksPercent, `is`(100f))
    }

    @Test
    fun getActiveAndCompletedStats_both_returnsFortySixty() {
        // Given 3 completed tasks and 2 active tasks
        val tasks = listOf(
            Task("title", "desc", isCompleted = true),
            Task("title", "desc", isCompleted = true),
            Task("title", "desc", isCompleted = true),
            Task("title", "desc", isCompleted = false),
            Task("title", "desc", isCompleted = false)
        )
        // When the list of tasks is computed
        val result = getActiveAndCompletedStats(tasks)

        // Then the result is 40-60
        assertThat(result.activeTasksPercent, `is`(40f))
        assertThat(result.completedTasksPercent, `is`(60f))
    }

    @Test
    fun getActiveAndCompletedStats_error_returnsZeros() {
        // When there's an error loading stats
        val result = getActiveAndCompletedStats(null)

        // Both active and completed tasks are 0
        assertThat(result.activeTasksPercent, `is`(0f))
        assertThat(result.completedTasksPercent, `is`(0f))
    }

    @Test
    fun getActiveAndCompletedStats_empty_returnsZeros() {
        // When there are no tasks
        val result = getActiveAndCompletedStats(emptyList())

        // Both active and completed tasks are 0
        assertThat(result.activeTasksPercent, `is`(0f))
        assertThat(result.completedTasksPercent, `is`(0f))
    }
}

StatisticsUtils.kt

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

    return if (tasks == null || tasks.isEmpty()) {
        StatsResult(0f, 0f)
    } else {
        val totalTasks = tasks.size
        val numberOfActiveTasks = tasks.count { it.isActive }
        StatsResult(
            activeTasksPercent = 100f * numberOfActiveTasks / tasks.size,
            completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size
        )
    }
}

Chúc mừng bạn đã hoàn thành kiến thức cơ bản về cách viết và chạy kiểm thử! Tiếp theo, bạn sẽ tìm hiểu cách viết các kiểm thử cơ bản ViewModelLiveData.

Trong phần còn lại của lớp học lập trình này, bạn sẽ tìm hiểu cách viết các bài kiểm thử cho 2 lớp Android thường gặp trong hầu hết các ứng dụng – ViewModelLiveData.

Bạn bắt đầu bằng cách viết các kiểm thử cho TasksViewModel.


Bạn sẽ tập trung vào các kiểm thử có toàn bộ logic trong mô hình chế độ xem và không dựa vào mã kho lưu trữ. Mã kho lưu trữ liên quan đến mã không đồng bộ, cơ sở dữ liệu và lệnh gọi mạng, tất cả đều làm tăng độ phức tạp của kiểm thử. Hiện tại, bạn sẽ tránh điều đó và tập trung vào việc viết các bài kiểm thử cho chức năng ViewModel mà không kiểm thử trực tiếp bất kỳ điều gì trong kho lưu trữ.



Bài kiểm thử mà bạn sẽ viết sẽ kiểm tra xem khi bạn gọi phương thức addNewTask, Event để mở cửa sổ tác vụ mới có được kích hoạt hay không. Đây là mã ứng dụng mà bạn sẽ kiểm thử.

TasksViewModel.kt

fun addNewTask() {
   _newTaskEvent.value = Event(Unit)
}

Bước 1. Tạo một lớp TasksViewModelTest

Làm theo các bước tương tự như đối với StatisticsUtilTest, trong bước này, bạn sẽ tạo một tệp kiểm thử cho TasksViewModelTest.

  1. Mở lớp bạn muốn kiểm thử trong gói tasks, TasksViewModel.
  2. Trong mã, hãy nhấp chuột phải vào tên lớp TasksViewModel -> Generate (Tạo) -> Test (Kiểm thử).

  1. Trên màn hình Tạo bài kiểm tra, hãy nhấp vào OK để chấp nhận (không cần thay đổi bất kỳ chế độ cài đặt mặc định nào).
  2. Trên hộp thoại Choose Destination Directory (Chọn thư mục đích), hãy chọn thư mục test.

Bước 2. Bắt đầu viết bài kiểm thử ViewModel

Trong bước này, bạn sẽ thêm một kiểm thử mô hình hiển thị để kiểm thử rằng khi bạn gọi phương thức addNewTask, Event để mở cửa sổ tác vụ mới sẽ được kích hoạt.

  1. Tạo một chương trình kiểm thử mới có tên là addNewTask_setsNewTaskEvent.

TasksViewModelTest.kt

class TasksViewModelTest {

    @Test
    fun addNewTask_setsNewTaskEvent() {

        // Given a fresh TasksViewModel


        // When adding a new task


        // Then the new task event is triggered

    }
    
}

Vậy còn ngữ cảnh ứng dụng thì sao?

Khi bạn tạo một thực thể TasksViewModel để kiểm thử, hàm khởi tạo của thực thể này sẽ yêu cầu một Application Context (Ngữ cảnh ứng dụng). Nhưng trong bài kiểm thử này, bạn không tạo một ứng dụng đầy đủ với các hoạt động, giao diện người dùng và mảnh. Vậy làm cách nào để bạn có được một ngữ cảnh ứng dụng?

TasksViewModelTest.kt

// Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(???)

Thư viện AndroidX Test bao gồm các lớp và phương thức cung cấp cho bạn các phiên bản của những thành phần như Ứng dụng và Hoạt động dành cho các bài kiểm thử. Khi bạn có một chương trình kiểm thử cục bộ mà bạn cần các lớp khung ứng dụng Android mô phỏng(chẳng hạn như Application Context), hãy làm theo các bước sau để thiết lập AndroidX Test đúng cách:

  1. Thêm các phần phụ thuộc cốt lõi và phần phụ thuộc mở rộng của AndroidX Test
  2. Thêm phần phụ thuộc Thư viện kiểm thử Robolectric
  3. Chú giải lớp bằng trình chạy kiểm thử AndroidJunit4
  4. Viết mã kiểm thử AndroidX

Bạn sẽ hoàn tất các bước này và sau đó hiểu được cách các bước này phối hợp với nhau.

Bước 3. Thêm các phần phụ thuộc vào gradle

  1. Sao chép các phần phụ thuộc này vào tệp build.gradle của mô-đun ứng dụng để thêm các phần phụ thuộc cốt lõi và phần phụ thuộc mở rộng của AndroidX Test, cũng như phần phụ thuộc kiểm thử Robolectric.

app/build.gradle

    // AndroidX Test - JVM testing
testImplementation "androidx.test.ext:junit-ktx:$androidXTestExtKotlinRunnerVersion"

    testImplementation "androidx.test:core-ktx:$androidXTestCoreVersion"

 testImplementation "org.robolectric:robolectric:$robolectricVersion"

Bước 4. Thêm JUnit Test Runner

  1. Thêm @RunWith(AndroidJUnit4::class) phía trên lớp kiểm thử.

TasksViewModelTest.kt

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
    // Test code
}

Bước 5. Sử dụng AndroidX Test

Tại thời điểm này, bạn có thể sử dụng Thư viện kiểm thử AndroidX. Trong đó có phương thức ApplicationProvider.getApplicationContext, phương thức này sẽ nhận được một Ngữ cảnh ứng dụng.

  1. Tạo một TasksViewModel bằng cách sử dụng ApplicationProvider.getApplicationContext() trong thư viện kiểm thử AndroidX.

TasksViewModelTest.kt

// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
  1. Gọi cho addNewTask theo số tasksViewModel.

TasksViewModelTest.kt

tasksViewModel.addNewTask()

Lúc này, kiểm thử của bạn sẽ có dạng như đoạn mã dưới đây.

TasksViewModelTest.kt

    @Test
    fun addNewTask_setsNewTaskEvent() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        // TODO test LiveData
    }
  1. Chạy kiểm thử để xác nhận rằng kiểm thử hoạt động.

Khái niệm: AndroidX Test hoạt động như thế nào?

AndroidX Test là gì?

AndroidX Test là một tập hợp các thư viện để kiểm thử. Nó bao gồm các lớp và phương thức cung cấp cho bạn các phiên bản của những thành phần như Ứng dụng và Hoạt động, dành cho các bài kiểm thử. Ví dụ: mã bạn đã viết là một ví dụ về hàm Kiểm thử AndroidX để lấy ngữ cảnh ứng dụng.

ApplicationProvider.getApplicationContext()

Một trong những lợi ích của API kiểm thử AndroidX là chúng được xây dựng để hoạt động cho cả kiểm thử cục bộ kiểm thử đo lường. Điều này rất hữu ích vì:

  • Bạn có thể chạy cùng một kiểm thử dưới dạng kiểm thử cục bộ hoặc kiểm thử đo lường.
  • Bạn không cần tìm hiểu các API kiểm thử khác nhau cho kiểm thử cục bộ so với kiểm thử đo lường.

Ví dụ: vì bạn đã viết mã bằng thư viện Kiểm thử AndroidX, nên bạn có thể di chuyển lớp TasksViewModelTest từ thư mục test sang thư mục androidTest và các kiểm thử vẫn sẽ chạy. getApplicationContext() hoạt động hơi khác một chút, tuỳ thuộc vào việc bạn chạy kiểm thử này dưới dạng kiểm thử cục bộ hay kiểm thử đo lường:

  • Nếu là một bài kiểm thử đo lường, thì bài kiểm thử đó sẽ nhận được ngữ cảnh Ứng dụng thực tế được cung cấp khi khởi động một trình mô phỏng hoặc kết nối với một thiết bị thực.
  • Nếu là kiểm thử cục bộ, thì kiểm thử này sẽ sử dụng một môi trường Android mô phỏng.

Robolectric là gì?

Môi trường Android mô phỏng mà AndroidX Test dùng cho các kiểm thử cục bộ được cung cấp bởi Robolectric. Robolectric là một thư viện tạo môi trường Android mô phỏng cho các chương trình kiểm thử và chạy nhanh hơn so với việc khởi động trình mô phỏng hoặc chạy trên thiết bị. Nếu không có phần phụ thuộc Robolectric, bạn sẽ gặp lỗi này:

Biểu tượng @RunWith(AndroidJUnit4::class) có chức năng gì?

Trình chạy kiểm thử là một thành phần JUnit chạy các kiểm thử. Nếu không có trình chạy kiểm thử, các kiểm thử của bạn sẽ không chạy. Có một trình chạy kiểm thử mặc định do JUnit cung cấp mà bạn sẽ tự động nhận được. @RunWith sẽ thay thế trình chạy kiểm thử mặc định đó.

Trình chạy kiểm thử AndroidJUnit4 cho phép Kiểm thử AndroidX chạy kiểm thử theo cách khác nhau, tuỳ thuộc vào việc đó là kiểm thử đo lường hay kiểm thử cục bộ.

Bước 6. Khắc phục cảnh báo Robolectric

Khi bạn chạy mã, hãy lưu ý rằng Robolectric được dùng.

Nhờ AndroidX Test và trình chạy kiểm thử AndroidJunit4, việc này được thực hiện mà bạn không cần trực tiếp viết một dòng mã Robolectric nào!

Bạn có thể nhận thấy 2 cảnh báo.

  • No such manifest file: ./AndroidManifest.xml
  • "WARN: Android SDK 29 requires Java 9..."

Bạn có thể khắc phục cảnh báo No such manifest file: ./AndroidManifest.xml bằng cách cập nhật tệp gradle.

  1. Thêm dòng sau vào tệp gradle để sử dụng tệp kê khai Android chính xác. Tuỳ chọn includeAndroidResources cho phép bạn truy cập vào các tài nguyên Android trong kiểm thử đơn vị, bao gồm cả tệp AndroidManifest.

app/build.gradle

    // Always show the result of every unit test when running via command line, even if it passes.
    testOptions.unitTests {
        includeAndroidResources = true

        // ... 
    }

Cảnh báo "WARN: Android SDK 29 requires Java 9..." phức tạp hơn. Để chạy kiểm thử trên Android Q, bạn cần có Java 9. Thay vì cố gắng định cấu hình Android Studio để sử dụng Java 9, trong lớp học lập trình này, hãy giữ SDK mục tiêu và SDK biên dịch ở mức 28.

Tóm lại:

  • Các kiểm thử mô hình hiển thị thuần tuý thường có thể nằm trong nhóm tài nguyên test vì mã của các kiểm thử này thường không yêu cầu Android.
  • Bạn có thể sử dụng thư viện kiểm thửAndroidX để nhận các phiên bản kiểm thử của các thành phần như Ứng dụng và Hoạt động.
  • Nếu cần chạy mã Android mô phỏng trong tập hợp nguồn test, bạn có thể thêm phần phụ thuộc Robolectric và chú thích @RunWith(AndroidJUnit4::class).

Xin chúc mừng! Bạn đang sử dụng cả thư viện kiểm thử AndroidX và Robolectric để chạy một kiểm thử. Bài kiểm thử của bạn chưa hoàn thành (bạn chưa viết câu lệnh xác nhận nào, chỉ có // TODO test LiveData). Tiếp theo, bạn sẽ học cách viết câu lệnh xác nhận bằng LiveData.

Trong nhiệm vụ này, bạn sẽ tìm hiểu cách xác nhận chính xác giá trị LiveData.

Đây là nơi bạn bỏ dở mà không có kiểm thử mô hình xem addNewTask_setsNewTaskEvent.

TasksViewModelTest.kt

    @Test
    fun addNewTask_setsNewTaskEvent() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        // TODO test LiveData
    }
    

Để kiểm thử LiveData, bạn nên làm hai việc:

  1. Sử dụng InstantTaskExecutorRule
  2. Đảm bảo LiveData quan sát

Bước 1. Sử dụng InstantTaskExecutorRule

InstantTaskExecutorRule là một Quy tắc JUnit. Khi bạn sử dụng chú thích @get:Rule, chú thích này sẽ khiến một số mã trong lớp InstantTaskExecutorRule được chạy trước và sau các kiểm thử (để xem mã chính xác, bạn có thể dùng phím tắt Command+B để xem tệp).

Quy tắc này chạy tất cả các tác vụ ở chế độ nền liên quan đến Thành phần cấu trúc trong cùng một luồng để kết quả kiểm thử diễn ra đồng bộ và theo một thứ tự có thể lặp lại. Khi bạn viết các kiểm thử bao gồm kiểm thử LiveData, hãy sử dụng quy tắc này!

  1. Thêm phần phụ thuộc gradle cho Thư viện kiểm thử cốt lõi của Thành phần cấu trúc (chứa quy tắc này).

app/build.gradle

testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
  1. Mở TasksViewModelTest.kt
  2. Thêm InstantTaskExecutorRule vào lớp TasksViewModelTest.

TasksViewModelTest.kt

class TasksViewModelTest {
    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()
    
    // Other code...
}

Bước 2. Thêm lớp LiveDataTestUtil.kt

Bước tiếp theo là đảm bảo LiveData mà bạn đang kiểm thử được quan sát.

Khi sử dụng LiveData, bạn thường có một hoạt động hoặc mảnh (LifecycleOwner) quan sát LiveData.

viewModel.resultLiveData.observe(fragment, Observer {
    // Observer code here
})

Quan sát này rất quan trọng. Bạn cần có người quan sát đang hoạt động trên LiveData để

  • kích hoạt mọi sự kiện onChanged.
  • kích hoạt mọi Biến đổi.

Để có được hành vi LiveData như mong đợi cho LiveData của mô hình hiển thị, bạn cần quan sát LiveData bằng LifecycleOwner.

Điều này gây ra một vấn đề: trong kiểm thử TasksViewModel, bạn không có hoạt động hoặc mảnh nào để quan sát LiveData. Để khắc phục vấn đề này, bạn có thể sử dụng phương thức observeForever. Phương thức này đảm bảo LiveData luôn được quan sát mà không cần LifecycleOwner. Khi observeForever, bạn cần nhớ xoá bộ quan sát nếu không sẽ có nguy cơ bị rò rỉ bộ quan sát.

Thao tác này sẽ có dạng như mã dưới đây. Kiểm tra:

@Test
fun addNewTask_setsNewTaskEvent() {

    // Given a fresh ViewModel
    val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())


    // Create observer - no need for it to do anything!
    val observer = Observer<Event<Unit>> {}
    try {

        // Observe the LiveData forever
        tasksViewModel.newTaskEvent.observeForever(observer)

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.value
        assertThat(value?.getContentIfNotHandled(), (not(nullValue())))

    } finally {
        // Whatever happens, don't forget to remove the observer!
        tasksViewModel.newTaskEvent.removeObserver(observer)
    }
}

Có rất nhiều mã nguyên mẫu để quan sát một LiveData duy nhất trong một bài kiểm thử! Có một số cách để loại bỏ đoạn mã này. Bạn sẽ tạo một hàm tiện ích có tên là LiveDataTestUtil để đơn giản hoá việc thêm các đối tượng theo dõi.

  1. Tạo một tệp Kotlin mới có tên LiveDataTestUtil.kt trong nhóm tài nguyên test.


  1. Sao chép và dán mã bên dưới.

LiveDataTestUtil.kt

import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException


@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun <T> LiveData<T>.getOrAwaitValue(
    time: Long = 2,
    timeUnit: TimeUnit = TimeUnit.SECONDS,
    afterObserve: () -> Unit = {}
): T {
    var data: T? = null
    val latch = CountDownLatch(1)
    val observer = object : Observer<T> {
        override fun onChanged(o: T?) {
            data = o
            latch.countDown()
            this@getOrAwaitValue.removeObserver(this)
        }
    }
    this.observeForever(observer)

    try {
        afterObserve.invoke()

        // Don't wait indefinitely if the LiveData is not set.
        if (!latch.await(time, timeUnit)) {
            throw TimeoutException("LiveData value was never set.")
        }

    } finally {
        this.removeObserver(observer)
    }

    @Suppress("UNCHECKED_CAST")
    return data as T
}

Đây là một phương pháp khá phức tạp. Thao tác này sẽ tạo một hàm tiện ích Kotlin có tên là getOrAwaitValue, hàm này sẽ thêm một đối tượng theo dõi, lấy giá trị LiveData rồi dọn dẹp đối tượng theo dõi. Về cơ bản, đây là một phiên bản ngắn, có thể dùng lại của mã observeForever nêu trên. Để biết nội dung giải thích đầy đủ về lớp này, hãy xem bài đăng này trên blog.

Bước 3. Sử dụng getOrAwaitValue để viết câu khẳng định

Ở bước này, bạn sẽ dùng phương thức getOrAwaitValue và viết một câu lệnh xác nhận để kiểm tra xem newTaskEvent có được kích hoạt hay không.

  1. Nhận giá trị LiveData cho newTaskEvent bằng cách sử dụng getOrAwaitValue.
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
  1. Xác nhận rằng giá trị không phải là giá trị rỗng.
assertThat(value.getContentIfNotHandled(), (not(nullValue())))

Chương trình kiểm thử hoàn chỉnh sẽ có dạng như mã bên dưới.

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.example.android.architecture.blueprints.todoapp.getOrAwaitValue
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.not
import org.hamcrest.Matchers.nullValue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {

    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()


    @Test
    fun addNewTask_setsNewTaskEvent() {
        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.getOrAwaitValue()

        assertThat(value.getContentIfNotHandled(), not(nullValue()))


    }

}
  1. Chạy mã và xem kết quả kiểm thử thành công!

Bây giờ bạn đã biết cách viết một chương trình kiểm thử, hãy tự viết một chương trình. Trong bước này, hãy sử dụng những kỹ năng bạn đã học được để thực hành viết một bài kiểm thử TasksViewModel khác.

Bước 1. Viết bài kiểm thử ViewModel của riêng bạn

Bạn sẽ viết setFilterAllTasks_tasksAddViewVisible(). Thử nghiệm này sẽ kiểm tra xem nếu bạn đã đặt loại bộ lọc để hiện tất cả các việc cần làm, thì nút Thêm việc cần làm có xuất hiện hay không.

  1. Sử dụng addNewTask_setsNewTaskEvent() để tham chiếu, hãy viết một kiểm thử trong TasksViewModelTest có tên là setFilterAllTasks_tasksAddViewVisible(), kiểm thử này sẽ đặt chế độ lọc thành ALL_TASKS và xác nhận rằng tasksAddViewVisible LiveData là true.


Sử dụng mã dưới đây để bắt đầu.

TasksViewModelTest

    @Test
    fun setFilterAllTasks_tasksAddViewVisible() {

        // Given a fresh ViewModel

        // When the filter type is ALL_TASKS

        // Then the "Add task" action is visible
        
    }

Lưu ý:

  • Enum TasksFilterType cho tất cả các nhiệm vụ là ALL_TASKS.
  • Chế độ hiển thị của nút thêm việc cần làm được kiểm soát bằng LiveData tasksAddViewVisible.
  1. Chạy kiểm thử.

Bước 2. So sánh bài kiểm tra của bạn với giải pháp

So sánh giải pháp của bạn với giải pháp bên dưới.

TasksViewModelTest

    @Test
    fun setFilterAllTasks_tasksAddViewVisible() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When the filter type is ALL_TASKS
        tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)

        // Then the "Add task" action is visible
        assertThat(tasksViewModel.tasksAddViewVisible.getOrAwaitValue(), `is`(true))
    }

Kiểm tra xem bạn có làm những việc sau đây hay không:

  • Bạn tạo tasksViewModel bằng chính câu lệnh ApplicationProvider.getApplicationContext() AndroidX.
  • Bạn gọi phương thức setFiltering, truyền vào enum loại bộ lọc ALL_TASKS.
  • Bạn kiểm tra xem tasksAddViewVisible có đúng hay không bằng cách sử dụng phương thức getOrAwaitNextValue.

Bước 3. Thêm quy tắc @Before

Lưu ý cách bạn xác định một TasksViewModel khi bắt đầu cả hai kiểm thử.

TasksViewModelTest

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

Khi có mã thiết lập lặp lại cho nhiều kiểm thử, bạn có thể sử dụng chú thích @Before để tạo một phương thức thiết lập và xoá mã lặp lại. Vì tất cả các kiểm thử này sẽ kiểm thử TasksViewModel và cần có một mô hình hiển thị, hãy di chuyển mã này đến một khối @Before.

  1. Tạo một biến thực thể lateinit có tên là tasksViewModel|.
  2. Tạo một phương thức có tên là setupViewModel.
  3. Chú giải hàm này bằng @Before.
  4. Di chuyển mã khởi tạo mô hình chế độ xem sang setupViewModel.

TasksViewModelTest

    // Subject under test
    private lateinit var tasksViewModel: TasksViewModel

    @Before
    fun setupViewModel() {
        tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
    }
  1. Chạy mã!

Cảnh báo

Không làm những việc sau, không khởi chạy

tasksViewModel

cùng với định nghĩa của nó:

val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

Điều này sẽ khiến cùng một thực thể được dùng cho tất cả các kiểm thử. Đây là điều bạn nên tránh vì mỗi kiểm thử phải có một phiên bản mới của đối tượng được kiểm thử (ViewModel trong trường hợp này).

Mã cuối cùng cho TasksViewModelTest sẽ có dạng như mã bên dưới.

TasksViewModelTest

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {

    // Subject under test
    private lateinit var tasksViewModel: TasksViewModel

    // Executes each task synchronously using Architecture Components.
    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()

    @Before
    fun setupViewModel() {
        tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
    }


    @Test
    fun addNewTask_setsNewTaskEvent() {

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.awaitNextValue()
        assertThat(
            value?.getContentIfNotHandled(), (not(nullValue()))
        )
    }

    @Test
    fun getTasksAddViewVisible() {

        // When the filter type is ALL_TASKS
        tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)

        // Then the "Add task" action is visible
        assertThat(tasksViewModel.tasksAddViewVisible.awaitNextValue(), `is`(true))
    }
    
}

Nhấp vào đây để xem sự khác biệt giữa mã bạn bắt đầu và mã cuối cùng.

Để tải mã xuống khi lớp học lập trình đã kết thúc, bạn có thể sử dụng lệnh git bên dưới:

$ git clone https://github.com/googlecodelabs/android-testing.git
$ cd android-testing
$ git checkout end_codelab_1


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ở tệp đó trong Android Studio.

Tải tệp Zip xuống

Lớp học lập trình này đề cập đến:

  • Cách chạy các bài kiểm thử trong Android Studio.
  • Sự khác biệt giữa kiểm thử cục bộ (test) và kiểm thử đo lường (androidTest).
  • Cách viết các bài kiểm thử đơn vị cục bộ bằng JUnitHamcrest.
  • Thiết lập các quy trình kiểm thử ViewModel bằng Thư viện kiểm thử AndroidX.

Khoá học của Udacity:

Tài liệu dành cho nhà phát triển Android:

Video:

Khác:

Để biết đường liên kết đến các lớp học lập trình khác trong khoá học này, hãy xem trang đích của các lớp học lập trình trong khoá học Kiến thức nâng cao về cách tạo ứng dụng Android bằng Kotlin.