Trong bài viết trước, tôi đã mô tả cách bạn có thể sử dụng Proto DataStore trong ứng dụng của mình. Tôi đã viết bài viết đó như một phần trải nghiệm của mình khi sử dụng Proto DataStore trong một trong các ứng dụng của mình.
Sau đó, tôi muốn xem việc viết bài kiểm tra cho Proto DataStore trong ứng dụng đó sẽ như thế nào bằng cách sử dụng kiến thức tôi có được.
Tìm kiếm hướng dẫn trực tuyến không mang lại nhiều sự nhẹ nhõm, vì vậy tôi nghĩ mình sẽ chia sẻ kiến thức của mình cho những người có thể đang tìm kiếm nó. Trong trường hợp xấu nhất thì đó là cho con cháu của tôi.
Trong quá trình tìm kiếm, tôi đã tìm thấy bài viết này, nhưng bài viết đó chủ yếu tập trung vào việc thử nghiệm DataStore Tùy chọn chứ không phải Proto DataStore. Nó có ghi rõ rằng:
“Tuy nhiên, hãy nhớ rằng bạn có thể sử dụng tài liệu này để thiết lập Proto DataStore thử nghiệm vì nó rất giống với Tùy chọn.”
Nhưng sau khi theo dõi, tôi phát hiện ra rằng ngoài phần phụ thuộc, không có nhiều điểm tương đồng ở đây và bạn cần đưa ra logic riêng để kiểm tra Proto DataStore của riêng mình.
Sắp xếp mọi thứ
Trong tệp build.gradle của ứng dụng của bạn, hãy thêm các phần phụ thuộc sau:
dependencies {
///.....
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
}
$compose_version là biến bạn đã xác định trong tệp build.gradle cấp dự án của mình.
Sau đó, đi tới androidTest của bạn thư mục và tạo một tập tin mới. Thông thường, bạn sẽ có một lớp kho lưu trữ tương tác với Proto DataStore của mình, vì vậy bạn có thể đặt tên tệp này là YourRepositoryClassNameTest. Chúng tôi sẽ sử dụng tên MyRepositoryTest .
Trước khi đi sâu vào thử nghiệm Proto DataStore, chúng ta cần khởi tạo nó. Nếu bạn lên mạng để tìm bất kỳ tài liệu nào về vấn đề này thì nó khá là thưa thớt.
Việc khởi tạo Proto DataStore được sử dụng với Bối cảnh chung như vậy (khi không được sử dụng trong kịch bản thử nghiệm):
private val Context.myDataStore: DataStore<MyItem> by dataStore(
fileName = DATA_STORE_FILE_NAME,
serializer = MyItemSerializer
)
Chà, bạn không thể làm điều này trong lớp kiểm tra, bởi vì, mặc dù bạn có thể sao chép và dán đoạn mã trên, nhưng bạn sẽ không thể quyền truy cập đối tượng DataStore. Bạn có thể lấy bối cảnh ứng dụng như thế này:
ApplicationProvider.getApplicationContext()
nhưng myDataStore của chúng tôi đối tượng sẽ không có sẵn thông qua nó.
Vậy chúng ta có thể làm gì?
Trong bài viết được liên kết ở trên, có một ví dụ về cách chúng ta có thể tạo Preference DataStore bằng phương thức PreferenceDataStoreFactory.create.
fun create(
corruptionHandler: ReplaceFileCorruptionHandler<Preferences>? = null,
migrations: List<DataMigration<Preferences>> = listOf(),
scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
produceFile: () -> File): DataStore<Preferences>
Nhưng vì chúng tôi không sử dụng Preference DataStore nên điều đó sẽ không hiệu quả với chúng tôi. Cách hiệu quả là sử dụng phương thức DataStoreFactory.create như thế này:
fun <T : Any?> create(
serializer: Serializer<T>,
corruptionHandler: ReplaceFileCorruptionHandler<T>? = null,
migrations: List<DataMigration<T>> = listOf(),
scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()), produceFile: () -> File): DataStore<T>
Có một số đối số cho phương thức này (và một số có giá trị mặc định), nhưng chúng ta không cần chuyển tất cả chúng vào. Chúng ta sẽ chuyển:
- Lớp serializer của chúng tôi
- Phương thức lambda để tạo tệp cho Proto DataStore
dataStore = DataStoreFactory.create(
produceFile = {
testContext.dataStoreFile(TEST_DATA_STORE_FILE_NAME) },
serializer = MyItemSerializer
)
Chúng tôi nhận được testContext bởi:
private val testContext: Context = ApplicationProvider.getApplicationContext()
Sau khi tạo thành công Proto DataStore, chúng ta có thể chuyển sang viết một số bài kiểm tra cho nó. Hãy nhớ rằng bạn có một lớp kho lưu trữ nhận phiên bản của Proto DataStore làm phần phụ thuộc, vì vậy sau khi tạo Proto DataStore, chúng ta cần tạo một phiên bản của lớp kho lưu trữ của mình.
private val repository = MyRepository(datastore)
Trước tiên, hãy tạo một thử nghiệm để kiểm tra trạng thái Proto DataStore ban đầu của chúng tôi. Bản thân Proto DataStore hiển thị một luồng mà chúng ta có thể sử dụng.
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun repository_testFetchInitialState() {
runTest {
testScope.launch {
val dataStoreObject = repository.myFlow.first()
// Insert here whatever we want to assert from our
// Proto DataStore. I.E. a flag whose initial value is false
assert(dataStoreObject.myFlag == false)
}
}
}
☝️ Bạn có thể đã nhận thấy điều này sớm hơn nhưng chúng tôi đang sử dụng chú thích Chọn tham gia ở đây. Điều này là do (hiện tại) các API mà chúng tôi đang sử dụng đang ở giai đoạn thử nghiệm và phải được đánh dấu khi chúng tôi sử dụng chúng.
Vì chúng ta đang truy cập vào luồng DataStore của mình , chúng ta cần gói nó trong testScope . TestScope được tạo bằng cách thực hiện:
@OptIn(ExperimentalCoroutinesApi::class)
private val dispatcher = TestCoroutineDispatcher()
@OptIn(ExperimentalCoroutinesApi::class)
private val testScope = TestCoroutineScope(dispatcher)
Hãy chạy nó và tận hưởng thử nghiệm Proto DataStore đầu tiên của bạn.
Điều đó thật thú vị trong khoảng hai giây.
Hãy làm điều gì đó có ý nghĩa hơn.
Hãy tưởng tượng Proto DataStore của chúng ta có một danh sách các đối tượng bên trong nó và chúng ta muốn kiểm tra trạng thái của nó khi thêm một mục vào đó.
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun repository_testAdditionOfItem() {
runTest {
testScope.launch {
//1
val item: MyItem = MyItem.newBuilder().setItemId(UUID.randomUUID().toString())
.setItemDescription(TEST_ITEM_DESCRIPTION).build()
//2
repository.updateItem(item)
//3
val items = repository.myFlow.first().itemsList
assert(items.size == 1)
//4
assert(items[0].itemDescription.equals(TEST_ITEM_DESCRIPTION))
}
}
}
- Chúng tôi tạo một mục thử nghiệm bằng cách sử dụng API được hiển thị từ protobuff
- Chúng tôi thêm mục này vào Proto DataStore bằng phương thức mà chúng tôi đã cung cấp trên lớp MyRepository
- Chúng tôi lấy danh sách các mục từ luồng mà Proto DataStore hiển thị
- Chúng tôi đảm bảo rằng mục được tìm thấy trong Proto DataStore khớp với mục chúng tôi đã tạo trước đó
DataStore của bạn bị rò rỉ
Nếu cố gắng chạy thử nghiệm ở trên cùng một lúc, bạn sẽ sớm gặp lỗi trong thời gian chạy:
Có nhiều DataStore đang hoạt động cho cùng một tệp:/data/user/0/com.example.app/files/datastore/dataStore_filename.pb. Bạn nên duy trì DataStore của mình dưới dạng một đơn vị hoặc xác nhận rằng không có hai DataStore nào đang hoạt động trên cùng một tệp (bằng cách xác nhận rằng phạm vi đã bị hủy).
Vâng, đó là vấn đề. Chúng tôi chỉ tạo một phiên bản DataStore trong lớp thử nghiệm của mình.
Chuyện gì đang xảy ra ở đây vậy?
Bởi vì chúng tôi không sử dụng đại biểu thuộc tính để tạo DataStore (nghĩa là Context.datastore), nên không đảm bảo rằng đối tượng DataStore của chúng tôi là một đối tượng đơn lẻ bất cứ khi nào chúng tôi truy cập nó.
Để tránh tình huống này, tôi đã phát hiện ra rằng có một cách tiếp cận là xóa và tạo lại DataStore cho từng trường hợp thử nghiệm. Để xóa DataStore, chúng ta có thể làm như sau:
@After
fun cleanup() {
File(testContext.filesDir, "datastore").deleteRecursively()
}
và trước mỗi lần kiểm tra, chúng tôi tạo lại nó:
@Before
fun setup() {
dataStore = DataStoreFactory.create(
produceFile = {
testContext.dataStoreFile(TEST_DATA_STORE_FILE_NAME)
},
serializer = MyItemSerializer
)
}
Để xem ví dụ đầy đủ, bạn có thể vào đây.
Trong bài viết này, tôi muốn trình bày tóm tắt về cách thử nghiệm Proto DataStore.
Mặc dù tôi đã xem qua hai trường hợp kiểm thử, tùy thuộc vào DataStore của bạn và các loại bạn đã định cấu hình ở đó, có thể có nhiều trường hợp kiểm thử và kịch bản cần viết hơn. Các khối xây dựng đã có sẵn, bạn chỉ cần điều chỉnh nó cho phù hợp với nhu cầu của mình.
Học cách viết mã miễn phí. Chương trình giảng dạy mã nguồn mở của freeCodeCamp đã giúp hơn 40.000 người có được việc làm với tư cách là nhà phát triển. Bắt đầu