Media3 มีอาร์ติแฟกต์การทดสอบเฉพาะ (media3-test-utils และ media3-test-utils-robolectric) เพื่อช่วยคุณทดสอบการเล่นสื่อ การเปลี่ยนสถานะเพลเยอร์ คอมโพเนนต์ UI และแหล่งที่มาของสื่อที่กำหนดเองทั้งในการทดสอบ JVM ในเครื่องและการทดสอบการวัดคุมอุปกรณ์
หน้านี้จะสรุปแนวทางปฏิบัติที่แนะนำสำหรับการทดสอบแอปที่ใช้ ExoPlayer และแสดงวิธีใช้ยูทิลิตีการทดสอบของ Media3 ในสภาพแวดล้อมการทดสอบแต่ละรายการ
คำแนะนำในการทดสอบ
เมื่อเขียนการทดสอบอัตโนมัติสำหรับการเล่นสื่อ ให้ปฏิบัติตามหลักการสำคัญต่อไปนี้
- ใช้คอมโพเนนต์จริงหรือออบเจ็กต์จำลอง Media3 แทนออบเจ็กต์จำลอง: หลีกเลี่ยงการจำลอง
PlayerหรือExoPlayerด้วยเฟรมเวิร์ก เช่น MockitoPlayerมีค่าคงที่ของสถานะและสัญญา Listener ที่การจำลองไม่บังคับใช้ ซึ่งอาจทำให้การทดสอบไม่เสถียรหรือซ่อนข้อบกพร่องไว้ แต่ให้ใช้อินสแตนซ์ExoPlayerจริง,FakePlayerหรือของปลอม เช่นFakeMediaSourceและFakeDataSourceแทน สำรองออบเจ็กต์จำลองไว้เพื่อยืนยันการเรียกใช้ Callback ที่กำหนดเองเมื่อจำเป็น - รออย่างมีเงื่อนไขแทนที่จะหยุดทำงานชั่วคราว:
ExoPlayerจะโหลดและเล่นสื่อแบบไม่พร้อมกันในชุดข้อความภายใน หลีกเลี่ยงการเรียกใช้Thread.sleepโดยไม่มีเหตุผล ซึ่งจะทำให้การทดสอบช้าและไม่เสถียร ใช้TestPlayerRunHelperในการทดสอบ Robolectric และระงับส่วนขยายPlayerหรือPlayerFenceในการทดสอบการวัดคุม ตัวช่วยเหล่านี้จะรอเงื่อนไขเพลเยอร์ที่ชัดเจนและจะดำเนินการไม่สำเร็จทันทีหากเกิดข้อผิดพลาดในการเล่น - เข้าถึงโปรแกรมเล่นในชุดข้อความของแอปพลิเคชัน: ต้องเรียกใช้อินสแตนซ์
ExoPlayerและตัวช่วยที่รอในชุดข้อความLooperของแอปพลิเคชันของโปรแกรมเล่น (โดยปกติจะเป็นชุดข้อความหลัก) ดูข้อมูลเพิ่มเติมได้ที่ข้อกำหนดเกี่ยวกับชุดข้อความของ ExoPlayer
เลือกสภาพแวดล้อมการทดสอบที่เหมาะสม
การผสานรวมสื่อในเลเยอร์ต่างๆ จะได้รับประโยชน์จากสภาพแวดล้อมการทดสอบที่แตกต่างกัน ดังนี้
| เป้าหมายการทดสอบ | สภาพแวดล้อมที่แนะนำ | ยูทิลิตี Media3 ที่สำคัญ |
|---|---|---|
คอมโพเนนต์ UI (PlayerView, Compose UI), ViewModel หรือ Callback ของ MediaSession |
การทดสอบ Robolectric ในเครื่อง (src/test) หรือการทดสอบ UI ที่ใช้เครื่องมือ (src/androidTest) |
FakePlayer |
การผสานรวม ExoPlayer, การเปลี่ยนเพลย์ลิสต์, ข้อมูลวิเคราะห์ และตรรกะ MediaSource หรือ DataSource ที่กำหนดเอง |
การทดสอบ Robolectric ในเครื่อง (src/test) |
TestExoPlayerBuilder, TestPlayerRunHelper, FakeMediaSource |
การถอดรหัส MediaCodec, การแสดงผล Surface, เอาต์พุตเสียง และ DRM ของฮาร์ดแวร์จริง |
การทดสอบการวัดคุมในอุปกรณ์จริง (src/androidTest) |
ExoPlayer.Builder, Player.await* ส่วนขยาย, PlayerFence |
หากเป็นไปได้ ให้เรียกใช้การทดสอบการเล่นแบบใช้เครื่องมือวัดในอุปกรณ์จริงแทนโปรแกรมจำลอง เนื่องจากการติดตั้งใช้งานสแต็กสื่อของโปรแกรมจำลองจะแตกต่างกันไป ดูข้อมูลเพิ่มเติมเกี่ยวกับการรองรับโปรแกรมจำลองได้ที่คู่มืออุปกรณ์ที่รองรับ
เพิ่มทรัพยากร Dependency สำหรับการทดสอบ
เพิ่มอาร์ติแฟกต์การทดสอบ Media3 ลงในบล็อก dependencies ของไฟล์ build.gradle.kts หรือ build.gradle ของโมดูลโดยใช้เวอร์ชันเดียวกับโมดูล Media3 อื่นๆ ดังนี้
Kotlin
testImplementation("androidx.media3:media3-test-utils:1.11.1")
testImplementation("androidx.media3:media3-test-utils-robolectric:1.11.1")
androidTestImplementation("androidx.media3:media3-test-utils:1.11.1")
ดึงดูด
testImplementation "androidx.media3:media3-test-utils:1.11.1"
testImplementation "androidx.media3:media3-test-utils-robolectric:1.11.1"
androidTestImplementation "androidx.media3:media3-test-utils:1.11.1"
ทั้ง 2 โมดูลจะแบ่งความรับผิดชอบตามสภาพแวดล้อมรันไทม์ ดังนี้
media3-test-utils: ยูทิลิตีข้ามสภาพแวดล้อมสำหรับการทดสอบทั้งในเครื่องและการทดสอบการวัดคุม ซึ่งรวมถึงTestExoPlayerBuilder,PlayerFence(และส่วนขยาย KotlinPlayer.await*),FakePlayer,FakeClock,FakeMediaSource,FakeDataSource,DataSourceContractTestและMedia3FlagsRulemedia3-test-utils-robolectric: ยูทิลิตีสำหรับการทดสอบ Robolectric ใน JVM ในเครื่อง ซึ่งรวมถึงTestPlayerRunHelper,RobolectricUtil,ShadowMediaCodecConfig,CapturingRenderersFactoryและPlaybackOutput
ตรวจสอบว่าโมดูลของคุณมีการขึ้นต่อกันของเฟรมเวิร์กการทดสอบสำหรับสภาพแวดล้อมการทดสอบด้วย ดังนี้
- Robolectric:
org.robolectric:robolectricตั้งค่าandroid.testOptions.unitTests.isIncludeAndroidResources = trueเมื่อโหลดชิ้นงานในเครื่อง - AndroidX Test:
androidx.test:runner,androidx.test:coreและandroidx.test.ext:junit - โครูทีน Kotlin ในเทรดหลัก:
org.jetbrains.kotlinx:kotlinx-coroutines-android
เลือกใช้ API การทดสอบที่ไม่เสถียร
คลาสและเมธอดใน media3-test-utils และ media3-test-utils-robolectric จะมีคำอธิบายประกอบด้วย @UnstableApi แม้ว่า Media3 จะมุ่งเน้นที่การรักษาตัวช่วยทดสอบที่ใช้กันอย่างแพร่หลายให้ใช้งานง่ายในทุกรุ่น แต่ยูทิลิตีการทดสอบไม่ได้เป็นไปตามกฎความเสถียรของความเข้ากันได้แบบไบนารีเช่นเดียวกับ API การเล่นเวอร์ชันที่ใช้งานจริง
ในชุดซอร์สทดสอบ ให้ใส่คำอธิบายประกอบคลาสหรือเมธอดทดสอบด้วย @androidx.annotation.OptIn, เพิ่มการเลือกใช้ package-info สำหรับแพ็กเกจทดสอบ หรือกำหนดค่า lint.xml สำหรับซอร์สทดสอบ ดูข้อมูลเพิ่มเติมได้ที่หมายเหตุการแก้ปัญหา Lint ของ API ที่ไม่เสถียร
การทดสอบในเครื่องด้วย Robolectric
การทดสอบ Robolectric ในเครื่องจะทำงานบน JVM ของเครื่องสำหรับการพัฒนาซอฟต์แวร์โดยไม่มีอุปกรณ์จริงหรือโปรแกรมจำลอง Robolectric ไม่มีการติดตั้งใช้งาน MediaCodec และจะหยุด Looper หลักไว้ชั่วคราวจนกว่าจะมีการปั๊มอย่างชัดเจน ดังนั้นอินสแตนซ์ ExoPlayer เริ่มต้นจึงไม่สามารถถอดรหัสสื่อหรือส่งการอัปเดตสถานะด้วยตัวเองได้
สร้างเพลเยอร์ทดสอบและเล่นขั้นสูง
ใช้ TestExoPlayerBuilder เพื่อสร้าง ExoPlayer ที่กำหนดค่าสำหรับการทดสอบ Robolectric โดยค่าเริ่มต้น TestExoPlayerBuilder จะกำหนดค่าคอมโพเนนต์ต่อไปนี้
FakeClock(isAutoAdvancing = true) ที่เลื่อนอัตโนมัติซึ่งประสานความคืบหน้าของเวลาระหว่างเทรดการเล่นในเบื้องหลังของExoPlayerกับLooperหลักที่หยุดชั่วคราวของ Robolectric- โปรแกรมแสดงผลวิดีโอและเสียงจำลอง (
FakeVideoRendererและFakeAudioRenderer) ที่ใช้ตัวอย่างสื่อตามการประทับเวลาโดยไม่ต้องใช้ตัวถอดรหัสMediaCodec
หากต้องการเล่นต่อและยืนยันผลลัพธ์ในเทรดหลัก (โดยใช้โปรแกรมเล่นที่ใช้ Looper.getMainLooper ซึ่งเป็นค่าเริ่มต้นใน Robolectric) ให้ใช้เมธอด advance(player) หรือ play(player) แบบคงที่ใน TestPlayerRunHelper (play(player) จะเรียก player.play ก่อนรอ) สำหรับเงื่อนไขส่วนใหญ่ ตัวช่วยจะปั๊ม Looper หลักของ Robolectric จนกว่าจะเป็นไปตามเงื่อนไขเป้าหมาย และจะแสดงข้อยกเว้นหากเกิดข้อผิดพลาดร้ายแรงหรือไม่ร้ายแรงในการเล่น
Kotlin
@RunWith(AndroidJUnit4::class) @OptIn(UnstableApi::class) class PlaybackRobolectricTest { private lateinit var player: ExoPlayer @After fun tearDown() { if (::player.isInitialized) { player.release() } } @Test fun play_sampleMediaItem_reachesEndedState() { val context = ApplicationProvider.getApplicationContext<Context>() player = TestExoPlayerBuilder(context).build() player.setMediaItem(MediaItem.fromUri("asset:///media/mp4/sample.mp4")) player.prepare() play(player).untilState(Player.STATE_ENDED) assertThat(player.playbackState).isEqualTo(Player.STATE_ENDED) } }
Java
@RunWith(AndroidJUnit4.class) @OptIn(markerClass = UnstableApi.class) public static class PlaybackRobolectricTest { private @Nullable ExoPlayer player; @After public void tearDown() { if (player != null) { player.release(); } } @Test public void play_sampleMediaItem_reachesEndedState() throws Exception { Context context = ApplicationProvider.getApplicationContext(); player = new TestExoPlayerBuilder(context).build(); player.setMediaItem(MediaItem.fromUri("asset:///media/mp4/sample.mp4")); player.prepare(); play(player).untilState(Player.STATE_ENDED); assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_ENDED); } }
ในตัวอย่างนี้ asset:///media/mp4/sample.mp4 หมายถึงไฟล์สื่อตัวอย่างที่อยู่ในไดเรกทอรี src/test/assets/ (หรือ src/main/assets/) ของโมดูล
เมื่อส่งผ่านการอ้างอิง ExoPlayer, advance(player) และ play(player) จะแสดงผล ExoPlayerRunResult (หรือ PlayerRunResult เมื่อส่งผ่าน Player) ประเภทผลลัพธ์เหล่านี้มีเมธอดสำหรับการรอสถานะการเล่น ตำแหน่ง การเปลี่ยนเพลย์ลิสต์ เฟรมที่แสดงผล คำสั่งที่รอดำเนินการ และข้อผิดพลาดในการเล่นที่คาดไว้ หรือการละเว้นข้อผิดพลาดที่ไม่ร้ายแรง ดูข้อมูลเพิ่มเติมเกี่ยวกับเงื่อนไขการรอได้ที่เอกสารอ้างอิง API ของ TestPlayerRunHelper
ทดสอบด้วยแหล่งที่มาจำลองและตัวแปลงรหัสจำลอง
การทดสอบการผสานรวมหลายรายการจะยืนยันการไปยังส่วนต่างๆ ของเพลย์ลิสต์ การเลือกแทร็ก การอัปเดตไทม์ไลน์ หรือลักษณะการทำงานของการลองใหม่ และไม่จำเป็นต้องใช้ไฟล์สื่อจริง คุณสามารถป้อนไทม์ไลน์และข้อมูลสังเคราะห์ลงในเพลเยอร์ TestExoPlayerBuilder โดยใช้ FakeMediaSource, FakeTimeline, FakeDataSource หรือ WebServerDispatcher (พร้อม OkHttp MockWebServer) ได้ดังนี้
Kotlin
val player = TestExoPlayerBuilder(context).build() val fakeMediaSource = FakeMediaSource(FakeTimeline(/* windowCount= */ 2)) player.setMediaSource(fakeMediaSource) player.prepare() play(player).untilMediaItemIndex(/* mediaItemIndex= */ 1) assertThat(player.currentMediaItemIndex).isEqualTo(1) player.release()
Java
ExoPlayer player = new TestExoPlayerBuilder(context).build(); FakeMediaSource fakeMediaSource = new FakeMediaSource(new FakeTimeline(/* windowCount= */ 2)); player.setMediaSource(fakeMediaSource); player.prepare(); play(player).untilMediaItemIndex(/* mediaItemIndex= */ 1); assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); player.release();
TestExoPlayerBuilder ใช้ตัวแสดงผลปลอมโดยค่าเริ่มต้น หากการทดสอบ Robolectric ของคุณต้องการการโต้ตอบ MediaCodec แทน ให้กำหนดค่าตัวแปลงรหัสจำลองและการจับภาพเอาต์พุตดังนี้
- เพิ่ม
withAllDefaultSupportedCodecsจากShadowMediaCodecConfigเป็น JUnit@Ruleเพื่อลงทะเบียนตัวแปลงรหัสจำลองของ Robolectric - ส่ง
CapturingRenderersFactoryที่สร้างด้วยClock(CapturingRenderersFactory(context, builder.getClock())) ของเครื่องมือสร้างไปยังbuilder.setRenderersFactory - เมื่อทดสอบแทร็กวิดีโอ ให้แนบ
Surface(เช่นSurface(SurfaceTexture(1))) กับplayer.setVideoSurfaceเพื่อให้MediaCodecVideoRendererแสดงผลเฟรมที่ถอดรหัสแทนที่จะข้ามเฟรมเหล่านั้น - หากต้องการยืนยันเอาต์พุตที่แสดงผล ให้เรียกใช้
register(player, capturingRenderersFactory)ในPlaybackOutputก่อนเริ่มเล่น - หลังจากเล่นเสร็จแล้ว ให้ปล่อยเพลเยอร์และ Surface จากนั้นตรวจสอบเอาต์พุตที่จัดรูปแบบแล้วจาก
Dumper(Dumper().add(playbackOutput).toString())
การทดสอบการวัดคุมในอุปกรณ์
การทดสอบ Robolectric ในเครื่องจะไม่เรียกใช้ตัวถอดรหัสแพลตฟอร์ม แทร็กเสียง เซสชัน DRM หรือการแสดงผล Surface จริง หากต้องการยืนยันลักษณะการทำงานของการเล่นตั้งแต่ต้นจนจบในอุปกรณ์ที่ใช้ Android ให้เขียนการทดสอบการวัดคุมใน src/androidTest โดยใช้ ExoPlayer จริงที่สร้างด้วย ExoPlayer.Builder
ในการทดสอบการวัดคุม JUnit เมธอด @Test จะทำงานในเธรดการวัดคุมแทนที่จะเป็นเธรดหลักของแอปพลิเคชัน สร้าง ควบคุม และปล่อย ExoPlayer ในเธรด Looper ของแอปพลิเคชันเสมอ เช่น โดยใช้ runBlocking(Dispatchers.Main) ใน Kotlin หรือ InstrumentationRegistry.getInstrumentation().runOnMainSync ใน Java
รอสถานะเพลเยอร์ด้วย PlayerFence และโครูทีน
ในการทดสอบการวัดคุม Looper หลักจะทำงานแบบเรียลไทม์บนอุปกรณ์
อย่ารวม Robolectric หรือ media3-test-utils-robolectric ไว้ในทรัพยากร Dependency ของ androidTestImplementation หากต้องการรอสถานะและเหตุการณ์การเล่น media3-test-utils จะมีตัวช่วยต่อไปนี้
- Kotlin: ฟังก์ชันส่วนขยายที่ระงับใน
Player(เช่นawaitPlaybackState,awaitIsPlaying,awaitFirstFrameRenderedและawaitContentPositionAtLeast) - Java: เมธอด
futureWhen(player)แบบคงที่ในPlayerFenceซึ่งแสดงผลPlayerFenceที่ต้องเรียกใช้เมธอดเงื่อนไข (เช่นentersPlaybackStateและisPlaying) ในเธรดLooperของเพลเยอร์ และแสดงผลListenableFutureที่เธรดทดสอบสามารถบล็อกด้วยgetได้
API ทั้ง 2 รายการจะควบคุมดูแลเพลเยอร์ขณะรอ หากเกิดข้อผิดพลาดร้ายแรงในการเล่น (หรือข้อผิดพลาดที่ไม่ร้ายแรงโดยค่าเริ่มต้น) หรือหากการรอเกินระยะหมดเวลา (10 วินาทีโดยค่าเริ่มต้น หรือ 30 วินาทีในโปรแกรมจำลอง) การเรียกจะล้มเหลวทันทีแทนที่จะค้าง
Kotlin
@RunWith(AndroidJUnit4::class) @OptIn(UnstableApi::class) class PlaybackInstrumentationTest { private lateinit var player: ExoPlayer @After fun tearDown() = runBlocking(Dispatchers.Main) { if (::player.isInitialized) { player.release() } } @Test fun play_sampleMediaItem_advancesAndEnds() = runBlocking(Dispatchers.Main) { val context = ApplicationProvider.getApplicationContext<Context>() player = ExoPlayer.Builder(context).build() player.setMediaItem(MediaItem.fromUri("asset:///media/mp4/sample.mp4")) player.prepare() player.play() player.awaitIsPlaying(true) player.awaitContentPositionAtLeast(500) player.awaitPlaybackState(Player.STATE_ENDED) assertThat(player.playbackState).isEqualTo(Player.STATE_ENDED) } }
Java
@RunWith(AndroidJUnit4.class) @OptIn(markerClass = UnstableApi.class) public static class PlaybackInstrumentationTest { private @Nullable ExoPlayer player; @After public void tearDown() { InstrumentationRegistry.getInstrumentation() .runOnMainSync(() -> { if (player != null) { player.release(); } }); } @Test public void play_sampleMediaItem_advancesAndEnds() throws Exception { SettableFuture<Void> playbackStarted = SettableFuture.create(); SettableFuture<Void> positionPassed = SettableFuture.create(); SettableFuture<Void> playbackEnded = SettableFuture.create(); InstrumentationRegistry.getInstrumentation() .runOnMainSync(() -> { Context context = ApplicationProvider.getApplicationContext(); player = new ExoPlayer.Builder(context).build(); player.setMediaItem(MediaItem.fromUri("asset:///media/mp4/sample.mp4")); playbackStarted.setFuture(futureWhen(player).isPlaying(true)); positionPassed.setFuture(futureWhen(player).passesContentPosition(500)); playbackEnded.setFuture(futureWhen(player).entersPlaybackState(Player.STATE_ENDED)); player.prepare(); player.play(); }); playbackStarted.get(); positionPassed.get(); playbackEnded.get(); InstrumentationRegistry.getInstrumentation() .runOnMainSync(() -> assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_ENDED)); } }
เมื่อสร้าง ExoPlayer ด้วย ApplicationProvider.getApplicationContext ในโมดูลแอป (com.android.application) URI asset:/// จะแก้ไขตามเนื้อหาของแอปเป้าหมาย (เช่น src/debug/assets/ หรือ src/main/assets/ แทนที่จะเป็น src/androidTest/assets/)
เหตุการณ์ชั่วคราวและการควบคุมดูแลที่กำหนดเอง
นอกเหนือจากการตรวจสอบสถานะผู้เล่นปัจจุบัน (ซึ่งการรอจะแสดงผลทันทีหากเป็นไปตามเงื่อนไขแล้ว) ตัวช่วยใน media3-test-utils ยังรองรับเหตุการณ์แบบครั้งเดียวและเงื่อนไข Listener ที่กำหนดเองด้วย ดังนี้
- เหตุการณ์ชั่วคราว เช่น เฟรมแรกที่แสดงผล: แนบเอาต์พุตวิดีโอ (เช่น
SurfaceหรือPlayerView) กับเพลเยอร์ แล้วเริ่มรอด้วยawaitFirstFrameRenderedหรือrendersFirstFrameก่อนเรียกใช้player.prepareเพื่อไม่ให้พลาดเหตุการณ์เฟรมแบบครั้งเดียวที่เกิดขึ้นก่อนเวลาอันควร - เงื่อนไขที่กำหนดเอง: รวม Listener ที่กำหนดเองหรือ Coroutine ที่รออยู่ภายใน
awaitSupervisedในplayerใน Kotlin หรือส่งListenableFutureที่กำหนดเองไปยังfutureWhen(player).withSupervision(future)ใน Java เมื่อทดสอบการกู้คืนจากข้อผิดพลาดที่ไม่ร้ายแรง ให้ส่งfailOnNonFatalErrors = false(Kotlin) หรือเรียกใช้ignoringNonFatalErrors(Java) และปรับแต่งระยะหมดเวลาด้วยtimeoutหรือwithTimeoutMs(timeoutMs)
ตัวอย่างต่อไปนี้จะรอเหตุการณ์เฟรมแรกชั่วคราวและควบคุมดูแล Listener ข้อมูลเมตาที่กำหนดเอง
Kotlin
// Start awaiting transient events (with a video surface attached) before prepare(). coroutineScope { val firstFrameDeferred = async(start = CoroutineStart.UNDISPATCHED) { player.awaitFirstFrameRendered() } player.setMediaItem(mediaItem) player.prepare() player.play() firstFrameDeferred.await() } // Supervise a custom listener condition while still failing fast on player errors. val metadataTitle = player.awaitSupervised { player.mediaMetadata.title?.let { return@awaitSupervised it } val titleDeferred = CompletableDeferred<CharSequence>() val listener = object : Player.Listener { override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) { mediaMetadata.title?.let { titleDeferred.complete(it) } } } player.addListener(listener) try { titleDeferred.await() } finally { player.removeListener(listener) } }
Java
SettableFuture<Void> firstFrameFuture = SettableFuture.create(); SettableFuture<CharSequence> metadataTitleFuture = SettableFuture.create(); SettableFuture<CharSequence> supervisedTitleFuture = SettableFuture.create(); Player.Listener metadataListener = new Player.Listener() { @Override public void onMediaMetadataChanged(MediaMetadata mediaMetadata) { if (mediaMetadata.title != null) { metadataTitleFuture.set(mediaMetadata.title); } } }; try { InstrumentationRegistry.getInstrumentation() .runOnMainSync(() -> { // Register transient event fences (with a video surface attached) before prepare(). firstFrameFuture.setFuture(futureWhen(player).rendersFirstFrame()); // Supervise a custom ListenableFuture so player errors fail fast. player.addListener(metadataListener); supervisedTitleFuture.setFuture( futureWhen(player).withSupervision(metadataTitleFuture)); player.setMediaItem(mediaItem); if (player.getMediaMetadata().title != null) { metadataTitleFuture.set(player.getMediaMetadata().title); } player.prepare(); player.play(); }); firstFrameFuture.get(); CharSequence title = supervisedTitleFuture.get(); } finally { InstrumentationRegistry.getInstrumentation() .runOnMainSync(() -> { player.removeListener(metadataListener); firstFrameFuture.cancel(/* mayInterruptIfRunning= */ true); supervisedTitleFuture.cancel(/* mayInterruptIfRunning= */ true); }); }
ทดสอบคอมโพเนนต์ UI และแอปด้วย FakePlayer
เมื่อทดสอบคอมโพเนนต์ UI เช่น คอมโพสิเบิล PlayerView หรือ media3-ui-compose, ViewModel หรือ Callback MediaSession การสร้าง ExoPlayer แบบเต็มมักไม่จำเป็น FakePlayer ใน media3-test-utils ขยาย SimpleBasePlayer เพื่อจำลองเพลย์ลิสต์ สถานะการเล่น ตำแหน่ง ขนาดวิดีโอ คิว และข้อผิดพลาดใน Looper ของชุดข้อความที่เรียกใช้ Looper นี้จะพร้อมใช้งานโดยอัตโนมัติในการทดสอบ Robolectric ด้วย @RunWith(AndroidJUnit4::class) และในเทรดหลักในการทดสอบการวัดคุม
โดยค่าเริ่มต้น FakePlayer จะจำลองความล่าช้าในการบัฟเฟอร์ 100 มิลลิวินาทีใน prepare และ seekTo ส่ง bufferingDelayMs = 0 ใน Kotlin หรือตั้งค่าสถานะโดยตรงด้วย setPlaybackState
Kotlin
val fakePlayer = FakePlayer(bufferingDelayMs = 0) fakePlayer.setMediaItem(MediaItem.fromUri("https://example.com/media.mp4")) fakePlayer.prepare() fakePlayer.play() assertThat(fakePlayer.isPlaying).isTrue() // Drive state transitions directly to test UI or session listeners. fakePlayer.setPosition(30_000) fakePlayer.setPlaybackState(Player.STATE_ENDED) fakePlayer.release()
Java
FakePlayer fakePlayer = new FakePlayer(); fakePlayer.setMediaItem(MediaItem.fromUri("https://example.com/media.mp4")); // Drive state transitions directly (prepare() simulates a 100 ms buffering delay by default). fakePlayer.setPlaybackState(Player.STATE_READY); fakePlayer.play(); assertThat(fakePlayer.isPlaying()).isTrue(); fakePlayer.setPosition(30_000); fakePlayer.setPlaybackState(Player.STATE_ENDED); fakePlayer.release();
ยูทิลิตีการทดสอบเพิ่มเติม
media3-test-utils มีตัวช่วยเฉพาะสำหรับสถานการณ์การทดสอบอื่นๆ ดังนี้
- แฟล็กฟีเจอร์และโหมด Canary: ใช้
Media3FlagsRuleร่วมกับ@BindFlagและ@BindCanaryModeเพื่อรีเซ็ตการลบล้างแฟล็กหลังจากการทดสอบแต่ละครั้ง และเรียกใช้การทดสอบที่มีพารามิเตอร์ในสถานะแฟล็กต่างๆ ดูข้อมูลเพิ่มเติมได้ที่การทดสอบด้วยแฟล็กฟีเจอร์ - การทดสอบสัญญา
DataSourceที่กำหนดเอง: คลาสย่อยDataSourceContractTestเพื่อยืนยันว่าการติดตั้งใช้งานDataSourceหรือHttpDataSourceที่กำหนดเองเป็นไปตามความคาดหวังของสัญญาของ Media3 ในคำขอช่วง การจัดการข้อผิดพลาด และ Listener การโอน ดูข้อมูลเพิ่มเติมได้ที่การผสานรวมสแต็กเครือข่ายอื่นๆ