Testowanie fragmentów

Z tego artykułu dowiesz się, jak uwzględnić interfejsy API udostępniane przez platformę w testach, które oceniają zachowanie poszczególnych fragmentów.

Fragmenty kodu pełnią w aplikacji kontenery wielokrotnego użytku, co pozwala prezentować ten sam układ interfejsu w różnych działaniach i konfiguracjach układu. Ze względu na uniwersalność fragmentów ważne jest, aby potwierdzić, że zapewniają one spójność i oszczędność zasobów. Uwaga:

  • Twój fragment nie powinien być zależny od konkretnej aktywności lub fragmentu elementu nadrzędnego.
  • Nie twórz hierarchii widoku fragmentu, jeśli dany fragment nie jest widoczny dla użytkownika.

Aby ułatwić skonfigurowanie warunków przeprowadzania tych testów, biblioteka AndroidaX fragment-testing udostępnia klasę FragmentScenario do tworzenia fragmentów i zmieniania ich Lifecycle.State.

Deklarowanie zależności

Aby użyć FragmentScenario, zdefiniuj artefakt fragment-testing-manifest w pliku build.gradle aplikacji za pomocą debugImplementation, a artefakt fragment-testing za pomocą parametru androidTestImplementation, jak w tym przykładzie:

Odlotowy

dependencies {
    def fragment_version = "1.8.1"

    debugImplementation "androidx.fragment:fragment-testing-manifest:$fragment_version"

    androidTestImplementation "androidx.fragment:fragment-testing:$fragment_version"
}

Kotlin

dependencies {
    val fragment_version = "1.8.1"

    debugImplementation("androidx.fragment:fragment-testing-manifest:$fragment_version")

    androidTestImplementation("androidx.fragment:fragment-testing:$fragment_version")
}

Przykłady testów na tej stronie wykorzystują asercje z bibliotek Espresso i Truth. Informacje o innych dostępnych bibliotekach testowania i potwierdzania znajdziesz w artykule Konfigurowanie projektu na potrzeby testu AndroidX.

Tworzenie fragmentu

FragmentScenario udostępnia te metody uruchamiania fragmentów w testach:

  • launchInContainer() do testowania interfejsu użytkownika fragmentu. FragmentScenario dołącza fragment do kontrolera głównego widoku aktywności. W przeciwnym razie pole zawierające dane o aktywności jest puste.
  • launch() do testowania bez użycia interfejsu użytkownika fragmentu. FragmentScenario łączy ten typ fragmentu do pustego działania, które nie ma głównego widoku.

Po uruchomieniu jednego z tych typów fragmentów FragmentScenario ustawia testowany fragment do określonego stanu. Domyślny stan to RESUMED, ale można to zmienić za pomocą argumentu initialState. Stan RESUMED wskazuje, że fragment jest aktywny i widoczny dla użytkownika. Informacje o elementach interfejsu możesz ocenić za pomocą testów interfejsu Espresso.

Poniższe przykłady kodu pokazują, jak uruchomić fragment przy użyciu poszczególnych metod:

PrzykładlaunchInContainer()

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        // The "fragmentArgs" argument is optional.
        val fragmentArgs = bundleOf(“selectedListItem” to 0)
        val scenario = launchFragmentInContainer<EventFragment>(fragmentArgs)
        ...
    }
}

launch() – przykład

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        // The "fragmentArgs" arguments are optional.
        val fragmentArgs = bundleOf("numElements" to 0)
        val scenario = launchFragment<EventFragment>(fragmentArgs)
        ...
    }
}

Podaj zależności

Jeśli Twoje fragmenty zawierają zależności, możesz udostępnić testowe wersje tych zależności, dodając niestandardowy FragmentFactory do metod launchInContainer() lub launch().

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val someDependency = TestDependency()
        launchFragmentInContainer {
            EventFragment(someDependency)
        }
        ...
    }
}

Więcej informacji o korzystaniu z elementu FragmentFactory do określania zależności od fragmentów znajdziesz w artykule o menedżerze fragmentów.

Przenieś fragment do nowego stanu

W testach interfejsu aplikacji zwykle wystarczy uruchomić testowany fragment i rozpocząć testowanie od stanu RESUMED. Jednak w bardziej szczegółowych testach jednostkowych możesz też ocenić zachowanie fragmentu podczas przechodzenia z jednego stanu cyklu życia do innego. Aby określić stan początkowy, przekaż argument initialState do dowolnej funkcji launchFragment*().

Aby przekierować fragment do innego stanu cyklu życia, wywołaj moveToState(). Ta metoda obsługuje jako argumenty te stany: CREATED, STARTED, RESUMED i DESTROYED. Ta metoda symuluje sytuację, w której fragment lub działanie zawierające Twój fragment zmienia swój stan z dowolnego powodu.

Poniższy przykład uruchamia fragment testowy w stanie INITIALIZED, a następnie przenosi go do stanu RESUMED:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<EventFragment>(
            initialState = Lifecycle.State.INITIALIZED
        )
        // EventFragment has gone through onAttach(), but not onCreate().
        // Verify the initial state.
        scenario.moveToState(Lifecycle.State.RESUMED)
        // EventFragment moves to CREATED -> STARTED -> RESUMED.
        ...
    }
}

Odtwórz fragment

Jeśli aplikacja działa na urządzeniu, na którym brakuje zasobów, system może zniszczyć aktywność zawierającą Twój fragment. W takiej sytuacji aplikacja musi odtworzyć fragment, gdy użytkownik do niego wróci. Aby zasymulować tę sytuację, zadzwoń pod numer recreate():

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<EventFragment>()
        scenario.recreate()
        ...
    }
}

FragmentScenario.recreate() niszczy fragment i jego hosta, a następnie tworzy je. Gdy klasa FragmentScenario odtworzy testowany fragment, fragment powraca do stanu cyklu życia sprzed jego zniszczenia.

Interakcja z fragmentami interfejsu

Aby aktywować działania interfejsu dla testowanego fragmentu, używaj dopasowań widoku Espresso do interakcji z elementami w widoku danych:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<EventFragment>()
        onView(withId(R.id.refresh)).perform(click())
        // Assert some expected behavior
        ...
    }
}

Jeśli chcesz wywołać metodę dla samego fragmentu, np. odpowiedzieć na wybór opcji z menu opcji, możesz to zrobić bezpiecznie, uzyskując odniesienie do fragmentu za pomocą polecenia FragmentScenario.onFragment() i przekazując je w FragmentAction:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<EventFragment>()
        scenario.onFragment { fragment ->
            fragment.myInstanceMethod()
        }
    }
}

Testuj działania w oknie dialogowym

FragmentScenario obsługuje też testowanie fragmentów okien dialogowych. Chociaż fragmenty okien zawierają elementy interfejsu, ich układ znajduje się w osobnym oknie, a nie w samym działaniu. Dlatego do testowania fragmentów okien używaj FragmentScenario.launch().

Ten przykład testuje proces zamykania okna:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testDismissDialogFragment() {
        // Assumes that "MyDialogFragment" extends the DialogFragment class.
        with(launchFragment<MyDialogFragment>()) {
            onFragment { fragment ->
                assertThat(fragment.dialog).isNotNull()
                assertThat(fragment.requireDialog().isShowing).isTrue()
                fragment.dismiss()
                fragment.parentFragmentManager.executePendingTransactions()
                assertThat(fragment.dialog).isNull()
            }
        }

        // Assumes that the dialog had a button
        // containing the text "Cancel".
        onView(withText("Cancel")).check(doesNotExist())
    }
}