ในบทความก่อนหน้านี้ ฉันได้อธิบายวิธีที่คุณสามารถใช้ Proto DataStore ในแอปพลิเคชันของคุณ ฉันเขียนบทความนั้นโดยเป็นส่วนหนึ่งของประสบการณ์ของฉันในการใช้ Proto DataStore ในแอปพลิเคชันของฉัน
หลังจากนั้น ฉันต้องการดูว่าการเขียนการทดสอบสำหรับ Proto DataStore ในแอปพลิเคชันนั้นเป็นอย่างไรโดยใช้ความรู้ที่ฉันได้รับ ป>
การค้นหาคำแนะนำทางออนไลน์ไม่ได้ช่วยอะไรมากนัก ฉันจึงคิดว่าจะแบ่งปันความรู้ของฉันให้กับผู้ที่อาจกำลังมองหาคำแนะนำนั้น ในกรณีที่เลวร้ายที่สุด มันจะเป็นของลูกหลานของฉัน
ในการค้นหาของฉัน ฉันพบบทความนี้ แต่ส่วนใหญ่จะมุ่งเน้นไปที่การทดสอบ Preferences DataStore ไม่ใช่ Proto DataStore มันระบุว่า:
“อย่างไรก็ตาม โปรดทราบว่าคุณสามารถใช้เนื้อหานี้ในการตั้งค่า Proto DataStore การทดสอบ เนื่องจากจะคล้ายกับการตั้งค่าอย่างมาก”
แต่เมื่อปฏิบัติตามแล้ว ฉันพบว่านอกเหนือจากการขึ้นต่อกันแล้ว ที่นี่ไม่มีความคล้ายคลึงกันมากนัก และคุณจำเป็นต้องแนะนำตรรกะที่แยกจากกันเพื่อทดสอบ Proto DataStore ของคุณเอง
การตั้งค่าสิ่งต่างๆ
ในไฟล์ build.gradle ของแอปพลิเคชันของคุณ ให้เพิ่มการอ้างอิงต่อไปนี้:
dependencies {
///.....
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
}
04รหัส> คือตัวแปรที่คุณกำหนดไว้ในไฟล์ build.gradle ระดับโปรเจ็กต์ของคุณ
จากนั้นไปที่ 17 ของคุณ ไดเร็กทอรีและสร้างไฟล์ใหม่ โดยปกติ คุณจะมีคลาสพื้นที่เก็บข้อมูลที่โต้ตอบกับ Proto DataStore ของคุณ ดังนั้นคุณจึงสามารถตั้งชื่อไฟล์นี้เป็น YourRepositoryClassNameTest เราจะใช้ชื่อ 23 .
ก่อนที่เราจะเจาะลึกการทดสอบ Proto DataStore เราจำเป็นต้องสร้างอินสแตนซ์ก่อน หากคุณออนไลน์เพื่อค้นหาเอกสารเกี่ยวกับเรื่องนี้ มันก็ค่อนข้างเบาบาง ป>
การสร้างอินสแตนซ์ Proto DataStore นั้นใช้กับบริบทส่วนกลางเช่นนั้น (เมื่อไม่ได้ใช้ในสถานการณ์การทดสอบ):
private val Context.myDataStore: DataStore<MyItem> by dataStore(
fileName = DATA_STORE_FILE_NAME,
serializer = MyItemSerializer
)
คุณไม่สามารถทำสิ่งนี้ในคลาสทดสอบได้ เพราะถึงแม้คุณจะสามารถคัดลอกและวางโค้ดด้านบนได้ แต่คุณจะไม่สามารถ การเข้าถึง วัตถุ DataStore คุณสามารถรับบริบทของแอปพลิเคชันดังนี้:
ApplicationProvider.getApplicationContext()
แต่ 36 ของเรา วัตถุจะไม่สามารถใช้ได้ผ่านมัน
แล้วเราจะทำอย่างไร
ในบทความที่เชื่อมโยงด้านบน มีตัวอย่างวิธีที่เราสามารถสร้าง Preference DataStore โดยใช้เมธอด PreferenceDataStoreFactory.create
fun create(
corruptionHandler: ReplaceFileCorruptionHandler<Preferences>? = null,
migrations: List<DataMigration<Preferences>> = listOf(),
scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
produceFile: () -> File): DataStore<Preferences>
แต่เนื่องจากเราไม่ได้ใช้ Preference DataStore นั่นจึงไม่เหมาะกับเรา สิ่งที่ได้ผลคือใช้เมธอด DataStoreFactory.create เช่นนี้:
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>
มีข้อโต้แย้งหลายประการสำหรับวิธีนี้ (และบางข้อมีค่าเริ่มต้น) แต่เราไม่จำเป็นต้องส่งผ่านข้อโต้แย้งทั้งหมด เราจะส่งผ่าน:
- คลาสซีเรียลไลเซอร์ของเรา
- วิธีการแลมบ์ดาสำหรับการสร้างไฟล์สำหรับ Proto DataStore ของเรา
dataStore = DataStoreFactory.create(
produceFile = {
testContext.dataStoreFile(TEST_DATA_STORE_FILE_NAME) },
serializer = MyItemSerializer
)
เราได้รับ 44 โดย:
private val testContext: Context = ApplicationProvider.getApplicationContext()
หลังจากที่สร้าง Proto DataStore ของเราสำเร็จแล้ว เราก็สามารถเขียนการทดสอบต่อไปได้ โปรดทราบว่าคุณมีคลาสพื้นที่เก็บข้อมูลที่ได้รับอินสแตนซ์ของ Proto DataStore เป็นการพึ่งพา ดังนั้นหลังจากสร้าง Proto DataStore เราจำเป็นต้องสร้างอินสแตนซ์ของคลาสพื้นที่เก็บข้อมูลของเรา
private val repository = MyRepository(datastore)
ขั้นแรก เรามาสร้างการทดสอบเพื่อตรวจสอบสถานะ Proto DataStore เริ่มต้นของเรา Proto DataStore เองก็เปิดเผยโฟลว์ที่เราสามารถใช้ได้
@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)
}
}
}
JPOR คุณอาจสังเกตเห็นสิ่งนี้ก่อนหน้านี้ แต่เรากำลังใช้คำอธิบายประกอบ OptIn ที่นี่ เนื่องจาก (ปัจจุบัน) API ที่เราใช้อยู่ในขั้นทดลอง และต้องทำเครื่องหมายไว้เมื่อเราใช้งาน
เนื่องจากเรากำลังเข้าถึงโฟลว์ของ 55 ของเรา เราต้องล้อมมันไว้ใน 60 ของเรา . 77รหัส> ถูกสร้างขึ้นโดยการทำ:
@OptIn(ExperimentalCoroutinesApi::class)
private val dispatcher = TestCoroutineDispatcher()
@OptIn(ExperimentalCoroutinesApi::class)
private val testScope = TestCoroutineScope(dispatcher)
เรียกใช้และสนุกกับการทดสอบ Proto DataStore ครั้งแรกของคุณ
นั่นเป็นเรื่องสนุกประมาณสองวินาที
มาทำอะไรที่มีความหมายมากกว่านี้กันดีกว่า
ลองนึกภาพ Proto DataStore ของเรามีรายการอ็อบเจ็กต์อยู่ข้างใน และเราต้องการทดสอบสถานะของมันเมื่อเราเพิ่มรายการเข้าไป
@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))
}
}
}
- เราสร้างรายการทดสอบโดยใช้ API ที่เปิดเผยจาก protobuff
- เราเพิ่มรายการนี้ไปยัง Proto DataStore โดยใช้วิธีที่เราเปิดเผยในคลาส MyRepository
- เรารวบรวมรายการจากโฟลว์ที่ Proto DataStore เปิดเผย
- เราตรวจสอบให้แน่ใจว่ารายการที่พบใน Proto DataStore ตรงกับรายการที่เราสร้างไว้ก่อนหน้านี้
DataStore ของคุณมีการรั่วไหล
หากคุณพยายามเรียกใช้การทดสอบข้างต้นในคราวเดียว ในไม่ช้า คุณจะได้รับข้อผิดพลาดระหว่างรันไทม์:
มี DataStore หลายตัวที่ใช้งานอยู่ในไฟล์เดียวกัน:/data/user/0/com.example.app/files/datastore/dataStore_filename.pb คุณควรรักษา DataStore ของคุณเป็นซิงเกิลตันหรือยืนยันว่าไม่มี DataStore สองอันที่ใช้งานอยู่ในไฟล์เดียวกัน (โดยยืนยันว่าขอบเขตถูกยกเลิก)
นั่นเป็นปัญหา เราสร้างอินสแตนซ์ DataStore เพียงอินสแตนซ์เดียวในคลาสทดสอบของเรา
เกิดอะไรขึ้นที่นี่?
เนื่องจากเราไม่ได้ใช้ตัวแทนคุณสมบัติเพื่อสร้าง DataStore ของเรา (หมายถึง Context.datastore) จึงไม่รับประกันว่าออบเจ็กต์ DataStore ของเราจะเป็นซิงเกิลตันทุกครั้งที่เราเข้าถึง
เพื่อหลีกเลี่ยงสถานการณ์นี้ ฉันพบว่าวิธีหนึ่งคือการลบและสร้าง DataStore ใหม่สำหรับแต่ละกรณีทดสอบ หากต้องการลบ DataStore เราสามารถทำได้ดังนี้:
@After
fun cleanup() {
File(testContext.filesDir, "datastore").deleteRecursively()
}
และก่อนการทดสอบทุกครั้ง เราจะสร้างมันขึ้นมาใหม่:
@Before
fun setup() {
dataStore = DataStoreFactory.create(
produceFile = {
testContext.dataStoreFile(TEST_DATA_STORE_FILE_NAME)
},
serializer = MyItemSerializer
)
}
หากต้องการดูตัวอย่างเต็ม โปรดไปที่นี่
ในบทความนี้ ฉันต้องการแสดงโครงร่างของวิธีทดสอบ Proto DataStore ป>
แม้ว่าฉันจะศึกษากรณีทดสอบสองกรณี ขึ้นอยู่กับ DataStore ของคุณและประเภทที่คุณกำหนดค่าไว้ที่นั่น อาจมีกรณีทดสอบและสถานการณ์จำลองเพิ่มเติมให้เขียน โครงสร้างมีอยู่ครบถ้วน คุณเพียงแค่ต้องปรับให้เข้ากับความต้องการของคุณ
เรียนรู้การเขียนโค้ดฟรี หลักสูตรโอเพ่นซอร์สของ freeCodeCamp ช่วยให้ผู้คนมากกว่า 40,000 คนได้งานในตำแหน่งนักพัฒนา เริ่มต้น