ลักษณะการใช้งานแบบอะซิงโครนัสของแอปบนอุปกรณ์เคลื่อนที่และเฟรมเวิร์กมักทำให้การเขียนการทดสอบที่เชื่อถือได้และทำซ้ำได้ยาก เมื่อมีการแทรกเหตุการณ์ของผู้ใช้ เฟรมเวิร์กการทดสอบจะต้องรอให้แอปตอบสนองต่อเหตุการณ์นั้นๆ จนเสร็จ ซึ่งอาจมีทั้งการเปลี่ยนข้อความบางส่วนบนหน้าจอไปจนถึงการสร้างกิจกรรมขึ้นมาอีกครั้ง เมื่อการทดสอบไม่มีลักษณะการทำงานแบบกำหนดได้ แสดงว่าไม่เสถียร
เฟรมเวิร์กสมัยใหม่อย่าง Compose หรือ Espresso ได้รับการออกแบบมาโดยคำนึงถึงการทดสอบ ดังนั้นจึงมีการรับประกันว่า UI จะหยุดทำงานก่อนการดําเนินการทดสอบหรือการตรวจสอบครั้งถัดไป การดำเนินการนี้เรียกว่าการซิงค์
การซิงค์การทดสอบ
ปัญหาอาจยังคงเกิดขึ้นเมื่อคุณเรียกใช้การดำเนินการแบบไม่พร้อมกันหรือการดำเนินการเบื้องหลังที่การทดสอบไม่รู้ เช่น การโหลดข้อมูลจากฐานข้อมูลหรือการแสดงภาพเคลื่อนไหวแบบวนซ้ำ
หากต้องการเพิ่มความน่าเชื่อถือของชุดทดสอบ คุณสามารถติดตั้งวิธีติดตามการดำเนินการในเบื้องหลัง เช่น Espresso Idling Resources นอกจากนี้ คุณยังแทนที่โมดูลสําหรับเวอร์ชันการทดสอบที่คุณสามารถค้นหาสถานะไม่มีการใช้งานหรือปรับปรุงการซิงค์ได้ เช่น TestDispatcher สําหรับ Coroutine หรือ RxIdler สําหรับ RxJava
วิธีปรับปรุงความเสถียร
การทดสอบขนาดใหญ่สามารถตรวจจับการถดถอยจำนวนมากในเวลาเดียวกันเนื่องจากทดสอบองค์ประกอบหลายรายการของแอป ซึ่งมักจะทำงานในโปรแกรมจำลองหรืออุปกรณ์ซึ่งหมายความว่ามีความแม่นยำสูง แม้ว่าการทดสอบจากต้นทางถึงปลายทางขนาดใหญ่จะครอบคลุมอย่างครอบคลุม แต่ก็มีโอกาสที่จะเกิดความล้มเหลวเป็นครั้งคราว
มาตรการหลักที่คุณสามารถใช้เพื่อลดความไม่สม่ำเสมอมีดังต่อไปนี้
- กำหนดค่าอุปกรณ์อย่างถูกต้อง
- ป้องกันปัญหาการซิงค์ข้อมูล
- ใช้งานการลองอีกครั้ง
หากต้องการสร้างการทดสอบขนาดใหญ่โดยใช้ Compose หรือ Espresso โดยทั่วไปคุณจะต้องเริ่มกิจกรรมใดกิจกรรมหนึ่งและไปยังส่วนต่างๆ ตามที่ผู้ใช้ทำ ตรวจสอบว่า UI ทํางานอย่างถูกต้องโดยใช้การยืนยันหรือการทดสอบภาพหน้าจอ
เฟรมเวิร์กอื่นๆ เช่น UI Automator ช่วยให้มีขอบเขตการใช้งานที่ใหญ่ขึ้น เนื่องจากคุณโต้ตอบกับ UI ของระบบและแอปอื่นๆ ได้ อย่างไรก็ตาม การทดสอบ UI ด้วยตัวดำเนินการอัตโนมัติอาจต้องมีการซิงค์ด้วยตนเองมากขึ้น จึงมีแนวโน้มที่จะเชื่อถือได้น้อยลง
กำหนดค่าอุปกรณ์
ก่อนอื่น คุณควรตรวจสอบว่าระบบปฏิบัติการของอุปกรณ์ไม่ขัดจังหวะการดําเนินการทดสอบโดยไม่คาดคิด เพื่อปรับปรุงความน่าเชื่อถือของการทดสอบ เช่น เมื่อกล่องโต้ตอบการอัปเดตระบบแสดงอยู่เหนือแอปอื่นๆ หรือเมื่อพื้นที่ในดิสก์ไม่เพียงพอ
ผู้ให้บริการฟาร์มอุปกรณ์จะกำหนดค่าอุปกรณ์และโปรแกรมจำลอง ดังนั้นคุณจึงไม่จำเป็นต้องดำเนินการใดๆ อย่างไรก็ตาม อาจมีคำสั่งการกำหนดค่าของตนเองสำหรับกรณีพิเศษ
อุปกรณ์ที่มีการจัดการโดย Gradle
หากจัดการโปรแกรมจำลองด้วยตนเอง คุณสามารถใช้อุปกรณ์ที่จัดการโดย Gradle เพื่อกำหนดอุปกรณ์ที่จะใช้ทำการทดสอบได้ ดังนี้
android {
testOptions {
managedDevices {
localDevices {
create("pixel2api30") {
// Use device profiles you typically see in Android Studio.
device = "Pixel 2"
// Use only API levels 27 and higher.
apiLevel = 30
// To include Google services, use "google".
systemImageSource = "aosp"
}
}
}
}
}
เมื่อใช้การกำหนดค่านี้ คำสั่งต่อไปนี้จะสร้างอิมเมจโปรแกรมจำลอง เริ่มอินสแตนซ์ เรียกใช้การทดสอบ และปิดการทำงาน
./gradlew pixel2api30DebugAndroidTest
อุปกรณ์ที่จัดการโดย Gradle มีกลไกในการลองอีกครั้งในกรณีที่อุปกรณ์ตัดการเชื่อมต่อและมีการปรับปรุงอื่นๆ
ป้องกันปัญหาการซิงค์ข้อมูล
คอมโพเนนต์ที่ดำเนินการแบบแบ็กกราวด์หรือแบบไม่สอดคล้องกันอาจทําให้ทดสอบไม่สําเร็จเนื่องจากมีการเรียกใช้คำสั่งทดสอบก่อนที่ UI จะพร้อมใช้งาน เมื่อขอบเขตการทดสอบกว้างขึ้น โอกาสที่จะเกิดความไม่เสถียรก็ยิ่งมากขึ้น ปัญหาการซิงค์เหล่านี้เป็นแหล่งที่มาหลักของความไม่เสถียร เนื่องจากเฟรมเวิร์กทดสอบต้องอนุมานว่ากิจกรรมโหลดเสร็จแล้วหรือควรรออีก
โซลูชัน
คุณสามารถใช้ทรัพยากรที่ไม่ได้ใช้งานของ Espresso เพื่อระบุว่าแอปไม่ว่าง แต่การติดตามการดำเนินการแบบไม่พร้อมกันทุกรายการนั้นทำได้ยาก โดยเฉพาะในการทดสอบจากต้นทางถึงปลายทางขนาดใหญ่ นอกจากนี้ การติดตั้งทรัพยากรที่ไม่ได้ใช้งานยังทำได้ยากโดยไม่ทำให้โค้ดที่ทดสอบปนเปื้อน
คุณสามารถทําให้การทดสอบรอจนกว่าจะมีเงื่อนไขที่เฉพาะเจาะจงตรงตามที่ต้องการแทนที่จะประมาณว่ากิจกรรมมีการใช้งานหรือไม่ เช่น คุณสามารถรอจนกว่าข้อความหรือคอมโพเนนต์ที่เฉพาะเจาะจงจะแสดงใน UI
Compose มีคอลเล็กชัน API การทดสอบเป็นส่วนหนึ่งของ ComposeTestRule
เพื่อรอการจับคู่ที่แตกต่างกัน ดังนี้
fun waitUntilAtLeastOneExists(matcher: SemanticsMatcher, timeout: Long = 1000L)
fun waitUntilDoesNotExist(matcher: SemanticsMatcher, timeout: Long = 1000L)
fun waitUntilExactlyOneExists(matcher: SemanticsMatcher, timeout: Long = 1000L)
fun waitUntilNodeCount(matcher: SemanticsMatcher, count: Int, timeout: Long = 1000L)
และ API ทั่วไปที่ใช้ฟังก์ชันใดก็ได้ที่แสดงผลบูลีน
fun waitUntil(timeoutMillis: Long, condition: () -> Boolean): Unit
ตัวอย่างการใช้:
composeTestRule.waitUntilExactlyOneExists(hasText("Continue")</code>)</p></td>
กลไกการลองใหม่
คุณควรแก้ไขการทดสอบที่ไม่น่าเชื่อถือ แต่บางครั้งเงื่อนไขที่ทำให้การทดสอบล้มเหลวก็เกิดขึ้นได้ยากมากจนทำให้ทำซ้ำได้ยาก แม้ว่าคุณควรติดตามและแก้ไขการทดสอบที่ไม่เสถียรอยู่เสมอ แต่กลไกการลองใหม่จะช่วยรักษาประสิทธิภาพการทำงานของนักพัฒนาแอปไว้ได้ด้วยการเรียกใช้การทดสอบหลายครั้งจนกว่าจะผ่าน
การลองอีกครั้งต้องเกิดขึ้นหลายระดับเพื่อป้องกันปัญหา เช่น
- การเชื่อมต่อกับอุปกรณ์หมดเวลาหรือตัดการเชื่อมต่อ
- การทดสอบครั้งเดียวไม่สำเร็จ
การติดตั้งหรือการกำหนดค่าการลองใหม่ขึ้นอยู่กับเฟรมเวิร์กและโครงสร้างพื้นฐานของการทดสอบ แต่กลไกทั่วไปมีดังนี้
- กฎ JUnit ที่พยายามทดสอบซ้ำหลายครั้ง
- การดำเนินการหรือขั้นตอนที่ลองใหม่ในเวิร์กโฟลว์ CI
- ระบบรีสตาร์ทโปรแกรมจำลองเมื่อไม่ตอบสนอง เช่น อุปกรณ์ที่จัดการโดย Gradle