การทดสอบ

Media3 มีอาร์ติแฟกต์การทดสอบเฉพาะ (media3-test-utils และ media3-test-utils-robolectric) เพื่อช่วยคุณทดสอบการเล่นสื่อ การเปลี่ยนสถานะเพลเยอร์ คอมโพเนนต์ UI และแหล่งที่มาของสื่อที่กำหนดเองทั้งในการทดสอบ JVM ในเครื่องและการทดสอบการวัดคุมอุปกรณ์

หน้านี้จะสรุปแนวทางปฏิบัติที่แนะนำสำหรับการทดสอบแอปที่ใช้ ExoPlayer และแสดงวิธีใช้ยูทิลิตีการทดสอบของ Media3 ในสภาพแวดล้อมการทดสอบแต่ละรายการ

คำแนะนำในการทดสอบ

เมื่อเขียนการทดสอบอัตโนมัติสำหรับการเล่นสื่อ ให้ปฏิบัติตามหลักการสำคัญต่อไปนี้

  • ใช้คอมโพเนนต์จริงหรือออบเจ็กต์จำลอง Media3 แทนออบเจ็กต์จำลอง: หลีกเลี่ยงการจำลอง Player หรือ ExoPlayer ด้วยเฟรมเวิร์ก เช่น Mockito Player มีค่าคงที่ของสถานะและสัญญา 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 โมดูลจะแบ่งความรับผิดชอบตามสภาพแวดล้อมรันไทม์ ดังนี้

ตรวจสอบว่าโมดูลของคุณมีการขึ้นต่อกันของเฟรมเวิร์กการทดสอบสำหรับสภาพแวดล้อมการทดสอบด้วย ดังนี้

  • 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 แทน ให้กำหนดค่าตัวแปลงรหัสจำลองและการจับภาพเอาต์พุตดังนี้

  1. เพิ่ม withAllDefaultSupportedCodecs จาก ShadowMediaCodecConfig เป็น JUnit @Rule เพื่อลงทะเบียนตัวแปลงรหัสจำลองของ Robolectric
  2. ส่ง CapturingRenderersFactory ที่สร้างด้วย Clock (CapturingRenderersFactory(context, builder.getClock())) ของเครื่องมือสร้างไปยัง builder.setRenderersFactory
  3. เมื่อทดสอบแทร็กวิดีโอ ให้แนบ Surface (เช่น Surface(SurfaceTexture(1))) กับ player.setVideoSurface เพื่อให้ MediaCodecVideoRenderer แสดงผลเฟรมที่ถอดรหัสแทนที่จะข้ามเฟรมเหล่านั้น
  4. หากต้องการยืนยันเอาต์พุตที่แสดงผล ให้เรียกใช้ register(player, capturingRenderersFactory) ใน PlaybackOutput ก่อนเริ่มเล่น
  5. หลังจากเล่นเสร็จแล้ว ให้ปล่อยเพลเยอร์และ 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 การโอน ดูข้อมูลเพิ่มเติมได้ที่การผสานรวมสแต็กเครือข่ายอื่นๆ