Codelab นี้เป็นส่วนหนึ่งของหลักสูตร Android ขั้นสูงใน Kotlin คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้ หากเรียนผ่าน Codelab ตามลําดับ แต่ไม่บังคับ Codelab ของหลักสูตรทั้งหมดจะแสดงอยู่ในหน้า Landing Page ขั้นสูงของ Android ใน Kotlin Codelab
บทนำ
เมื่อคุณใช้ฟีเจอร์แรกของแอปแรก คุณอาจต้องเรียกใช้โค้ดเพื่อยืนยันว่าโค้ดทํางานตามที่คาดไว้ คุณได้ทําการทดสอบแม้ว่าจะทดสอบด้วยตนเองก็ตาม ขณะที่คุณเพิ่มและอัปเดตฟีเจอร์อย่างต่อเนื่อง คุณก็อาจเรียกใช้โค้ดต่อไปและยืนยันว่าโค้ดทํางานได้ แต่การลงมือทําเองทุกๆ ครั้งก็เป็นเรื่องน่าเบื่อ มีแนวโน้มที่จะเกิดความผิดพลาด และไม่ได้ปรับขนาด
คอมพิวเตอร์ทํางานได้ดีเยี่ยมในการปรับขนาดและเป็นระบบอัตโนมัติ ดังนั้น นักพัฒนาซอฟต์แวร์ในบริษัทขนาดใหญ่และขนาดเล็กจึงเขียนการทดสอบอัตโนมัติ ซึ่งเป็นการทดสอบที่ดําเนินการโดยซอฟต์แวร์ และคุณไม่จําเป็นต้องดําเนินการแอปด้วยตนเองเพื่อยืนยันว่าโค้ดทํางาน
สิ่งที่คุณจะได้เรียนรู้จาก Codelab ชุดนี้คือวิธีสร้างคอลเล็กชันการทดสอบ (หรือที่เรียกว่าชุดทดสอบ) สําหรับแอปในชีวิตจริง
Codelab แรกครอบคลุมข้อมูลพื้นฐานของการทดสอบใน Android โดยคุณจะเขียนการทดสอบแรกและดูวิธีทดสอบ LiveData
และ ViewModel
ได้
สิ่งที่ควรทราบอยู่แล้ว
คุณควรทําความคุ้นเคยกับสิ่งต่อไปนี้
- ภาษาโปรแกรม Kotlin
- ไลบรารีหลักของ Android Jetpack:
ViewModel
และLiveData
- สถาปัตยกรรมแอปพลิเคชันตามรูปแบบจากคู่มือสถาปัตยกรรมแอปและ Codelab พื้นฐานของ Android
สิ่งที่คุณจะได้เรียนรู้
คุณจะได้เรียนรู้เกี่ยวกับหัวข้อต่อไปนี้
- วิธีเขียนและเรียกใช้การทดสอบหน่วยใน Android
- วิธีใช้การพัฒนาที่ขับเคลื่อนโดยการทดสอบ
- วิธีเลือกการทดสอบการวัดคุมและการทดสอบในพื้นที่
คุณจะได้เรียนรู้เกี่ยวกับไลบรารีและแนวคิดโค้ดต่อไปนี้
- หน่วยการเรียนรู้ 4
- Hamcrest
- ไลบรารีการทดสอบของ AndroidX
- ไลบรารีทดสอบหลักของคอมโพเนนต์สถาปัตยกรรม AndroidX
สิ่งที่คุณจะทํา
- ตั้งค่า เรียกใช้ และตีความการทดสอบทั้งในเครื่องและในท้องถิ่นใน Android
- เขียนการทดสอบหน่วยใน Android โดยใช้ JUnit4 และ Hamcrest
- เขียนการทดสอบ
LiveData
และViewModel
แบบง่าย
ใน Codelab ชุดนี้ คุณจะต้องทํางานร่วมกับแอป TO-DO Notes ซึ่งช่วยให้คุณเขียนงานให้เสร็จและแสดงในรายการได้ จากนั้นทําเครื่องหมายว่าเสร็จสมบูรณ์ กรอง หรือลบ
แอปนี้เขียนขึ้นใน Kotlin มีหน้าจอหลายหน้าจอ ใช้คอมโพเนนต์ Jetpack และปฏิบัติตามสถาปัตยกรรมจากคําแนะนําเกี่ยวกับสถาปัตยกรรมแอป การเรียนรู้วิธีทดสอบแอปนี้จะทําให้คุณทดสอบแอปที่ใช้ไลบรารีและสถาปัตยกรรมเดียวกันได้
ดาวน์โหลดโค้ดเพื่อเริ่มต้นใช้งาน
หรือโคลนที่เก็บ GitHub สําหรับโค้ดได้ดังนี้
$ git clone https://github.com/googlecodelabs/android-testing.git $ cd android-testing $ git checkout starter_code
ในงานนี้ คุณจะได้เรียกใช้แอปและสํารวจฐานโค้ด
ขั้นตอนที่ 1: เรียกใช้แอปตัวอย่าง
เมื่อดาวน์โหลดแอป TO-DO แล้ว ให้เปิดแอปใน Android Studio และเรียกใช้ ควรรวบรวม สํารวจแอปโดยทําตามขั้นตอนต่อไปนี้
- สร้างงานใหม่ด้วยปุ่มการทํางานแบบลอยและบวก ป้อนชื่อเรื่องก่อน จากนั้นป้อนข้อมูลเพิ่มเติมเกี่ยวกับงาน บันทึกโดยใช้ FAB เครื่องหมายถูกสีเขียว
- ในรายการงาน ให้คลิกชื่องานที่เพิ่งทําเสร็จ แล้วดูหน้าจอรายละเอียดของงานนั้นเพื่อดูคําอธิบายที่เหลือ
- ในรายการหรือในหน้าจอรายละเอียด ให้เลือกช่องทําเครื่องหมายของงานนั้นเพื่อตั้งสถานะเป็นเสร็จสมบูรณ์
- กลับไปที่หน้าจองาน เปิดเมนูตัวกรอง และกรองงานตามสถานะใช้งานอยู่และเสร็จสมบูรณ์
- เปิดลิ้นชักการนําทาง แล้วคลิกสถิติ
- กลับไปที่หน้าจอภาพรวม และเลือกล้างงานที่เสร็จสมบูรณ์เพื่อลบงานทั้งหมดที่มีสถานะเสร็จสมบูรณ์จากเมนูงาน
ขั้นตอนที่ 2: สํารวจโค้ดของแอปตัวอย่าง
แอป "สิ่งที่ต้องทํา" นั้นมาจากตัวอย่างการทดสอบและสถาปัตยกรรมของ Architecture Blueprints ที่ได้รับความนิยม (โดยใช้เวอร์ชันสถาปัตยกรรมเชิงรับ) แอปจะทําตามสถาปัตยกรรมจากคู่มือสถาปัตยกรรมแอป โดยใช้ View Models กับ Fragments, ที่เก็บ และ Room หากคุณคุ้นเคยกับตัวอย่างด้านล่าง แอปนี้มีสถาปัตยกรรมที่คล้ายกัน
- ห้องที่มี View Codelab
- Codelab การฝึกอบรม Kotlin Fundamentals ขั้นพื้นฐาน
- Codelab การฝึกอบรม Android ขั้นสูง
- ตัวอย่าง Android Sunflower
- การพัฒนาแอป Android ด้วยหลักสูตรการฝึกอบรม Kotlin Udacity
คุณจําเป็นต้องทําความเข้าใจสถาปัตยกรรมทั่วไปของแอปมากกว่าการทําความเข้าใจตรรกะในเลเยอร์ใดชั้นหนึ่ง
ด้านล่างนี้เป็นข้อมูลสรุปแพ็กเกจที่คุณจะพบ
แพ็กเกจ: | |
| เพิ่มหรือแก้ไขหน้าจองาน: โค้ดเลเยอร์ UI สําหรับการเพิ่มหรือแก้ไขงาน |
| ชั้นข้อมูล: ใช้กับชั้นข้อมูลของงาน ซึ่งประกอบด้วยฐานข้อมูล เครือข่าย และโค้ดที่เก็บ |
| หน้าจอสถิติ: รหัสเลเยอร์ UI สําหรับหน้าจอสถิติ |
| หน้าจอรายละเอียดงาน: รหัสเลเยอร์ UI สําหรับงานเดียว |
| หน้าจองาน: รหัสเลเยอร์ UI สําหรับรายการงานทั้งหมด |
| ชั้นเรียนยูทิลิตี้:ชั้นเรียนที่แชร์และใช้ในส่วนต่างๆ ของแอป เช่น สําหรับเลย์เอาต์การรีเฟรชแบบปัดที่ใช้หลายหน้าจอ |
ชั้นข้อมูล (.data)
แอปนี้มีเลเยอร์เครือข่ายจําลองในแพ็กเกจระยะไกลและเลเยอร์ฐานข้อมูลในแพ็กเกจภายใน เพื่อความสะดวก โปรเจ็กต์นี้จะจําลองชั้นเครือข่ายด้วย HashMap
ที่มีความล่าช้า ไม่ใช่การส่งคําขอเครือข่ายจริง
พิกัดหรือสื่อกลางของ DefaultTasksRepository
ระหว่างเลเยอร์เครือข่ายและเลเยอร์ฐานข้อมูลคือสิ่งที่ส่งคืนข้อมูลไปยังเลเยอร์ UI
เลเยอร์ของ UI ( .addedittask, .statistics, .taskdetail, .tasks)
แพ็กเกจเลเยอร์ UI แต่ละรายการมีส่วนย่อยและโมเดลข้อมูลพร็อพเพอร์ตี้ ตลอดจนคลาสอื่นๆ ที่จําเป็นสําหรับ UI (เช่น อะแดปเตอร์สําหรับรายการงาน) TaskActivity
คือกิจกรรมที่มีส่วนย่อยทั้งหมด
การไปยังส่วนต่างๆ
การนําทางสําหรับแอปจะควบคุมโดยคอมโพเนนต์การนําทาง ดังที่ระบุไว้ในไฟล์ nav_graph.xml
ระบบจะทริกเกอร์การนําทางในโมเดลข้อมูลพร็อพเพอร์ตี้โดยใช้คลาส Event
นอกจากนี้ โมเดลข้อมูลพร็อพเพอร์ตี้ยังกําหนดอาร์กิวเมนต์ที่จะส่งผ่านด้วย Fragment จะสังเกต Event
และทําการนําทางจริงระหว่างหน้าจอ
ในงานนี้ คุณจะทําการทดสอบครั้งแรก
- ใน Android Studio ให้เปิดแผงโปรเจ็กต์ แล้วหาโฟลเดอร์ 3 รายการต่อไปนี้
com.example.android.architecture.blueprints.todoapp
com.example.android.architecture.blueprints.todoapp (androidTest)
com.example.android.architecture.blueprints.todoapp (test)
โฟลเดอร์เหล่านี้เรียกว่าชุดแหล่งที่มา ชุดแหล่งที่มาคือโฟลเดอร์ที่มีซอร์สโค้ดสําหรับแอปของคุณ ชุดแหล่งที่มาซึ่งมีสีเขียว (androidTest และ test) ประกอบด้วยการทดสอบของคุณ เมื่อสร้างโปรเจ็กต์ Android ใหม่ คุณจะได้รับชุดแหล่งที่มา 3 ชุดต่อไปนี้โดยค่าเริ่มต้น ปัจจัยต่างๆ มีดังนี้
main
: มีโค้ดของแอป โค้ดนี้จะแชร์กับแอปเวอร์ชันต่างๆ ทั้งหมดที่คุณสร้างได้ (ซึ่งเรียกว่าเวอร์ชันบิลด์)androidTest
: มีการทดสอบที่เรียกว่าการทดสอบการวัดคุมtest
: มีการทดสอบที่เรียกว่าการทดสอบในพื้นที่
ความแตกต่างระหว่างการทดสอบในพื้นที่และการทดสอบแบบมีเครื่องทํางานในการทดสอบนี้
การทดสอบในพื้นที่ (ชุดแหล่งที่มา test
)
การทดสอบนี้จะทํางานในเครื่อง JVM ของเครื่องพัฒนาซอฟต์แวร์ และไม่ต้องใช้โปรแกรมจําลองหรืออุปกรณ์จริง ด้วยเหตุนี้จึงทํางานได้อย่างรวดเร็ว แต่ความแม่นยําจะต่ํากว่า ซึ่งหมายความว่าจะใช้งานน้อยกว่าในโลกจริง
การทดสอบในพื้นที่ของ Android Studio จะแสดงด้วยไอคอนสามเหลี่ยมสีเขียวและสีแดง
การทดสอบคุมสอบ (ชุดแหล่งที่มาandroidTest
)
การทดสอบเหล่านี้ทํางานในอุปกรณ์ Android จริงหรือที่จําลองออกมา ดังนั้นการทดสอบจึงสะท้อนถึงสิ่งที่จะเกิดขึ้นในชีวิตจริง แต่ก็ช้ากว่ามากด้วย
ในการทดสอบที่ใช้เครื่องดนตรีของ Android Studio จะแสดงด้วย Android ที่มีไอคอนสามเหลี่ยมสีเขียวและแดง
ขั้นตอนที่ 1: เรียกใช้การทดสอบในพื้นที่
- เปิดโฟลเดอร์
test
จนกว่าจะพบไฟล์ ExampleUnitTest.kt - คลิกขวาที่การทดสอบและเลือกเรียกใช้ ExampleUnitTest
คุณควรจะเห็นเอาต์พุตต่อไปนี้ในหน้าต่าง Run ที่ด้านล่างของหน้าจอ
- สังเกตเครื่องหมายถูกสีเขียวและขยายผลการทดสอบเพื่อยืนยันว่าการทดสอบหนึ่งที่ชื่อ
addition_isCorrect
ผ่าน การที่รู้ว่าสิ่งที่เพิ่มนั้นเป็นไปตามที่คาดไว้ก็เป็นเรื่องที่ดี
ขั้นตอนที่ 2: ทําการทดสอบไม่สําเร็จ
ด้านล่างนี้เป็นการทดสอบที่คุณเพิ่งดําเนินการ
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)
}
}
โปรดสังเกตว่าการทดสอบ
- เป็นคลาสในชุดแหล่งข้อมูลทดสอบชุดใดชุดหนึ่ง
- มีฟังก์ชันที่เริ่มต้นด้วยคําอธิบายประกอบ
@Test
(แต่ละฟังก์ชันเป็นการทดสอบครั้งเดียว) - มีข้อความยืนยันจริงๆ
Android ใช้ไลบรารีการทดสอบ JUnit สําหรับการทดสอบ (ใน JUnit 4 ของ Codelab นี้) ทั้งการยืนยันและคําอธิบายประกอบ @Test
มาจาก JUnit
การยืนยันคือหัวใจสําคัญของการทดสอบ ค่านี้เป็นคําสั่งเกี่ยวกับโค้ดที่ตรวจสอบว่าโค้ดหรือแอปของคุณทํางานตามที่คาดไว้ ในกรณีนี้ การยืนยันคือ assertEquals(4, 2 + 2)
ซึ่งจะตรวจสอบว่า 4 เท่ากับ 2 + 2
หากต้องการดูว่าการทดสอบที่ไม่สําเร็จมีลักษณะอย่างไร ให้เพิ่มข้อความยืนยันที่คุณจะเห็นได้ง่าย ระบบจะตรวจสอบว่า 3 เท่ากับ 1+1
- เพิ่ม
assertEquals(3, 1 + 1)
ในการทดสอบ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
}
}
- ทําการทดสอบ
- ผลการทดสอบจะมีเครื่องหมาย X ข้างการทดสอบ
- โปรดทราบด้วยว่า
- การยืนยันไม่สําเร็จครั้งเดียวทําให้การทดสอบทั้งหมดไม่สําเร็จ
- ระบบจะบอกค่าที่คาดหวัง (3) กับค่าที่คํานวณจริง (2)
- ระบบจะนําคุณไปยังบรรทัดการยืนยันที่ล้มเหลว
(ExampleUnitTest.kt:16)
ขั้นตอนที่ 3: ทําการทดสอบการวัดคุม
การทดสอบการวัดคุมจะอยู่ในชุดแหล่งที่มา androidTest
- เปิดชุดแหล่งที่มา
androidTest
- เรียกใช้การทดสอบที่ชื่อ
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)
}
}
การทดสอบนี้จะเรียกใช้ในอุปกรณ์ (ในตัวอย่างด้านล่างคือโทรศัพท์ Pixel 2 ที่จําลอง) ซึ่งแตกต่างจากการทดสอบในพื้นที่)
หากมีอุปกรณ์แนบหรือมีโปรแกรมจําลองทํางานอยู่ คุณควรเห็นการทดสอบทํางานอยู่ในโปรแกรมจําลอง
ในงานนี้ คุณจะเขียนการทดสอบสําหรับ getActiveAndCompleteStats
ซึ่งจะคํานวณเปอร์เซ็นต์ของสถิติที่ใช้งานอยู่และเสร็จสิ้นของแอป คุณดูตัวเลขเหล่านี้ได้ในหน้าจอสถิติของแอป
ขั้นตอนที่ 1: สร้างชั้นเรียนทดสอบ
- เปิด
StatisticsUtils.kt
ในซอร์สโค้ดmain
ในtodoapp.statistics
- ค้นหาฟังก์ชัน
getActiveAndCompletedStats
สถิติ Utils.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)
ฟังก์ชัน getActiveAndCompletedStats
จะยอมรับรายการงานและส่งคืน StatsResult
StatsResult
คือคลาสข้อมูลที่มี 2 จํานวน เปอร์เซ็นต์ของงานที่เสร็จสมบูรณ์ และเปอร์เซ็นต์ที่ใช้งานอยู่
Android Studio มีเครื่องมือให้คุณสร้างหลอดไฟทดสอบเพื่อช่วยให้คุณนําการทดสอบไปใช้สําหรับฟังก์ชันนี้ได้
- คลิกขวาที่
getActiveAndCompletedStats
และเลือกสร้าง > ทดสอบ
กล่องโต้ตอบสร้างการทดสอบจะเปิดขึ้น
- เปลี่ยน Class name: เป็น
StatisticsUtilsTest
(แทนStatisticsUtilsKtTest
; แย่กว่าไม่มี KT ในชื่อชั้นเรียนทดสอบ) - ใช้ค่าเริ่มต้นที่เหลือ JUnit 4 เป็นไลบรารีการทดสอบที่เหมาะสม แพ็กเกจปลายทางนั้นถูกต้อง (สะท้อนตําแหน่งของชั้นเรียน
StatisticsUtils
) และคุณไม่จําเป็นต้องเลือกช่องทําเครื่องหมายใดๆ (ขั้นตอนนี้เพียงแค่สร้างโค้ดเพิ่มเติม แต่คุณจะเขียนการทดสอบตั้งแต่ต้น) - กดตกลง
กล่องโต้ตอบเลือกไดเรกทอรีปลายทางจะเปิดขึ้น
คุณจะต้องทําการทดสอบในท้องถิ่นเนื่องจากฟังก์ชันของคุณคํานวณการคํานวณอยู่ และไม่มีโค้ดที่เฉพาะเจาะจงสําหรับ Android ดังนั้นจึงไม่จําเป็นต้องเรียกใช้บนอุปกรณ์จริงหรือที่จําลอง
- เลือกไดเรกทอรี
test
(ไม่ใช่androidTest
) เนื่องจากคุณจะเขียนการทดสอบในเครื่อง - คลิกตกลง
- สังเกตคลาส
StatisticsUtilsTest
ที่สร้างขึ้นในtest/statistics/
ขั้นตอนที่ 2: เขียนฟังก์ชันทดสอบแรก
คุณจะเขียนการทดสอบที่จะตรวจสอบว่า
- หากไม่มีงานที่เสร็จแล้วและงานเดียวที่ใช้งานอยู่
- เปอร์เซ็นต์ของการทดสอบที่ทํางานอยู่คือ 100%
- และเปอร์เซ็นต์ของงานที่เสร็จสมบูรณ์คือ 0%
- เปิด
StatisticsUtilsTest
- สร้างฟังก์ชันชื่อ
getActiveAndCompletedStats_noCompleted_returnsHundredZero
สถิติ UtilsTest.kt
class StatisticsUtilsTest {
fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
// Create an active task
// Call your function
// Check the result
}
}
- เพิ่มคําอธิบายประกอบ
@Test
เหนือชื่อฟังก์ชันเพื่อระบุว่าเป็นการทดสอบ - สร้างรายการงาน
// Create an active task
val tasks = listOf<Task>(
Task("title", "desc", isCompleted = false)
)
- โทรหา
getActiveAndCompletedStats
พร้อมงานเหล่านี้
// Call your function
val result = getActiveAndCompletedStats(tasks)
- ตรวจสอบว่า
result
เป็นไปตามที่คุณคาดไว้โดยใช้การยืนยัน
// Check the result
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)
นี่คือรหัสที่สมบูรณ์
สถิติ UtilsTest.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)
}
}
- ทําการทดสอบ (คลิกขวา
StatisticsUtilsTest
แล้วเลือกเรียกใช้)
ซึ่งควรผ่าน
ขั้นตอนที่ 3: เพิ่มทรัพยากร Dependency ของ Hamcrest
เนื่องจากการทดสอบจะทําหน้าที่เป็นเอกสารประกอบว่าโค้ดของคุณทําอะไรได้บ้าง จึงเป็นการดีเมื่อมนุษย์อ่านได้ เปรียบเทียบการยืนยัน 2 ข้อต่อไปนี้
assertEquals(result.completedTasksPercent, 0f)
// versus
assertThat(result.completedTasksPercent, `is`(0f))
การยืนยันครั้งที่ 2 จะอ่านคล้ายๆ กับประโยคจากมนุษย์ ซึ่งเขียนโดยใช้เฟรมเวิร์กการยืนยันชื่อ Hamcrest เครื่องมือที่ดีอีกอย่างสําหรับการเขียนการยืนยันที่อ่านได้คือคลัง Ruth คุณจะใช้ Hamcrest ใน Codelab นี้เพื่อเขียนการยืนยัน
- เปิด
build.grade (Module: app)
และเพิ่มทรัพยากร Dependency ต่อไปนี้
app/build.gradle
dependencies {
// Other dependencies
testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
}
โดยทั่วไป คุณจะใช้ implementation
เวลาเพิ่มทรัพยากร Dependency แต่ที่นี่คุณใช้ testImplementation
เมื่อพร้อมแชร์แอปกับผู้คนทั่วโลก ไม่ควรขยายขนาดของ APK ด้วยโค้ดทดสอบหรือทรัพยากร Dependency ในแอป คุณกําหนดได้ว่าจะให้ไลบรารีอยู่ในโค้ดหลักหรือโค้ดทดสอบโดยใช้การกําหนดค่า Gradle การกําหนดค่าที่พบบ่อยที่สุดมีดังนี้
implementation
- ทรัพยากร Dependency พร้อมใช้งานในชุดแหล่งที่มาทั้งหมด ซึ่งรวมถึงชุดแหล่งที่มาของการทดสอบtestImplementation
- ทรัพยากร Dependency ใช้ได้เฉพาะในชุดแหล่งที่มาของการทดสอบเท่านั้นandroidTestImplementation
- ทรัพยากร Dependency ใช้ได้เฉพาะในชุดแหล่งที่มาandroidTest
เท่านั้น
การกําหนดค่าที่ใช้จะเป็นตัวกําหนดตําแหน่งที่ใช้ทรัพยากร Dependency ได้ หากคุณเขียน:
testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
ซึ่งหมายความว่า Hamcrest จะพร้อมใช้งานในชุดแหล่งที่มาของการทดสอบเท่านั้น นอกจากนี้ยังช่วยให้มั่นใจว่า Hamcrest จะไม่รวมอยู่ในแอปขั้นสุดท้าย
ขั้นตอนที่ 4: ใช้ Hamcrest เพื่อเขียนการยืนยัน
- อัปเดตการทดสอบ
getActiveAndCompletedStats_noCompleted_returnsHundredZero()
เพื่อใช้assertThat
ของ Hamcrest แทนassertEquals
// REPLACE
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)
// WITH
assertThat(result.activeTasksPercent, `is`(100f))
assertThat(result.completedTasksPercent, `is`(0f))
โปรดทราบว่าคุณใช้การนําเข้า import org.hamcrest.Matchers.`is`
ได้เมื่อระบบแจ้ง
การทดสอบสุดท้ายจะมีรูปแบบด้านล่างนี้
สถิติ UtilsTest.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))
}
}
- เรียกใช้การทดสอบที่อัปเดตเพื่อยืนยันว่าการทดสอบยังใช้งานได้อยู่
Codelab นี้จะไม่สอนทุกสิ่งเกี่ยวกับ Hamcrest ดังนั้นหากต้องการดูข้อมูลเพิ่มเติม โปรดดูบทแนะนําอย่างเป็นทางการ
นี่คืองานที่ไม่บังคับสําหรับการฝึก
ในงานนี้ คุณจะเขียนการทดสอบเพิ่มเติมโดยใช้ JUnit และ Hamcrest นอกจากนี้คุณยังจะเขียนการทดสอบโดยใช้กลยุทธ์ที่ได้จากการฝึกอบรมโปรแกรมการพัฒนาที่ขับเคลื่อนด้วยการทดสอบด้วย Test Driven Development หรือ TDD คือโรงเรียนแนวความคิดทางโปรแกรมที่จะทํางานแทนที่จะเขียนโค้ดฟีเจอร์ขึ้นมาก่อน คุณก็ต้องเขียนการทดสอบก่อน จากนั้นจึงเขียนโค้ดฟีเจอร์โดยมีเป้าหมายในการผ่านการทดสอบ
ขั้นตอนที่ 1 เขียนการทดสอบ
เขียนการทดสอบสําหรับเมื่อคุณมีรายการงานปกติ:
- หากมีงานที่เสร็จแล้ว 1 รายการและไม่มีงานที่ใช้งานอยู่ เปอร์เซ็นต์
activeTasks
ควรเป็น0f
และเปอร์เซ็นต์งานที่เสร็จสมบูรณ์ควรเป็น100f
- หากมีงานที่เสร็จสมบูรณ์ 2 รายการและงานที่ใช้งานอยู่ 3 รายการ เปอร์เซ็นต์ที่เสร็จสมบูรณ์ควรเป็น
40f
และเปอร์เซ็นต์ที่ใช้งานอยู่ควรเป็น60f
ขั้นตอนที่ 2 เขียนการทดสอบสําหรับข้อบกพร่อง
รหัสสําหรับ getActiveAndCompletedStats
ตามที่มีข้อบกพร่อง โปรดสังเกตด้วยว่าวิธีจัดการกับรายการอย่างเหมาะสมอย่างไรหากรายการว่างเปล่าหรือไม่มีค่าว่าง ในทั้งสองกรณี เปอร์เซ็นต์ทั้งสองควรเป็น 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()
)
}
ในการแก้ไขโค้ดและเขียนการทดสอบ คุณจะต้องใช้การพัฒนาที่ขับเคลื่อนโดยการทดสอบ การพัฒนาที่ขับเคลื่อนด้วยการทดสอบทําตามขั้นตอนต่อไปนี้
- เขียนการทดสอบโดยใช้โครงสร้าง "ให้ไว้" "เมื่อไร" และ "แล้ว" พร้อมตั้งชื่อตามแบบแผน
- ยืนยันว่าการทดสอบล้มเหลว
- เขียนโค้ดน้อยที่สุดเพื่อให้ผ่านการทดสอบ
- ทําขั้นตอนนี้ซ้ําสําหรับการทดสอบทั้งหมด
แทนที่จะเริ่มต้นจากการแก้ไขข้อบกพร่อง คุณควรเริ่มต้นด้วยการเขียนการทดสอบก่อน จากนั้นคุณจะยืนยันได้ว่ามีการทดสอบที่ปกป้องคุณจากการนําข้อบกพร่องเหล่านี้มาใช้ซ้ําโดยไม่ได้ตั้งใจในอนาคต
- หากมีรายการว่างเปล่า (
emptyList()
) เปอร์เซ็นต์ทั้ง 2 เปอร์เซ็นต์ควรเป็น 0f - หากเกิดข้อผิดพลาดในการโหลดงาน รายการจะเป็น
null
และทั้ง 2 เปอร์เซ็นต์ควรมีค่าเป็น 0f - เรียกใช้การทดสอบและยืนยันว่าไม่ผ่าน
ขั้นตอนที่ 3 แก้ไขข้อบกพร่อง
เมื่อทําการทดสอบแล้ว ให้แก้ไขข้อบกพร่องดังกล่าว
- แก้ไขข้อบกพร่องใน
getActiveAndCompletedStats
โดยแสดงผล0f
หากtasks
คือnull
หรือว่างเปล่า:
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
)
}
}
- ทําการทดสอบอีกครั้งและยืนยันว่าการทดสอบทั้งหมดผ่านแล้ว
การปฏิบัติตาม TDD และการเขียนการทดสอบก่อนจะช่วยให้
- ฟังก์ชันใหม่มีการทดสอบที่เกี่ยวข้องเสมอ ดังนั้นการทดสอบจะทําหน้าที่เป็นเอกสารประกอบว่าโค้ดของคุณทําอะไรได้บ้าง
- การทดสอบของคุณจะตรวจสอบผลลัพธ์ที่ถูกต้องและป้องกันข้อบกพร่องที่คุณได้เห็นแล้ว
วิธีแก้ไข: การเขียนการทดสอบเพิ่มเติม
ต่อไปนี้คือการทดสอบทั้งหมดและรหัสฟีเจอร์ที่เกี่ยวข้อง
สถิติ UtilsTest.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))
}
}
สถิติ Utils.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
)
}
}
เยี่ยมมากที่มีพื้นฐานการเขียนและทําการทดสอบ ถัดไป คุณจะได้เรียนรู้วิธีเขียนการทดสอบขั้นพื้นฐานสําหรับ ViewModel
และ LiveData
ในส่วนที่เหลือของ Codelab คุณจะได้เรียนรู้วิธีเขียนการทดสอบสําหรับ Android 2 คลาสที่ใช้กันทั่วไปในแอปส่วนใหญ่ ได้แก่ ViewModel
และ LiveData
เริ่มต้นด้วยการเขียนการทดสอบสําหรับTasksViewModel
คุณกําลังจะมุ่งเน้นที่การทดสอบที่มีตรรกะทั้งหมดของโมเดลข้อมูลพร็อพเพอร์ตี้และไม่อาศัยโค้ดที่เก็บ โค้ดที่เก็บประกอบด้วยโค้ดแบบอะซิงโครนัส ฐานข้อมูล และการเรียกจากเครือข่ายที่ทั้งหมดเพิ่มความซับซ้อนของการทดสอบ คุณจะหลีกเลี่ยงในตอนนี้และเน้นไปที่การเขียนการทดสอบสําหรับฟังก์ชัน Viewmodel ที่ไม่ได้ทดสอบสิ่งที่อยู่ในที่เก็บโดยตรง
การทดสอบจะเขียนว่าเมื่อคุณเรียกใช้เมธอด addNewTask
Event
สําหรับการเปิดหน้าต่างงานใหม่จะเริ่มทํางาน นี่คือโค้ดของแอปที่คุณจะทดสอบ
TasksViewModel.kt
fun addNewTask() {
_newTaskEvent.value = Event(Unit)
}
ขั้นตอนที่ 1 สร้างชั้นเรียน TasksViewModelTest
โดยทําตามขั้นตอนเดียวกับที่คุณดําเนินการสําหรับ StatisticsUtilTest
ในขั้นตอนนี้ คุณจะสร้างไฟล์ทดสอบสําหรับ TasksViewModelTest
- เปิดชั้นเรียนที่คุณต้องการทดสอบในแพ็กเกจ
tasks
TasksViewModel.
- คลิกขวาที่ชื่อคลาส
TasksViewModel
-> Generate -> Test ในโค้ด
- ในหน้าจอสร้างการทดสอบ ให้คลิกตกลงเพื่อยอมรับ (ไม่ต้องเปลี่ยนการตั้งค่าเริ่มต้นใดๆ)
- ในกล่องโต้ตอบเลือกไดเรกทอรีปลายทาง ให้เลือกไดเรกทอรีการทดสอบ
ขั้นตอนที่ 2 เริ่มเขียนการทดสอบ ViewView
ในขั้นตอนนี้ คุณเพิ่มการทดสอบโมเดลข้อมูลพร็อพเพอร์ตี้เพื่อทดสอบว่าเมื่อคุณเรียกใช้เมธอด addNewTask
Event
สําหรับการเปิดหน้าต่างงานใหม่จะเริ่มทํางาน
- สร้างการทดสอบใหม่ชื่อ
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
}
}
แล้วบริบทของแอปพลิเคชันล่ะ
เมื่อคุณสร้างอินสแตนซ์ของ TasksViewModel
เพื่อทดสอบ ตัวสร้างต้องใช้บริบทของแอปพลิเคชัน แต่ในการทดสอบครั้งนี้ คุณจะไม่ได้สร้างแอปพลิเคชันขึ้นมาเต็มรูปแบบพร้อมด้วยกิจกรรมและ UI และส่วนย่อย คุณจึงจะได้รับบริบทของแอปพลิเคชันอย่างไร
TasksViewModelTest.kt
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(???)
ไลบรารีการทดสอบ AndroidX ประกอบด้วยคลาสและวิธีการที่มอบคอมโพเนนต์เวอร์ชันต่างๆ เช่น แอปพลิเคชันและกิจกรรม มีไว้สําหรับการทดสอบ เมื่อคุณมีการทดสอบในเครื่องที่คุณต้องการจําลองเฟรมเวิร์ก Android(เช่น บริบทแอปพลิเคชัน) ให้ทําตามขั้นตอนต่อไปนี้เพื่อตั้งค่าการทดสอบ AndroidX อย่างเหมาะสม
- เพิ่มแกนการทดสอบ XX และได้รับการยกเว้น
- เพิ่มทรัพยากร Dependency Robolectric Testing Library
- เพิ่มหมายเหตุของชั้นเรียนด้วยตัวทดสอบการทดสอบ AndroidJunit4
- เขียนโค้ดการทดสอบ AndroidX
คุณจะต้องทําตามขั้นตอนเหล่านี้ให้เสร็จและจากนั้นเข้าใจขั้นตอนเหล่านี้
ขั้นตอนที่ 3 เพิ่มทรัพยากร Dependency ของ Gradle
- คัดลอกทรัพยากร Dependency เหล่านี้ลงในไฟล์
build.gradle
ของโมดูลแอปเพื่อเพิ่มแกนทดสอบ AndroidX Test Core และ Dependency ของการทดสอบ รวมถึงทรัพยากร Dependency ของการทดสอบ 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"
ขั้นตอนที่ 4 เพิ่มผู้เรียกใช้การทดสอบ JUnit
- เพิ่ม
@RunWith(AndroidJUnit4::class)
ด้านบนคลาสทดสอบ
TasksViewModelTest.kt
@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
// Test code
}
ขั้นตอนที่ 5 ใช้การทดสอบ AndroidX
คุณสามารถใช้ไลบรารีการทดสอบ AndroidX ได้ในขั้นตอนนี้ ซึ่งรวมถึงเมธอด ApplicationProvider.getApplicationContex
t
ซึ่งได้รับบริบทของแอปพลิเคชัน
- สร้าง
TasksViewModel
โดยใช้ApplicationProvider.getApplicationContext()
จากไลบรารีการทดสอบของ AndroidX
TasksViewModelTest.kt
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
- โทรหา
addNewTask
ในtasksViewModel
TasksViewModelTest.kt
tasksViewModel.addNewTask()
ในขั้นตอนนี้ การทดสอบของคุณควรมีลักษณะเป็นโค้ดด้านล่าง
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
}
- ทําการทดสอบเพื่อยืนยันว่าใช้งานได้
แนวคิด: AndroidX Test ทํางานอย่างไร
AndroidX Test คืออะไร
AndroidX Test คือคลังไลบรารีสําหรับการทดสอบ โดยจะมีคลาสและวิธีการที่มอบคอมโพเนนต์เวอร์ชันต่างๆ เช่น แอปพลิเคชันและกิจกรรม มีไว้สําหรับการทดสอบ โดยในตัวอย่างโค้ดนี้ เป็นตัวอย่างของฟังก์ชันการทดสอบ AndroidX ในการรับบริบทของแอปพลิเคชัน
ApplicationProvider.getApplicationContext()
ข้อดีอย่างหนึ่งของ AndroidX Test API คือ API เหล่านี้สร้างขึ้นมาเพื่อทํางานทั้งในการทดสอบในพื้นที่และการทดสอบแบบมีเครื่องดนตรี ซึ่งถือเป็นเรื่องดี เนื่องจาก
- คุณทําการทดสอบเดียวกันกับการทดสอบในเครื่องหรือการทดสอบแบบมีเครื่องดนตรีก็ได้
- คุณไม่จําเป็นต้องเรียนรู้ API การทดสอบต่างๆ สําหรับการทดสอบในพื้นที่เทียบกับการทดสอบด้วยเครื่องดนตรี
ตัวอย่างเช่น เพราะคุณเขียนโค้ดโดยใช้ไลบรารีการทดสอบ AndroidX คุณจึงย้ายชั้นเรียน TasksViewModelTest
จากโฟลเดอร์ test
ไปยังโฟลเดอร์ androidTest
ได้และการทดสอบจะยังทํางานอยู่ getApplicationContext()
จะทํางานต่างออกไปเล็กน้อย ขึ้นอยู่กับว่าเรียกใช้เป็นการทดสอบภายในหรือทดสอบด้วยเครื่องต่อไปนี้
- หากเป็นการทดสอบการวัดคุม อุปกรณ์จะแสดงตัวอย่างบริบทของแอปพลิเคชันที่มีให้เมื่อเปิดโปรแกรมจําลองหรือเชื่อมต่อกับอุปกรณ์จริง
- หากเป็นการทดสอบในระบบ ก็จะใช้สภาพแวดล้อม Android จําลอง
Robolectric คืออะไร
สภาพแวดล้อม Android จําลองที่การทดสอบ AndroidX ใช้สําหรับการทดสอบในพื้นที่ให้บริการโดย Robolectric Robolectric คือไลบรารีที่สร้างสภาพแวดล้อม Android จําลองสําหรับการทดสอบและทํางานเร็วกว่าการเปิดเครื่องจําลองหรือทํางานบนอุปกรณ์ หากไม่อาศัยการอ้างอิงแบบ Robolectric คุณจะได้รับข้อผิดพลาดนี้
@RunWith(AndroidJUnit4::class)
ทําอะไร
ผู้เรียกใช้การทดสอบเป็นคอมโพเนนต์ JUnit ที่ทําการทดสอบ หากไม่มีเครื่องมือทดสอบ การทดสอบจะไม่ทํางาน มีผู้เรียกใช้การทดสอบเริ่มต้นจาก JUnit ที่คุณได้รับโดยอัตโนมัติ @RunWith
สลับตัวทดสอบเริ่มต้นนั้น
เครื่องมือทดสอบ AndroidJUnit4
ช่วยให้ AndroidX Test ทําการทดสอบได้ด้วยวิธีที่ต่างกัน โดยขึ้นอยู่กับว่าการทดสอบนั้นเป็นวิธีทดสอบหรืออยู่ในเครื่อง
ขั้นตอนที่ 6 แก้ไขคําเตือนเกี่ยวกับการโจรกรรม
เมื่อคุณเรียกใช้โค้ด โปรดทราบว่าระบบใช้ Robolectric
เนื่องจากเป็นการทดสอบ AndroidX และเครื่องมือทดสอบ AndroidJunit4 ขั้นตอนนี้ทําได้โดยที่คุณไม่ต้องเขียนโค้ด Robolectric บรรทัดเดียวโดยตรง
คุณอาจสังเกตเห็นคําเตือน 2 รายการ
No such manifest file: ./AndroidManifest.xml
"WARN: Android SDK 29 requires Java 9..."
คุณแก้ไขคําเตือน No such manifest file: ./AndroidManifest.xml
ได้โดยการอัปเดตไฟล์ Gradle
- เพิ่มบรรทัดต่อไปนี้ลงในไฟล์ Gradle เพื่อใช้ไฟล์ Manifest ของ Android ที่ถูกต้อง ตัวเลือก includeAndroidResources ช่วยให้คุณเข้าถึงแหล่งข้อมูลของ Android ในการทดสอบหน่วย ซึ่งรวมถึงไฟล์ 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
// ...
}
คําเตือน "WARN: Android SDK 29 requires Java 9..."
ซับซ้อนขึ้น การทดสอบใน Android Q ต้องใช้ Java 9 แทนที่จะพยายามกําหนดค่า Android Studio ให้ใช้ Java 9 สําหรับ Codelab นี้ ให้ใช้เป้าหมายและคอมไพล์ SDK ที่ 28
สรุป:
- โดยปกติแล้ว การทดสอบโมเดลการแสดงผลอย่างเดียวอาจอยู่ในชุดแหล่งที่มา
test
เนื่องจากมักจะไม่ใช้โค้ดของ Android - คุณสามารถใช้ไลบรารี AndroidX ในการทดสอบเพื่อดูเวอร์ชันทดสอบของคอมโพเนนต์ เช่น แอปพลิเคชันและกิจกรรม
- หากต้องการเรียกใช้โค้ด Android ที่จําลองในชุดซอร์สโค้ด
test
คุณสามารถเพิ่มทรัพยากร Dependency ของ Robolectric และคําอธิบายประกอบ@RunWith(AndroidJUnit4::class)
ได้
ยินดีด้วย คุณกําลังใช้ไลบรารีการทดสอบ AndroidX และ Robolectric ในการทดสอบ การสอบของคุณยังไม่สมบูรณ์ (คุณยังไม่ได้เขียนคําแถลงยืนยันต่อ แต่มีคําตอบว่า // TODO test LiveData
เท่านั้น) และคุณจะได้เรียนการเขียนคําแถลงยืนยันกับ LiveData
ต่อไป
ในงานนี้ คุณจะได้ดูวิธียืนยันค่า LiveData
อย่างถูกต้อง
คุณฟังต่อจากที่ค้างไว้โดยไม่มีการทดสอบโมเดล 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
}
เพื่อทดสอบ LiveData
เราขอแนะนําให้ทํา 2 สิ่งต่อไปนี้
- ใช้
InstantTaskExecutorRule
- คอยสังเกตการณ์
LiveData
ขั้นตอนที่ 1 ใช้ InstantTaskExecutorRule
InstantTaskExecutorRule
คือกฎหน่วย เมื่อใช้กับคําอธิบายประกอบ @get:Rule
จะทําให้โค้ดบางรายการในคลาส InstantTaskExecutorRule
ทํางานก่อนและหลังการทดสอบ (หากต้องการดูโค้ดแบบแม่นยํา คุณใช้แป้นพิมพ์ลัด Command+B เพื่อดูไฟล์ได้)
กฎนี้จะทํางานบนพื้นหลังที่เกี่ยวกับคอมโพเนนต์สถาปัตยกรรมทั้งหมดในชุดข้อความเดียวกันเพื่อให้ผลการทดสอบเกิดขึ้นพร้อมกัน และในลําดับที่ทําซ้ําได้ เวลาเขียนการทดสอบที่รวมการทดสอบ LiveData ให้ใช้กฎนี้
- เพิ่มทรัพยากร Dependency ในไลบรารีการทดสอบหลักของคอมโพเนนต์ Architecture Components (ซึ่งมีกฎนี้)
app/build.gradle
testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
- เปิด "
TasksViewModelTest.kt
" - เพิ่ม
InstantTaskExecutorRule
ภายในชั้นเรียนTasksViewModelTest
TasksViewModelTest.kt
class TasksViewModelTest {
@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
// Other code...
}
ขั้นตอนที่ 2 เพิ่มคลาส LiveDataTestUtil.kt
ขั้นตอนถัดไปคือการตรวจสอบว่ามีการทดสอบ LiveData
ที่คุณทดสอบ
เมื่อใช้ LiveData
โดยทั่วไปคุณมักจะมีกิจกรรมหรือส่วนย่อย (LifecycleOwner
) สังเกตเห็น LiveData
viewModel.resultLiveData.observe(fragment, Observer {
// Observer code here
})
การสังเกตการณ์นี้สําคัญ คุณต้องมีผู้สังเกตการณ์ที่ใช้งานอยู่ใน LiveData
เพื่อ
- ทริกเกอร์เหตุการณ์
onChanged
- ทริกเกอร์การเปลี่ยนรูปแบบ
หากต้องการใช้ลักษณะการทํางาน LiveData
ที่คาดไว้สําหรับโมเดลข้อมูลพร็อพเพอร์ตี้ LiveData
คุณต้องสังเกต LiveData
ด้วย LifecycleOwner
ปัญหานี้อาจเกิดจากการทดสอบ TasksViewModel
ไม่มีกิจกรรมหรือส่วนย่อยเพื่อที่จะสังเกต LiveData
ในการแก้ปัญหานี้ ให้ใช้เมธอด observeForever
เพื่อให้แน่ใจว่าระบบจะสังเกต LiveData
ตลอดเวลา โดยไม่จําเป็นต้องใช้ LifecycleOwner
เมื่อคุณobserveForever
อย่าลืมนําผู้สังเกตการณ์ออกหรือเสี่ยงต่อการรั่วไหลของผู้สังเกตการณ์
ซึ่งมีลักษณะดังนี้ โค้ดด้านล่าง ลองตรวจสอบ:
@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)
}
}
นั่นคือโค้ด Boilerplate จํานวนมากที่ให้คุณสังเกต LiveData
ในการทดสอบแต่ละครั้ง การกําจัด Boilerplate นี้ทําได้หลายวิธี คุณกําลังจะสร้างฟังก์ชันส่วนขยายชื่อ LiveDataTestUtil
เพื่อให้เพิ่มการสังเกตการณ์ได้ง่ายขึ้น
- สร้างไฟล์ Kotlin ใหม่ที่ชื่อ
LiveDataTestUtil.kt
ในชุดแหล่งที่มาtest
- คัดลอกและวางโค้ดด้านล่าง
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
}
เป็นวิธีที่ค่อนข้างซับซ้อน สร้างฟังก์ชันส่วนขยาย Kotlin ชื่อ getOrAwaitValue
ซึ่งจะเพิ่มผู้สังเกตการณ์ รับค่า LiveData
แล้วล้างตัวสังเกต โดยพื้นฐานแล้วโค้ด observeForever
เวอร์ชันที่ใช้ซ้ําได้ซึ่งระบุไว้ข้างต้น ดูคําอธิบายแบบเต็มของคลาสนี้ได้ในบล็อกโพสต์นี้
ขั้นตอนที่ 3 ใช้ getOrAWaitValue เขียนการยืนยัน
ในขั้นตอนนี้ ให้ใช้เมธอด getOrAwaitValue
และเขียนคําสั่งยืนยันที่จะตรวจสอบว่า newTaskEvent
ทํางาน
- รับค่า
LiveData
สําหรับnewTaskEvent
โดยใช้getOrAwaitValue
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
- ยืนยันว่าค่านั้นไม่เป็นนัล
assertThat(value.getContentIfNotHandled(), (not(nullValue())))
การทดสอบที่สมบูรณ์ควรมีลักษณะเหมือนโค้ดด้านล่าง
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()))
}
}
- เรียกใช้โค้ดและรับชมบัตรผ่านทดสอบ
ตอนนี้คุณก็ได้ดูวิธีเขียนการทดสอบแล้ว ก็สามารถเขียนขึ้นมาเองได้ ในขั้นตอนนี้ ให้ใช้ทักษะที่คุณได้เรียนรู้ แล้วลองเขียนการทดสอบ TasksViewModel
อีกครั้ง
ขั้นตอนที่ 1 เขียนการทดสอบ ViewModel ของคุณเอง
คุณจะเขียน setFilterAllTasks_tasksAddViewVisible()
การทดสอบนี้ควรตรวจสอบว่าหากคุณตั้งค่าตัวกรองประเภทให้แสดงงานทั้งหมดหรือไม่ คุณจะเห็นปุ่มเพิ่มงาน
- ด้วยการใช้
addNewTask_setsNewTaskEvent()
สําหรับการอ้างอิง โปรดเขียนการทดสอบในTasksViewModelTest
ชื่อsetFilterAllTasks_tasksAddViewVisible()
ซึ่งตั้งค่าโหมดการกรองเป็นALL_TASKS
และยืนยันว่าtasksAddViewVisible
LiveData คือtrue
ใช้รหัสด้านล่างเพื่อเริ่มต้นใช้งาน
TasksViewModelTest
@Test
fun setFilterAllTasks_tasksAddViewVisible() {
// Given a fresh ViewModel
// When the filter type is ALL_TASKS
// Then the "Add task" action is visible
}
หมายเหตุ
- การแจกแจง
TasksFilterType
สําหรับงานทั้งหมดคือALL_TASKS.
- การมองเห็นปุ่มเพิ่มงานจะควบคุมโดย
LiveData
tasksAddViewVisible.
- ทําการทดสอบ
ขั้นตอนที่ 2 เปรียบเทียบการทดสอบกับโซลูชัน
เปรียบเทียบโซลูชันของคุณกับโซลูชันด้านล่าง
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))
}
ตรวจสอบว่าคุณทําสิ่งต่อไปนี้หรือไม่
- คุณสร้าง
tasksViewModel
โดยใช้คําสั่ง AndroidXApplicationProvider.getApplicationContext()
เดียวกัน - คุณเรียกใช้เมธอด
setFiltering
ซึ่งส่งผ่าน enum ประเภทตัวกรองALL_TASKS
- คุณตรวจสอบว่า
tasksAddViewVisible
เป็น "จริง" โดยใช้เมธอดgetOrAwaitNextValue
ขั้นตอนที่ 3 เพิ่มกฎ @ก่อน
โปรดสังเกตว่าในช่วงเริ่มต้นการทดสอบทั้ง 2 รายการของคุณ คุณกําหนด TasksViewModel
ไว้
TasksViewModelTest
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
เมื่อมีรหัสการตั้งค่าซ้ําสําหรับการทดสอบหลายรายการ คุณจะใช้คําอธิบายประกอบ @before เพื่อสร้างวิธีการตั้งค่าและนําโค้ดที่ซ้ําออกได้ เนื่องจากการทดสอบทั้งหมดนี้จะทดสอบ TasksViewModel
และจําเป็นต้องใช้รูปแบบข้อมูลพร็อพเพอร์ตี้ โปรดย้ายโค้ดนี้ไปยังบล็อก @Before
- สร้างตัวแปรอินสแตนซ์
lateinit
ชื่อtasksViewModel|
- สร้างเมธอดชื่อ
setupViewModel
- เพิ่มคําอธิบายประกอบด้วย
@Before
- ย้ายรหัสอินสแตนซ์ของโมเดลข้อมูลพร็อพเพอร์ตี้ไปที่
setupViewModel
TasksViewModelTest
// Subject under test
private lateinit var tasksViewModel: TasksViewModel
@Before
fun setupViewModel() {
tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
}
- เรียกใช้โค้ดของคุณ
คำเตือน
อย่าทําสิ่งต่อไปนี้ อย่าเริ่ม
tasksViewModel
โดยมีคําจํากัดความดังต่อไปนี้
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
ซึ่งจะทําให้ระบบใช้อินสแตนซ์เดียวกันสําหรับการทดสอบทั้งหมด นี่คือสิ่งที่คุณควรหลีกเลี่ยงเนื่องจากการทดสอบแต่ละครั้งควรมีอินสแตนซ์แรกของเรื่องใต้การทดสอบ (ViewView ซึ่งในกรณีนี้)
โค้ดสุดท้ายสําหรับ TasksViewModelTest
ควรมีลักษณะเหมือนโค้ดด้านล่าง
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))
}
}
คลิกที่นี่เพื่อดูความแตกต่างระหว่างโค้ดที่คุณเริ่มและรหัสสุดท้าย
หากต้องการดาวน์โหลดโค้ดสําหรับ Codelab ที่เสร็จสิ้นแล้ว ให้ใช้คําสั่ง git ที่ด้านล่าง
$ git clone https://github.com/googlecodelabs/android-testing.git $ cd android-testing $ git checkout end_codelab_1
คุณอาจดาวน์โหลดที่เก็บเป็นไฟล์ ZIP แล้วแตกไฟล์ และเปิดใน Android Studio ได้ด้วย
Codelab นี้ครอบคลุมข้อมูลต่อไปนี้
- วิธีเรียกใช้การทดสอบจาก Android Studio
- ความแตกต่างระหว่างการทดสอบภายใน (
test
) และการทดสอบการวัดคุม (androidTest
) - วิธีเขียนการทดสอบหน่วยท้องถิ่นโดยใช้ JUnit และ Hamcrest
- การตั้งค่าการทดสอบ Viewmodel ด้วยไลบรารีการทดสอบ AndroidX
หลักสูตร Udacity:
เอกสารประกอบสําหรับนักพัฒนาซอฟต์แวร์ Android
- คําแนะนําเกี่ยวกับสถาปัตยกรรมแอป
- หน่วยการเรียนรู้ 4
- Hamcrest
- ไลบรารีการทดสอบ Robolectric
- ไลบรารีการทดสอบของ AndroidX
- ไลบรารีทดสอบหลักของคอมโพเนนต์สถาปัตยกรรม AndroidX
- ชุดแหล่งที่มา
- ทดสอบจากบรรทัดคําสั่ง
วิดีโอ:
อื่นๆ:
สําหรับลิงก์ไปยังหน้า Codelab อื่นๆ ในหลักสูตรนี้ โปรดดูหน้า Landing Page ขั้นสูงสําหรับ Android ใน Kotlin