Testlerinizi senkronize edin

Compose testleri varsayılan olarak kullanıcı arayüzünüzle senkronize edilir. ComposeTestRule ile bir onaylama veya işlem çağırdığınızda test önceden senkronize edilir ve kullanıcı arayüzü ağacı boşta kalana kadar beklenir.

Normalde herhangi bir işlem yapmanız gerekmez. Ancak bilmeniz gereken bazı uç durumlar vardır.

Bir test senkronize edildiğinde Compose uygulamanız sanal bir saat kullanılarak zamanda ileri alınır. Bu, Compose testlerinin gerçek zamanlı olarak çalışmadığı ve mümkün olduğunca hızlı bir şekilde geçebileceği anlamına gelir.

Ancak testlerinizi senkronize eden yöntemleri kullanmazsanız yeniden oluşturma gerçekleşmez ve kullanıcı arayüzü duraklatılmış gibi görünür.

@Test
fun counterTest() {
    val myCounter = mutableStateOf(0) // State that can cause recompositions.
    var lastSeenValue = 0 // Used to track recompositions.
    composeTestRule.setContent {
        Text(myCounter.value.toString())
        lastSeenValue = myCounter.value
    }
    myCounter.value = 1 // The state changes, but there is no recomposition.

    // Fails because nothing triggered a recomposition.
    assertTrue(lastSeenValue == 1)

    // Passes because the assertion triggers recomposition.
    composeTestRule.onNodeWithText("1").assertExists()
}

Bu koşulun yalnızca Compose hiyerarşileri için geçerli olduğunu ve uygulamanın geri kalanı için geçerli olmadığını unutmayın.

Otomatik senkronizasyonu devre dışı bırakma

ComposeTestRule üzerinden bir onay veya işlem (ör. assertExists()) çağırdığınızda testiniz, Compose kullanıcı arayüzüyle senkronize edilir. Bazı durumlarda bu senkronizasyonu durdurup saati kendiniz kontrol etmek isteyebilirsiniz. Örneğin, kullanıcı arayüzünün hâlâ meşgul olduğu bir noktada animasyonun doğru ekran görüntülerini almak için zamanı kontrol edebilirsiniz. Otomatik senkronizasyonu devre dışı bırakmak için mainClock içindeki autoAdvance özelliğini false olarak ayarlayın:

composeTestRule.mainClock.autoAdvance = false

Genellikle zamanı kendiniz ilerletirsiniz. advanceTimeByFrame() ile tam olarak bir kare veya advanceTimeBy() ile belirli bir süre ilerleyebilirsiniz:

composeTestRule.mainClock.advanceTimeByFrame()
composeTestRule.mainClock.advanceTimeBy(milliseconds)

Boşta bekleyen kaynaklar

Compose, testleri ve kullanıcı arayüzünü senkronize ederek her işlemin ve onayın boşta bir durumda yapılmasını, gerektiğinde saati beklemesini veya ilerletmesini sağlar. Ancak sonuçları kullanıcı arayüzü durumunu etkileyen bazı eşzamansız işlemler, test bunlardan haberdar olmadan arka planda çalıştırılabilir.

Test edilen uygulamanın meşgul veya boşta olup olmadığına karar verilirken dikkate alınmaları için testinizde bu boşta bekleyen kaynakları oluşturup kaydedin. Espresso veya Compose ile senkronize edilmeyen bir arka plan işi çalıştırıyorsanız ek boşta bekleyen kaynakları kaydetmeniz gerekmediği sürece herhangi bir işlem yapmanız gerekmez.

Bu API, test edilen konunun boşta mı yoksa meşgul mü olduğunu belirtmek için Espresso'nun boşta bekleyen kaynaklarına çok benzer. IdlingResource uygulamasını kaydetmek için Compose test kuralını kullanın.

composeTestRule.registerIdlingResource(idlingResource)
composeTestRule.unregisterIdlingResource(idlingResource)

Manuel senkronizasyon

Bazı durumlarda, Oluştur kullanıcı arayüzünü testinizin diğer bölümleriyle veya test ettiğiniz uygulamayla senkronize etmeniz gerekir.

waitForIdle() işlevi, Compose'un boşta olmasını bekler ancak işlev, autoAdvance özelliğine bağlıdır:

composeTestRule.mainClock.autoAdvance = true // Default
composeTestRule.waitForIdle() // Advances the clock until Compose is idle.

composeTestRule.mainClock.autoAdvance = false
composeTestRule.waitForIdle() // Only waits for idling resources to become idle.

Her iki durumda da waitForIdle(), bekleyen çizim ve düzen geçişlerini de bekler.

Ayrıca, advanceTimeUntil() ile belirli bir koşul karşılanana kadar saati ileri alabilirsiniz.

composeTestRule.mainClock.advanceTimeUntil(timeoutMs) { condition }

Belirtilen koşulun, bu saatten etkilenebilecek durumu kontrol etmesi gerektiğini unutmayın (yalnızca Compose durumuyla çalışır).

Animasyon testlerini optimize etme

Yüksek kaliteli animasyonları test ederken genellikle otomatik ilerlemeyi devre dışı bırakmanız ve ara kullanıcı arayüzü durumlarını onaylamak için kareleri manuel olarak ilerletmeniz gerekir. Bu kare kare döngüler için runWithoutImplicitWait yöntemini kullanarak onaylarınızı yürütün. Standart düğüm sorguları (onNodeWithTag veya fetchSemanticsNode gibi), saati manuel olarak kontrol ettiğinizde gereksiz olan dolaylı senkronizasyonları tetikler. Bu nedenle, bunları atlamak test çalışma sürelerinizi önemli ölçüde hızlandırır.

Kullanım talimatları

  • Manuel saat yönetimi: mainClock.autoAdvance, false olarak ayarlandığında ve kullanıcı arayüzü mevcut kare için bilinen, kararlı bir durumdayken bu API'yi kullanın.
  • Kullanıcı arayüzü iş parçacığı yürütme: Kullanıcı arayüzü ağacının kararlılığını sağlamak için kullanıcı arayüzü iş parçacığında runWithoutImplicitWait yöntemini çağırın (ör. runOnUiThread ile). Testi kullanıcı arayüzü iş parçacığının dışında çalıştırmak, testinizi yarışma durumlarına ve eski durum okumalarına maruz bırakır.
  • Salt okunur onaylar: Blok, kesinlikle salt okunur onaylar içermelidir. Durumu değiştiren tüm işlemler bu bloğun dışında gerçekleştirilmelidir.

Örnek

@Test
fun runWithoutImplicitWaitSample() = runComposeUiTest {
    setContent { MainScreen() }
    mainClock.autoAdvance = false

    // Trigger an animation
    onNodeWithText("Start Animation").performClick()

    // Step through the animation frame-by-frame
    while (hasPendingWork()) {
        mainClock.advanceTimeByFrame()
        waitForIdle()
        runOnUiThread {
            // Suppress implicit synchronization inside this block to avoid redundant
            // waits on each node query, making the frame assertions execute much faster.
            runWithoutImplicitWait {
                val box1 = onNodeWithTag("Box1").fetchSemanticsNode()
                val box2 = onNodeWithTag("Box2").fetchSemanticsNode()
                val box3 = onNodeWithTag("Box3").fetchSemanticsNode()

                // Assert the exact intermediate state of all three properties for this frame
                assert(box1.boundsInRoot.right <= box2.boundsInRoot.left)
                assert(box2.boundsInRoot.right <= box3.boundsInRoot.left)
            }
        }
    }
}

Ana iş parçacığı senkronizasyonu

Compose testi artık ana iş parçacığı senkronizasyonunu destekliyor. Bu sayede waitForIdle'i ve dolayısıyla Compose kullanıcı arayüzü işlemlerini ve iddialarını doğrudan ana iş parçacığından güvenli bir şekilde çağırabilirsiniz.

Daha önce Compose testi, iki iş parçacıklı bir modeli kesin olarak uyguluyordu: Test işlemi, arka plan test iş parçacığında gerçekleşirken kullanıcı arayüzü güncellemeleri ana iş parçacığında gerçekleşiyordu. Ana iş parçacığından (örneğin, bir runOnUiThread bloğunun içinden) waitForIdle veya runOnIdle gibi senkronizasyon yöntemlerini çağırmak, çerçeve ana iş parçacığı senkronizasyonunu önlemek için katı iş parçacığı kontrolleri uyguladığından IllegalStateException hatasına neden olur.

Ana iş parçacığı senkronizasyonu etkinleştirildiğinde, Compose test çerçevesi artık ana iş parçacığında engelleme çağrıları yapıldığında bile saati ilerletebilir ve bekleyen işleri işleyebilir.

Ana iş parçacığı senkronizasyonu ne zaman kullanılır?

Testleri arka plan iş parçacığında tutmak, saf Compose testleri için standart olmaya devam etse de ana iş parçacığı senkronizasyonu birkaç özel senaryoda oldukça avantajlıdır:

  • Karmaşık görünüm birlikte çalışabilirliği: Hem Compose hem de eski Android görünümlerini içeren karma kullanıcı arayüzlerini test ederken görünümleri değiştirmek için genellikle ana iş parçacığında çalıştırmak gerekir. Artık sürekli olarak iş parçacığı bağlamları arasında geçiş yapmadan görünümlerle etkileşim kurabilir ve Compose düğümlerinde sırayla onaylama yapabilirsiniz.
  • Eşzamanlı durum mutasyonları: Mimariniz kesinlikle ana iş parçacığına bağlı durum bilgisi depolayıcılara dayanıyorsa artık durumu değiştirebilir ve ana iş parçacığından ayrılmadan Compose kullanıcı arayüzünün yerleşmesini hemen bekleyebilirsiniz.
  • Özel test çalıştırıcılar: Özel test altyapısı oluşturuyorsanız veya test çalıştırıcının doğal olarak ana iş parçacığında çalıştığı ortamlar kullanıyorsanız Compose testleri artık arka plan iş parçacığı yetkilendirmesi gerektirmeden sorunsuz bir şekilde çalışır.

Örnek

Geçmişte, ana iş parçacığında senkronizasyon kesinlikle yasak olduğu için geliştiriciler, arka plan test çalıştırıcı iş parçacığı ile kullanıcı arayüzü iş parçacığı arasında gidip gelmek zorunda kalıyordu. Bu da testlerin birbirinden kopuk olmasına neden oluyordu:

@Test
fun testBidirectionalInteropUIUpdates_old() {
    val scenario = launchFragmentInContainer<InteropFragment>()
    composeTestRule.waitForIdle()
    scenario.onFragment { fragment ->
        fragment.legacyButton.performClick()
    }
    // Jump to Test Thread to verify state settles inside compose
    composeTestRule.waitForIdle()
    composeTestRule.onNodeWithText("Legacy Clicks: 1").assertIsDisplayed()
    composeTestRule.onNodeWithText("Increment Legacy TextView").performClick()
    composeTestRule.waitForIdle()
    // Jump back to Main Thread to verify target view state settles
    scenario.onFragment { fragment ->
        assert(fragment.legacyTextView.text.toString() == "Compose Clicks: 1")
    }
}

Ana iş parçacığı senkronizasyonu etkinleştirildiğinde, Oluşturma ve Görünüm hiyerarşileri için onaylar aynı blokta yürütülebilir:

@Test
fun testBidirectionalInteropUIUpdates_new() {
    val scenario = launchFragmentInContainer<InteropFragment>()
    composeTestRule.waitForIdle()
    scenario.onFragment { fragment ->
        fragment.legacyButton.performClick()
        composeTestRule.waitForIdle()
        composeTestRule.onNodeWithText("Legacy Clicks: 1").assertIsDisplayed()
        composeTestRule.onNodeWithText("Increment Legacy TextView").performClick()
        composeTestRule.waitForIdle()
        assert(fragment.legacyTextView.text.toString() == "Compose Clicks: 1")
    }
}

Koşulları bekleme

Veri yükleme veya Android'in ölçme ya da çizme (yani Compose'un dışındaki ölçme veya çizme) gibi harici çalışmaya bağlı olan tüm koşullar, waitUntil() gibi daha genel bir kavram kullanmalıdır:

composeTestRule.waitUntil(timeoutMs) { condition }

Aşağıdaki waitUntil yardımcılarından herhangi birini de kullanabilirsiniz:

composeTestRule.waitUntilAtLeastOneExists(matcher, timeoutMs)

composeTestRule.waitUntilDoesNotExist(matcher, timeoutMs)

composeTestRule.waitUntilExactlyOneExists(matcher, timeoutMs)

composeTestRule.waitUntilNodeCount(matcher, count, timeoutMs)

Ek Kaynaklar

  • Android'de uygulamaları test etme: Android testleriyle ilgili ana açılış sayfası, testle ilgili temel bilgiler ve teknikler hakkında daha geniş bir bakış açısı sunar.
  • Testlerle ilgili temel bilgiler: Android uygulamalarını test etmenin temel kavramları hakkında daha fazla bilgi edinin.
  • Yerel testler: Bazı testleri yerel olarak, kendi iş istasyonunuzda çalıştırabilirsiniz.
  • Enstrümantasyon testleri: Enstrümantasyon testleri de çalıştırmak iyi bir uygulamadır. Yani doğrudan cihaz üzerinde çalışan testler.
  • Sürekli entegrasyon: Sürekli entegrasyon, testlerinizi dağıtım ardışık düzeninize entegre etmenize olanak tanır.
  • Farklı ekran boyutlarını test edin: Kullanıcıların kullanabileceği çok sayıda cihaz olduğundan farklı ekran boyutlarını test etmeniz gerekir.
  • Espresso: Görünüm tabanlı kullanıcı arayüzleri için tasarlanmış olsa da Espresso bilgisi, Compose testinin bazı yönleri için yine de faydalı olabilir.