Media3, hem yerel JVM testlerinde hem de cihaz enstrümantasyon testlerinde medya oynatmayı, oynatıcı durum geçişlerini, kullanıcı arayüzü bileşenlerini ve özel medya kaynaklarını test etmenize yardımcı olmak için özel test yapıları (media3-test-utils ve media3-test-utils-robolectric) sağlar.
Bu sayfada, ExoPlayer kullanan uygulamaları test etmek için önerilen uygulamalar açıklanmakta ve her test ortamında Media3'ün test yardımcı programlarının nasıl kullanılacağı gösterilmektedir.
Önerileri test etme
Medya oynatma için otomatik testler yazarken şu temel ilkelere uyun:
- Taklitler yerine gerçek bileşenleri veya Media3 sahtelerini tercih edin: Mockito gibi çerçevelerle
PlayerveyaExoPlayer'yi taklit etmekten kaçının.Player, taklitlerin zorunlu kılmadığı durum sabitlerine ve dinleyici sözleşmelerine sahiptir. Bu durum, testlerin kırılgan olmasına veya hataların gizlenmesine neden olabilir. Bunun yerine gerçek birExoPlayerörneği,FakePlayerveyaFakeMediaSourceveFakeDataSourcegibi sahte örnekler kullanın. Taklitleri, gerektiğinde özel geri çağırma çağrılarını doğrulamak için kullanın. - Bekleme yerine deterministik bekleme kullanın:
ExoPlayer, medya yükleme ve oynatma işlemlerini dahili bir iş parçacığında eşzamansız olarak gerçekleştirir. Testleri yavaş ve güvenilmez hale getiren rastgeleThread.sleepçağrılarından kaçının. Robolectric testlerindeTestPlayerRunHelper, enstrümantasyon testlerinde ise askıya almaPlayeruzantıları veyaPlayerFencekullanın. Bu yardımcılar, belirli oyuncu koşullarını bekler ve oynatma hatası oluşursa hemen başarısız olur. - Oynatıcıya uygulama iş parçacığında erişme:
ExoPlayerörnekleri ve bekleyen yardımcılar, oynatıcının uygulama iş parçacığındaLooper(genellikle ana iş parçacığı) çağrılmalıdır. Daha fazla bilgi için ExoPlayer iş parçacığı oluşturma gereksinimleri bölümüne bakın.
Doğru test ortamını seçme
Medya entegrasyonunuzun farklı katmanları, farklı test ortamlarından yararlanır:
| Test hedefi | Önerilen ortam | Temel Media3 yardımcı programları |
|---|---|---|
Kullanıcı arayüzü bileşenleri (PlayerView, Compose kullanıcı arayüzü), ViewModel'ler veya MediaSession geri çağırmaları |
Yerel Robolectric testleri (src/test) veya araçlı kullanıcı arayüzü testleri (src/androidTest) |
FakePlayer |
ExoPlayer entegrasyonu, oynatma listesi geçişleri, analizler ve özel MediaSource veya DataSource mantığı |
Yerel Robolectric testleri (src/test) |
TestExoPlayerBuilder, TestPlayerRunHelper, FakeMediaSource |
Gerçek donanım MediaCodec kod çözme, Surface oluşturma, ses çıkışı ve DRM |
Fiziksel cihazlarda enstrümantasyon testleri (src/androidTest) |
ExoPlayer.Builder, Player.await* uzantı, PlayerFence |
Emülatör medya yığını uygulamaları farklılık gösterdiğinden, mümkün olduğunda araçlı oynatma testlerini emülatörler yerine fiziksel cihazlarda çalıştırın. Emülatör desteği hakkında daha fazla bilgi için desteklenen cihazlar kılavuzuna bakın.
Test bağımlılıkları ekleme
Media3 test yapılarını, diğer Media3 modüllerinizle aynı sürümü kullanarak modülünüzün build.gradle.kts veya build.gradle dosyasının dependencies bloğuna ekleyin:
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")
Modern
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"
İki modül, sorumlulukları çalışma zamanı ortamına göre böler:
media3-test-utils:TestExoPlayerBuilder,PlayerFence(ve KotlinPlayer.await*uzantıları),FakePlayer,FakeClock,FakeMediaSource,FakeDataSource,DataSourceContractTestveMedia3FlagsRuledahil olmak üzere hem yerel hem de enstrümantasyon testleri için ortamlar arası yardımcı programlar.media3-test-utils-robolectric: Yerel JVM'de Robolectric testleri için yardımcı programlar.TestPlayerRunHelper,RobolectricUtil,ShadowMediaCodecConfig,CapturingRenderersFactoryvePlaybackOutputbuna dahildir.
Modülünüzün, test ortamlarınız için test çerçevesi bağımlılıklarını da içerdiğinden emin olun:
- Robolectric:
org.robolectric:robolectric. Yerel öğeler yüklenirkenandroid.testOptions.unitTests.isIncludeAndroidResources = truedeğerini ayarlayın. - AndroidX Test:
androidx.test:runner,androidx.test:coreveandroidx.test.ext:junit. - Ana iş parçacığındaki Kotlin eş yordamları:
org.jetbrains.kotlinx:kotlinx-coroutines-android.
Kararsız test API'lerini etkinleştirme
media3-test-utils ve media3-test-utils-robolectric içindeki sınıflar ve yöntemler @UnstableApi ile ek açıklama eklenir. Media3, yaygın olarak kullanılan test yardımcılarını sürümler arasında ergonomik tutmayı amaçlasa da test yardımcı programları, üretim oynatma API'leriyle aynı ikili uyumluluk kararlılık kurallarına uymaz.
Test kaynak gruplarınızda, test sınıflarına veya yöntemlerine @androidx.annotation.OptIn ile ek açıklama ekleyin, test paketleriniz için package-info etkinleştirmesi ekleyin veya test kaynakları için lint.xml yapılandırın. Daha fazla bilgi için kararsız API lint sorun giderme notlarına bakın.
Robolectric ile yerel testler
Yerel Robolectric test çalıştırmaları, fiziksel cihaz veya emülatör olmadan geliştirme makinenizin JVM'sinde çalışır. Robolectric, MediaCodec uygulamaları sağlamaz ve ana Looper'i açıkça pompalanana kadar duraklatılmış olarak tutar. Bu nedenle, varsayılan bir ExoPlayer örneği medyayı çözemez veya durum güncellemelerini kendi başına iletemez.
Test oynatıcı oluşturma ve oynatmayı ilerletme
Robolectric testleri için yapılandırılmış bir ExoPlayer oluşturmak üzere TestExoPlayerBuilder kullanın. Varsayılan olarak TestExoPlayerBuilder, aşağıdaki bileşenleri yapılandırır:
ExoPlayer'nin arka planda oynatma iş parçacığı ile Robolectric'in duraklatılmış anaLooperarasında zaman ilerlemesini koordine eden, otomatik ilerleyen birFakeClock(isAutoAdvancing = true).MediaCodeckod çözücüleri gerektirmeden medya örneklerini zaman damgalarına göre kullanan sahte video ve ses oluşturucular (FakeVideoRendererveFakeAudioRenderer).
Oynatmayı ilerletmek ve ana iş parçacığında sonuçları onaylamak için (Robolectric'te varsayılan olan Looper.getMainLooper kullanan bir oynatıcıyla) TestPlayerRunHelper üzerinde statik advance(player) veya play(player) yöntemlerini kullanın (play(player), beklemeden önce player.play yöntemini çağırır). Çoğu koşulda yardımcı, hedef koşul karşılanana kadar Robolectric'in ana Looper öğesini pompalar ve önemli veya önemli olmayan bir oynatma hatası oluşursa istisna oluşturur:
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); } }
Bu örnekte asset:///media/mp4/sample.mp4, modülünüzün src/test/assets/ (veya src/main/assets/) dizinine yerleştirilmiş örnek bir medya dosyasını ifade eder.
Bir ExoPlayer referansı iletildiğinde advance(player) ve play(player), bir ExoPlayerRunResult (veya bir Player iletildiğinde PlayerRunResult) döndürür. Bu sonuç türleri; oynatma durumlarını, konumları, oynatma listesi geçişlerini, oluşturulan kareleri, bekleyen komutları ve beklenen oynatma hatalarını bekleme veya önemli olmayan hataları yoksayma yöntemleri sağlar. Bekleme koşulları hakkında daha fazla bilgi için TestPlayerRunHelper API referansına bakın.
Sahte kaynaklar ve simüle edilmiş codec'lerle test etme
Birçok entegrasyon testi; oynatma listesinde gezinme, parça seçimi, zaman çizelgesi güncellemeleri veya yeniden deneme davranışını doğrular ve gerçek medya dosyalarına ihtiyaç duymaz. FakeMediaSource, FakeTimeline, FakeDataSource veya WebServerDispatcher (OkHttp MockWebServer ile) kullanarak sentetik zaman çizelgelerini ve verileri bir TestExoPlayerBuilder oynatıcıya aktarabilirsiniz:
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, varsayılan olarak sahte oluşturucular kullanır. Robolectric testinizde MediaCodec etkileşimleri gerekiyorsa simüle edilmiş codec'leri ve çıkış yakalamayı aşağıdaki gibi yapılandırın:
- Robolectric'in simüle edilmiş codec'lerini kaydetmek için
ShadowMediaCodecConfig'danwithAllDefaultSupportedCodecsöğesini JUnit@Ruleolarak ekleyin. - Oluşturucunun
Clock(CapturingRenderersFactory(context, builder.getClock())) ile oluşturulmuş birCapturingRenderersFactoryöğesinibuilder.setRenderersFactoryöğesine iletin. - Video kanallarını test ederken
player.setVideoSurfaceile birSurface(ör.Surface(SurfaceTexture(1))) ekleyin. BöyleceMediaCodecVideoRenderer, kodu çözülmüş kareleri atlamak yerine oluşturur. - Oluşturulan çıkışı doğrulamak için oynatmayı başlatmadan önce
PlaybackOutputüzerinderegister(player, capturingRenderersFactory)yöntemini çağırın. - Oynatma bittikten sonra oynatıcıyı ve yüzeyi serbest bırakın, ardından
Dumper(Dumper().add(playbackOutput).toString()) biçimlendirilmiş çıkışını inceleyin.
Cihazlarda enstrümantasyon testleri
Yerel Robolectric testleri gerçek platform kod çözücülerini, ses parçalarını, DRM oturumlarını veya Surface oluşturmayı yürütmez. Android çalıştıran cihazlarda uçtan uca oynatma davranışını doğrulamak için ExoPlayer.Builder ile oluşturulmuş gerçek bir ExoPlayer kullanarak src/androidTest içinde enstrümantasyon testleri yazın.
JUnit enstrümantasyon testlerinde @Test yöntemleri, uygulamanın ana iş parçacığı yerine bir enstrümantasyon iş parçacığında çalışır. ExoPlayer öğesini her zaman uygulama Looper iş parçacığında oluşturun, kontrol edin ve serbest bırakın. Örneğin, Kotlin'de runBlocking(Dispatchers.Main) veya Java'da InstrumentationRegistry.getInstrumentation().runOnMainSync kullanın.
PlayerFence ve eş yordamlarla oyuncu durumlarını bekleme
Enstrümantasyon testlerinde ana Looper, cihazda gerçek zamanlı olarak çalışır.
androidTestImplementation bağımlılıklarına Robolectric veya media3-test-utils-robolectric eklemeyin. media3-test-utils, oynatma durumlarını ve etkinliklerini beklemek için aşağıdaki yardımcıları sağlar:
- Kotlin:
Playerüzerinde askıya alma uzantısı işlevleri (ör.awaitPlaybackState,awaitIsPlaying,awaitFirstFrameRenderedveawaitContentPositionAtLeast). - Java:
PlayerFenceüzerindeki statikfutureWhen(player)yöntemi, koşul yöntemleri (entersPlaybackStateveisPlayinggibi) oynatıcınınLooperiş parçacığında çağrılması gereken birPlayerFencedöndürür ve test iş parçacığınıngetile engelleyebileceği birListenableFuturedöndürür.
Her iki API de oyuncuyu beklerken denetler. Önemli bir oynatma hatası (veya varsayılan olarak önemli olmayan bir hata) oluşursa ya da bekleme süresi zaman aşımını (varsayılan olarak 10 saniye veya emülatörde 30 saniye) aşarsa çağrı askıda kalmak yerine hemen başarısız olur:
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)); } }
Bir uygulama modülünde (com.android.application) ApplicationProvider.getApplicationContext ile ExoPlayer oluştururken asset:/// URI'leri, hedef uygulamanın öğelerine (src/androidTest/assets/ yerine src/debug/assets/ veya src/main/assets/ gibi) göre çözümlenir.
Geçici etkinlikler ve özel gözetim
media3-test-utils içindeki yardımcılar, mevcut oyuncu durumunu kontrol etmenin yanı sıra (koşul zaten karşılanmışsa bekleme işlemleri hemen döndürülür) tek seferlik etkinlikleri ve özel dinleyici koşullarını destekler:
- Oluşturulan ilk kare gibi geçici etkinlikler: Oynatıcıya bir video çıkışı (ör.
SurfaceveyaPlayerView) ekleyin veplayer.prepareyöntemini çağırmadan önceawaitFirstFrameRenderedya darendersFirstFrameile beklemeye başlayın. Böylece erken bir tek seferlik kare etkinliği kaçırılmaz. - Özel koşullar: Kotlin'de
playerüzerindeawaitSupervisediçinde özel dinleyici veya eş yordam bekleme sürelerini sarmalayın ya da Java'dafutureWhen(player).withSupervision(future)'e özel birListenableFutureiletin. Önemli olmayan hatalardan kurtarma testini yaparkenfailOnNonFatalErrors = false(Kotlin) veyaignoringNonFatalErrors(Java) çağrısını iletin ve zaman aşımlarınıtimeoutya dawithTimeoutMs(timeoutMs)ile özelleştirin.
Aşağıdaki örnekte, geçici bir ilk kare etkinliği beklenir ve özel bir meta veri dinleyicisi denetlenir:
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); }); }
FakePlayer ile kullanıcı arayüzünü ve uygulama bileşenlerini test etme
PlayerView veya media3-ui-compose bileşenleri, ViewModel'ler ya da MediaSession geri çağırmaları gibi kullanıcı arayüzü bileşenlerini test ederken tam bir ExoPlayer oluşturmak genellikle gereksizdir. media3-test-utils içindeki FakePlayer, çağıran iş parçacığının Looper öğesinde oynatma listelerini, oynatma durumlarını, konumları, video boyutlarını, işaretleri ve hataları simüle etmek için SimpleBasePlayer öğesini genişletir. Bu Looper, @RunWith(AndroidJUnit4::class) ile Robolectric testlerinde ve enstrümantasyon testlerindeki ana iş parçacığında otomatik olarak kullanılabilir.
Varsayılan olarak FakePlayer, prepare ve seekTo üzerinde 100 ms'lik bir arabelleğe alma gecikmesini simüle eder. Kotlin'de bufferingDelayMs = 0 değerini iletin veya durumları doğrudan setPlaybackState ile ayarlayın:
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();
Ek test yardımcı programları
media3-test-utils, diğer test senaryoları için özel yardımcılar içerir:
- Özellik bayrakları ve Canary modu: Her testten sonra bayrak geçersiz kılma işlemlerini sıfırlamak ve bayrak durumları genelinde parametreleştirilmiş testler çalıştırmak için
Media3FlagsRule'ı@BindFlagve@BindCanaryModeile birlikte kullanın. Daha fazla bilgi için özellik bayraklarıyla test etme başlıklı makaleyi inceleyin. - Özel
DataSourcesözleşme testleri: ÖzelDataSourceveyaHttpDataSourceuygulamalarının aralık istekleri, hata işleme ve aktarım dinleyicileri genelinde Media3'ün sözleşme beklentilerine uygun olduğunu doğrulamak içinDataSourceContractTestalt sınıfını kullanın. Daha fazla bilgi için Diğer ağ yığınlarını entegre etme başlıklı makaleyi inceleyin.