Khái niệm cơ bản về thử nghiệm

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

Giới thiệu

Khi triển khai tính năng đầu tiên của ứng dụng đầu tiên, bạn có thể chạy mã để xác minh rằng ứng dụng hoạt động như mong đợi. Bạn đã kiểm tra, mặc dù thử nghiệm thủ công. Khi tiếp tục thêm và cập nhật các tính năng, bạn có thể vẫn tiếp tục chạy mã và xác minh mã hoạt động. Tuy nhiên, làm theo cách thủ công mỗi lần sẽ nhàm chán, dễ xảy ra sai sót và không mở rộng quy mô.

Máy tính là một công cụ hiệu quả để mở rộng quy mô và tự động hóa! Do đó, các nhà phát triển tại các công ty lớn và nhỏ viết thử nghiệm tự động, là các thử nghiệm do phần mềm chạy và không yêu cầu bạn phải vận hành ứng dụng theo cách thủ công để xác minh mã hoạt động.

Nội dung bạn sẽ tìm hiểu trong chuỗi lớp học lập trình này là cách tạo tập hợp các thử nghiệm (được gọi là bộ thử nghiệm) cho ứng dụng thực tế.

Lớp học lập trình đầu tiên này cung cấp thông tin cơ bản về cách thử nghiệm trên Android. Bạn có thể viết các thử nghiệm đầu tiên và tìm hiểu cách thử nghiệm 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 thử nghiệm đơn vị trên Android
  • Cách sử dụng tính năng Phát triển theo hướng thử nghiệm
  • Cách chọn thử nghiệm đo lường và thử nghiệm tại địa phương

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

Bạn sẽ thực hiện

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

Trong chuỗi 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 công việc để hoàn thành và hiển thị chúng trong danh sách. Sau đó, bạn có thể đánh dấu lời nhắc là đã hoàn thành, hay không, lọc hoặc xóa lời nhắ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 từ Hướng dẫn về cấu trúc ứng dụng. Bằng cách tìm hiểu cách thử nghiệm ứng dụng này, bạn có thể thử nghiệm các ứng dụng sử dụng cùng 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ó 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 bạn 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. Nên biên dịch. Khám phá ứng dụng bằng cách làm như sau:

  • Tạo một việc mới cần làm bằng nút dấu cộng thao tác nổi. Nhập tiêu đề trước, sau đó nhập thêm thông tin về việc cần làm. Lưu mật khẩu bằng kiểm tra FAB 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 bạn vừa hoàn thành, rồi xem màn hình chi tiết về việc cần làm đó để xem phần mô tả còn lại.
  • Trong danh sách hoặc trên màn hình chi tiết, hãy đánh dấu vào hộp kiểm của việc cần làm đó để đặt trạng thái cho Việc cần làm.
  • Quay lại màn hình việc cần làm, mở trình đơn bộ lọc rồi lọc việc cần làm theo trạng thái Đang hoạt độngĐã hoàn thành.
  • Mở ngăn điều hướng và nhấp vào Thống kê.
  • Quay lại màn hình tổng quan và từ trình đơn ngăn điều hướng, hãy chọn Xóa đã hoàn tất để xóa 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 được xây dựng dựa trên mẫu kiến trúc và thử nghiệm Bản vẽ kiến trúc phổ biến (sử dụng phiên bản cấu trúc phản hồi của mẫu). Ứng dụng này tuân theo cấu trúc từ Hướng dẫn về cấu trúc ứng dụng. Chế độ xem này sử dụng ViewModel với các Mảnh, kho lưu trữ và Phòng. Nếu bạn quen thuộc với bất kỳ ví dụ nào dưới đây, ứng dụng này có cấu trúc tương tự:

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

Dưới đây là phần tóm tắt các gói bạn sẽ tìm thấy:

Gói hàng: com.example.android.architecture.blueprints.todoapp

.addedittask

Thêm hoặc chỉnh sửa màn hình 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ẻ này chứa cơ sở dữ liệu, mạng và mã kho lưu trữ.

.statistics

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

.taskdetail

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

.tasks

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

.util

Lớp tiện ích: Lớp học dùng chung được dùng trong nhiều phần của ứng dụng, ví dụ: cho bố cục làm mới khi vuốt được sử dụng trên nhiều màn hình.

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

Ứng dụng này bao gồm 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 hóa, trong dự án này, lớp mạng được mô phỏng chỉ bằng HashMap có độ trễ, thay vì thực hiện các yêu cầu mạng thực.

DefaultTasksRepository tọa độ hoặc dàn xếp giữa lớp mạng và lớp cơ sở dữ liệu, đồng thời là dữ liệu 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, .stats, .taskdetail, .tasks)

Mỗi gói lớp giao diện người dùng chứa một mảnh và mô hình chế độ xem, cùng với bất kỳ lớp nào khác cần thiết cho giao diện người dùng (chẳng hạn như bộ chuyển đổi cho danh sách tác vụ). 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. Tên này được xác định trong tệp nav_graph.xml. Trình kích hoạt được kích hoạt trong các mô hình chế độ xem bằng cách sử dụng lớp Event; các mô hình chế độ xem cũng xác định các đối số cần chuyển. Các mảnh quan sát Event và thực hiện việc di chuyển thực tế giữa các màn hình.

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

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

Những thư mục này được gọi là nhóm nguồn. Tập hợp nguồn là các thư mục chứa mã nguồn cho ứng dụng của bạn. Các nhóm nguồn có màu xanh lục (androidTesttest) chứa các thử nghiệm của bạn. Theo mặc định, khi tạo một dự án Android mới, bạn sẽ nhận được 3 nhóm nguồn sau đây. Các yếu tố này là:

  • main: Chứa mã ứng dụng. Mã này được chia sẻ giữa tất cả các phiên bản khác nhau của ứng dụng mà bạn có thể xây dựng (được gọi là biến thể bản dựng)
  • androidTest: Chứa các thử nghiệm được gọi là thử nghiệm đo lường.
  • test: Chứa các thử nghiệm được gọi là thử nghiệm tại địa phương.

Sự khác biệt giữa thử nghiệm cục bộthử nghiệm theo công cụ là cách chạy thử nghiệm.

Thử nghiệm cục bộ (test nhóm nguồn)

Những thử nghiệm này được 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 độ chân thực sẽ thấp hơn, có nghĩa là chúng hoạt động ít hơn so với trong thế giới thực.

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

Thử nghiệm đo lường (androidTest nhóm nguồn)

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

Trong các thử nghiệm đo lường trên Android Studio, biểu tượng Android được biểu thị bằng biểu tượng 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 thấy tệp ExampleUnitTest.kt.
  2. Nhấp chuột phải vào tệp và chọn Chạy ExampleUnitTest.

Bạn sẽ thấy kết quả sau trong cửa sổ 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ả thử nghiệm để xác nhận rằng một thử nghiệm có tên là addition_isCorrect đã vượt qua. Thật tuyệt khi biết rằng tính năng bổ sung hoạt động như mong đợi!

Bước 2: Không chạy thử nghiệm

Dưới đây là thử nghiệm 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 thử nghiệm

  • là một lớp ở một trong các nhóm nguồn thử nghiệm.
  • chứa các hàm bắt đầu bằng chú thích @Test (mỗi hàm là một thử nghiệm duy nhất).
  • có chứa các tuyên bố xác nhận.

Android dùng thư viện thử nghiệm JUnit để thử nghiệm (trong lớp học lập trình JUnit4 này). Cả hai loại này, từ xác nhận và chú thích @Test đều đến từ JUnit.

Cài đặt là cốt lõi trong thử nghiệm của bạn. Đó là 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, khẳng định là assertEquals(4, 2 + 2) kiểm tra để đảm bảo rằng 4 bằng 2 + 2.

Để xem thử nghiệm không thành công trông như thế nào, hãy thêm xác nhận rằng bạn có thể dễ dàng thấy không thành công. Nó sẽ kiểm tra rằng 3 bằng 1 + 1.

  1. Thêm assertEquals(3, 1 + 1) vào thử nghiệm 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 thử nghiệm.
  1. Trong kết quả thử nghiệm, hãy thấy dấu X bên cạnh thử nghiệm.

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

Bước 3: Chạy thử nghiệm đo lường

Các thử nghiệm đo lường nằm trong nhóm nguồn androidTest.

  1. Mở tập hợp nguồn androidTest.
  2. Chạy thử nghiệm tên là ExampleInstrumentedTest.

ExampleTooledTest

@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ư thử nghiệm cục bộ, thử nghiệm này chạy trên một thiết bị (trong ví dụ dưới đây là điện thoại Pixel 2 được mô phỏng):

Nếu bạn đã đính kèm thiết bị hoặc trình mô phỏng đang chạy, thì bạn sẽ thấy thử nghiệm chạy trên trình mô phỏng.

Trong nhiệm vụ này, bạn sẽ viết các bài kiểm tra cho getActiveAndCompleteStats, tính toán tỷ lệ phần trăm số liệu thống kê nhiệm vụ đang hoạt động và hoàn chỉnh cho ứng dụng của bạn. Bạn có thể xem các số này trên màn hình thống kê của ứng dụng.

Bước 1: Tạo lớp kiểm tra

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

ThốngUtils.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 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 việc cần làm đã hoàn thành và tỷ lệ phần trăm mà đang hoạt động.

Android Studio cung cấp cho bạn các công cụ để tạo mã thử nghiệm giúp bạn triển khai các thử nghiệm cho chức năng này.

  1. Nhấp chuột phải vào getActiveAndCompletedStats và chọn Tạo > Thử nghiệm.

Hộp thoại Tạo thử nghiệm sẽ mở ra:

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

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

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

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

Bước 2: Viết hàm kiểm tra đầu tiên của bạn

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

  • nếu không có tác vụ đã hoàn thành nào và một tác vụ đang hoạt động,
  • rằng tỷ lệ phần trăm của số thử nghiệm đang hoạt động là 100%,
  • và tỷ lệ phần trăm 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.

ThốngUtilsTest.kt

class StatisticsUtilsTest {

    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
        // Create an active task

        // Call your function

        // Check the result
    }
}
  1. Thêm chú thích @Test phía trên tên hàm để cho biết đó là thử nghiệm.
  2. Tạo một 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 cho những việc cần làm này.
// Call your function
val result = getActiveAndCompletedStats(tasks)
  1. Kiểm tra để chắc chắn result là những gì bạn mong đợi, bằng cách sử dụng các lời khẳng định.
// Check the result
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)

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

ThốngUtilsTest.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 thử nghiệm (Nhấp chuột phải vào StatisticsUtilsTest rồi chọn Chạy).

Thẻ phải vượt qua:

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

Vì thử nghiệm của bạn hoạt động như một tài liệu về mã hoạt động nên sẽ rất hữu ích khi người dùng có thể đọc được. So sánh hai lời khẳng định sau đây:

assertEquals(result.completedTasksPercent, 0f)

// versus

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

Câu xác nhận thứ hai giống với câu nói của người khác. Thông tin này được viết bằng khung xác nhận có tên Hamcrest. Một công cụ tốt khác để viết tuyên bố có thể đọc được là Thư viện trung thực. Bạn sẽ sử dụng Hamcrest trong lớp học lập trình này để viết phần khẳng định.

  1. Mở build.grade (Module: app) và 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 phần phụ thuộc, nhưng bạn đang sử dụng testImplementation ở đây. Khi bạn sẵn sàng chia sẻ ứng dụng của mình với mọi người, tốt nhất bạn không nên mở rộng kích thước APK với bất kỳ mã hoặc phần phụ thuộc thử nghiệm nào trong ứng dụng của mình. Bạn có thể chỉ định xem một thư viện có được đưa vào mã thử nghiệm hoặc chính không 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 này có sẵn trong tất cả các nhóm nguồn, bao gồm cả các nhóm nguồn thử nghiệm.
  • testImplementation—Phần phụ thuộc chỉ có trong nhóm nguồn thử nghiệm.
  • androidTestImplementation—Phần phụ thuộc này chỉ có trong nhóm nguồn androidTest.

Cấu hình mà bạn sử dụng sẽ xác định vị trí có thể sử dụng phần phụ thuộc này. 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 thử nghiệm. Điều này cũng đảm bảo rằng Hamcrest sẽ không được đưa vào ứng dụng cuối cùng của bạn.

Bước 4: Sử dụng Hamcrest để viết lời khẳng định

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

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

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

Thử nghiệm cuối cùng sẽ trông giống mã bên dưới.

ThốngUtilsTest.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. Hãy chạy thử nghiệm đã cập nhật để chắc chắn rằng tính năng này vẫn hoạt động!

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

Đây là nhiệm vụ không bắt buộc để thực hành.

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

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

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

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

Bước 2. Viết bài kiểm tra để tìm lỗi

Mã cho getActiveAndCompletedStats như đã viết có lỗi. Lưu ý rằng trình xử lý không xử lý đúng cách những gì sẽ 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 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 thử nghiệm, bạn sẽ sử dụng tính năng phát triển theo hướng thử nghiệm. Phát triển theo hướng thử nghiệm theo những bước sau.

  1. Viết bài kiểm tra bằng cách sử dụng cấu trúc Cho, Khi, Sau đó và với tên theo quy ước.
  2. Xác nhận kiểm tra không thành công.
  3. Viết mã tối thiểu để vượt qua bài kiểm tra.
  4. Lặp lại cho tất cả thử nghiệm!

Thay vì bắt đầu bằng cách sửa lỗi, bạn sẽ bắt đầu bằng cách viết các thử nghiệm trước. Sau đó, bạn có thể xác nhận rằng bạn có các thử nghiệm bảo vệ bạn khỏi việc vô tình giới thiệu lại các lỗi này 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 việc cần làm, thì danh sách sẽ là null và cả hai tỷ lệ phần trăm đều phải là 0f.
  3. Chạy thử nghiệm của bạn và xác nhận rằng chúng không đạt:

Bước 3. Sửa lỗi

Giờ đây, khi bạn đã kiểm tra xong, hãy khắc phục lỗi.

  1. Sửa lỗi trong getActiveAndCompletedStats bằng cách trả về 0f nếu tasks hoặc null 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 thử nghiệm lại và xác nhận rằng tất cả thử nghiệm hiện đã vượt qua!

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

  • Chức năng mới luôn có các thử nghiệm liên quan; do đó, thử nghiệm của bạn hoạt động như một tài liệu về mã của bạn.
  • Các thử nghiệm của bạn sẽ kiểm tra kết quả chính xác và bảo vệ khỏi những lỗi mà bạn đã thấy.

Giải pháp: Viết thêm các thử nghiệm

Dưới đây là tất cả thử nghiệm và mã tính năng tương ứng.

ThốngUtilsTest.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))
    }
}

ThốngUtils.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
        )
    }
}

Bạn đã làm tốt công việc cơ bản của quá trình viết và chạy thử nghiệm! Tiếp theo, bạn sẽ tìm hiểu cách viết các bài kiểm tra ViewModelLiveData cơ bản.

Trong phần còn lại của lớp học lập trình, bạn sẽ tìm hiểu cách viết bài kiểm tra cho hai lớp học Android phổ biến trên hầu hết các ứng dụng – ViewModelLiveData.

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


Bạn sẽ tập trung vào các thử nghiệm 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ữ bao gồm 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 thử nghiệm. Bạn sẽ tránh điều đó ngay bây giờ và tập trung vào việc viết các thử nghiệm cho chức năng ViewModel không trực tiếp kiểm tra bất kỳ điều gì trong kho lưu trữ.



Thử nghiệm mà bạn sẽ viết sẽ kiểm tra để đảm bảo rằng khi bạn gọi phương thức addNewTask, Event để mở cửa sổ việc cần làm mới sẽ được kích hoạt. Đây là mã ứng dụng mà bạn sẽ thử nghiệm.

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, ở bước này, bạn tạo một tệp thử nghiệm cho TasksViewModelTest.

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

  1. Trên màn hình Tạo thử nghiệm, 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 Chọn thư mục đích, hãy chọn thư mục thử nghiệm.

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

Trong bước này, bạn thêm một thử nghiệm mô hình chế độ xem để kiểm tra 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 thử nghiệm 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 bối cảnh đăng ký thì sao?

Khi bạn tạo một bản sao của TasksViewModel để kiểm tra, hàm dựng của nó sẽ cần có Ngữ cảnh ứng dụng. Nhưng trong thử nghiệm này, bạn không tạo một ứng dụng đầy đủ với các hoạt động và giao diện người dùng cũng như các mảnh, vậy làm thế nào để bạn có được ngữ cảnh ứng dụng?

TasksViewModelTest.kt

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

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

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

Bạn sẽ hoàn thành các bước này và sau đó hiểu rõ chức năng của chúng.

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

  1. Hãy 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 phần phụ thuộc Thử nghiệm AndroidX và cốt lõi bên ngoài, cũng như phần phụ thuộc thử nghiệm 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 trình chạy kiểm tra JUnit

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

TasksViewModelTest.kt

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

Bước 5. Sử dụng thử nghiệm AndroidX

Tại thời điểm này, bạn có thể sử dụng thư viện Thử nghiệm AndroidX. Điều này bao gồm phương thức ApplicationProvider.getApplicationContext, lấy bối cảnh ứng dụng.

  1. Tạo TasksViewModel bằng ApplicationProvider.getApplicationContext()từ thư viện thử nghiệm AndroidX.

TasksViewModelTest.kt

// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
  1. Gọi cho addNewTask trên tasksViewModel.

TasksViewModelTest.kt

tasksViewModel.addNewTask()

Khi đó, thử nghiệm của bạn sẽ trông giống như mã bên dưới.

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 thử nghiệm của bạn để xác nhận tính năng này hoạt động.

Khái niệm: Tính năng Kiểm tra AndroidX hoạt động như thế nào?

Thử nghiệm AndroidX là gì?

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

ApplicationProvider.getApplicationContext()

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

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

Ví dụ: vì bạn đã viết mã bằng thư viện Thử nghiệm 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 thử nghiệm sẽ vẫn chạy. getApplicationContext() hoạt động hơi khác nhau tùy thuộc vào việc thử nghiệm đó đang được chạy dưới dạng thử nghiệm cục bộ hay thử nghiệm:

  • Nếu thử nghiệm được đo lường, nó sẽ nhận được ngữ cảnh Ứng dụng thực tế được cung cấp khi khởi động trình mô phỏng hoặc kết nối với thiết bị thực.
  • Nếu đây là một thử nghiệm cục bộ, thì thử nghiệm 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à Thử nghiệm AndroidX sử dụng cho các thử nghiệm cục bộ do Robolectric cung cấp. Robolectric là một thư viện tạo ra môi trường Android mô phỏng cho mục đích thử nghiệm và chạy nhanh hơn 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 sau:

Việc @RunWith(AndroidJUnit4::class) làm gì?

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

Trình chạy thử nghiệm AndroidJUnit4 cho phép Thử nghiệm AndroidX chạy thử nghiệm theo cách khác nhau, tùy thuộc vào việc thử nghiệm đó là phương tiện thử nghiệm hay thử nghiệm trên thiết bị.

Bước 6. Khắc phục cảnh báo cướp biển

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

Nhờ có tính năng Thử nghiệm AndroidX và người chạy thử nghiệm AndroidJunit4, bạn có thể thực hiện việc này mà không cần phải trực tiếp viết một dòng mã Robolectric!

Bạn có thể 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 của bạn.

  1. Thêm dòng sau vào tệp gradle của bạn để sử dụng tệp kê khai Android chính xác. Tùy chọn includeAndroidResources cho phép bạn truy cập vào các tài nguyên Android trong thử nghiệm đơ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. Việc chạy thử nghiệm trên Android Q yêu cầu Java 9. Thay vì cố gắng định cấu hình Android Studio để sử dụng Java 9, đối với lớp học lập trình này, hãy giữ SDK mục tiêu và biên dịch SDK ở mức 28.

Tóm tắt:

  • Việc thử nghiệm mô hình chế độ xem thuần túy thường có thể diễn ra trong bộ nguồn test vì mã của chúng thường không yêu cầu Android.
  • Bạn có thể sử dụng thư viện kiểm tra AndroidX để tải phiên bản thử nghiệm của các thành phần như Ứng dụng và Hoạt động.
  • Nếu cần chạy mã Android được 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 thử nghiệm AndroidX và Robolectric để chạy thử nghiệm. Bài kiểm tra của bạn chưa hoàn tất (bạn chưa viết tuyên bố xác nhận, bạn chỉ cần nói // TODO test LiveData). Bạn sẽ học cách viết tuyên bố xác nhận với LiveData tiếp theo.

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 đã dừng lại mà không kiểm tra 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 tra LiveData, bạn nên làm hai việc sau:

  1. Sử dụng InstantTaskExecutorRule
  2. Đảm bảo chế độ quan sát của LiveData

Bước 1. Sử dụngQuy tắc thực thi tác vụ tức thì

InstantTaskExecutorRule là một Quy tắc JUnit. Khi bạn dùng chú thích này với chú thích @get:Rule, thao tác này sẽ khiến một số mã trong lớp InstantTaskExecutorRule chạy trước và sau khi thử nghiệm (để 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 công việc trong nền Thành phần cấu trúc liên quan trong cùng một luồng để kết quả thử nghiệm diễn ra đồng bộ và theo thứ tự lặp lại. Khi bạn viết thử nghiệm bao gồm LiveData thử nghiệm, hãy sử dụng quy tắc này!

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

app/build.gradle

testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
  1. Mở TasksViewModelTest.kt
  2. Hãy thêm InstantTaskExecutorRule bên trong 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 rằng bạn quan sát được LiveData.

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 là quan trọng. Bạn cần có các đối tượng tiếp nhận dữ liệu đang hoạt động trên LiveData để

  • kích hoạt bất kỳ sự kiện onChanged nào.
  • kích hoạt bất kỳ Transformations nào.

Để nhận được hành vi LiveData dự kiến cho mô hình chế độ xem LiveData, bạn cần quan sát LiveData bằng LifecycleOwner.

Điều này gây ra vấn đề: trong thử nghiệm TasksViewModel, bạn không có hoạt động hay mảnh nào để quan sát LiveData của mình. Để giải quyết vấn đề này, bạn có thể sử dụng phương thức observeForever để đảm bảo LiveData liên tục được quan sát mà không cần LifecycleOwner. Khi observeForever, bạn cần nhớ loại bỏ người quan sát hoặc có nguy cơ rò rỉ đối tượng tiếp nhận dữ liệu.

Mã này trông giống như mã bên dưới. Hã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)
    }
}

Đó là rất nhiều mã nguyên mẫu để quan sát một LiveData duy nhất trong thử nghiệm! Có một số cách để loại bỏ mẫu nguyên mẫu này. Bạn sẽ tạo một hàm mở rộng có tên là LiveDataTestUtil để việc thêm đối tượng tiếp nhận dữ liệu trở nên đơn giản hơn.

  1. Tạo một tệp Kotlin mới có tên là LiveDataTestUtil.kt trong tập hợp nguồn test của bạn.


  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 thức khá phức tạp. Hàm này tạo ra một hàm mở rộng Kotlin có tên là getOrAwaitValue. Hàm này thêm một đối tượng tiếp nhận dữ liệu, lấy giá trị LiveData, sau đó xóa một đối tượng tiếp nhận dữ liệu – về cơ bản là một phiên bản ngắn và có thể tái sử dụng của mã observeForever hiển thị ở trên. Để xem nội dung giải thích đầy đủ về lớp học này, hãy xem bài đăng trên blog này.

Bước 3. Sử dụng getOrAChờValue để viết khẳng định

Ở bước này, bạn sử dụng phương thức getOrAwaitValue và viết câu lệnh xác nhận kiểm tra để đảm bảo rằng newTaskEvent đã được kích hoạt.

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

Quá trình kiểm tra hoàn tất phải 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ã của bạn và xem bài kiểm tra!

Giờ đây, khi đã biết cách viết thử nghiệm, bạn có thể tự viết chúng. Trong bước này, hãy sử dụng các kỹ năng mà bạn đã học được, thực hành viết một bài kiểm tra TasksViewModel khác.

Bước 1. Viết thử nghiệm ViewModel của riêng bạn

Bạn sẽ viết setFilterAllTasks_tasksAddViewVisible(). Thử nghiệm này sẽ kiểm tra để đảm bảo rằng nếu bạn đặt loại bộ lọc để hiển thị tất cả việc cần làm, thì nút Thêm việc cần làm sẽ hiển thị.

  1. Sử dụng addNewTask_setsNewTaskEvent() để tham chiếu, viết thử nghiệm trong TasksViewModelTest có tên setFilterAllTasks_tasksAddViewVisible(), đặt chế độ lọc thành ALL_TASKS và xác nhận rằng LiveData tasksAddViewVisibletrue.


Hãy sử dụng mã bên dưới để 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 ý:

  • liệt kê TasksFilterType cho tất cả việc cần làm là ALL_TASKS.
  • Chế độ hiển thị của nút để thêm một việc cần làm là do LiveData tasksAddViewVisible. kiểm soát
  1. Chạy thử nghiệm.

Bước 2. So sánh thử nghiệm 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:

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

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

Hãy xem cách bạn xác định TasksViewModel khi bắt đầu cả hai thử nghiệm.

TasksViewModelTest

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

Khi thiết lập lặp lại mã cho nhiều thử nghiệm, bạn có thể 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ả những thử nghiệm này sẽ thử nghiệm TasksViewModel và cần có mô hình chế độ xem, nên hãy chuyển mã này sang 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ú thích bằng @Before.
  4. Di chuyển mã tạo thực thể 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 thực hiện những việc sau, không khởi tạo

tasksViewModel

kèm theo định nghĩa:

val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

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

Mã cuối cùng của bạn cho TasksViewModelTest phải giố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 xuống mã cho lớp học lập trình đã hoàn thành, 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


Hoặc bạn có thể tải kho lưu trữ xuống dưới dạng tệp Zip, 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 thử nghiệm từ Android Studio.
  • Sự khác biệt giữa các bài kiểm tra cục bộ (test) và các thiết bị đo lường (androidTest).
  • Cách viết bài kiểm tra đơn vị cục bộ bằng JUnitHamcrest.
  • Thiết lập thử nghiệm ViewModel với Thư viện thử nghiệm AndroidX.

Khóa học từ Udacity:

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

Video:

Các tài liệu khác:

Để xem đường liên kết đến các lớp học lập trình khác trong khóa học này, hãy xem trang đích Nâng cao cho Android trong Kotlin.