Wersje 2 interfejsów API do testowania Compose (createComposeRule, createAndroidComposeRule, runComposeUiTest, runAndroidComposeUiTest itp.) są teraz dostępne, aby zwiększyć kontrolę nad wykonywaniem korutyn. Ta aktualizacja nie obejmuje wszystkich interfejsów API, tylko te, które tworzą środowisko testowe.
Interfejsy API w wersji 1 zostały wycofane i zdecydowanie zalecamy przejście na nowe interfejsy API. Migracja potwierdza, że testy są zgodne ze standardowym działaniem współprogramów, i pozwala uniknąć przyszłych problemów ze zgodnością. Listę wycofanych interfejsów API w wersji 1 znajdziesz w mapowaniu interfejsów API.
Te zmiany są uwzględnione w androidx.compose.ui:ui-test-junit4:1.11.0-alpha03+ i androidx.compose.ui:ui-test:1.11.0-alpha03+.
Interfejsy API w wersji 1 korzystały z UnconfinedTestDispatcher, a interfejsy API w wersji 2 domyślnie używają StandardTestDispatcher do uruchamiania kompozycji. Ta zmiana
dostosowuje działanie testów w Compose do standardowych interfejsów API runTest i zapewnia
wyraźną kontrolę nad kolejnością wykonywania korutyn.
Mapowania interfejsu API
Podczas uaktualniania do interfejsów API w wersji 2 możesz zwykle użyć funkcji Znajdź i zamień, aby zaktualizować importy pakietów i wprowadzić nowe zmiany w dispatcherze.
Możesz też poprosić Gemini o przeprowadzenie migracji do wersji 2 interfejsów API testowania Compose, używając tego prompta:
Prompt AI
Przechodzenie z interfejsów API do testowania w wersji 1 na interfejsy API do testowania w wersji 2
Ten prompt użyje tego przewodnika, aby przeprowadzić migrację do interfejsów API testowania w wersji 2.
Migrate to Compose testing v2 APIs using the official
migration guide.W tabeli poniżej znajdziesz mapowanie wycofanych interfejsów API w wersji 1 na ich odpowiedniki w wersji 2:
Wycofana (v1) |
Wymiana (wersja 2) |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Zgodność wsteczna i wyjątki
Obecne interfejsy API w wersji 1 są już wycofane, ale nadal używają UnconfinedTestDispatcher, aby zachować dotychczasowe działanie i zapobiec zmianom powodującym błędy.
Jedynym wyjątkiem, w przypadku którego zmieniliśmy działanie domyślne, jest:
Domyślny dyspozytor testów używany do uruchamiania kompozycji w klasie
AndroidComposeUiTestEnvironment został zmieniony z UnconfinedTestDispatcher na StandardTestDispatcher. Dotyczy to przypadków, w których tworzysz instancję za pomocą konstruktora lub podklasy AndroidComposeUiTestEnvironment i wywołujesz ten konstruktor.
Kluczowa zmiana: wpływ na wykonywanie współprogramów
Główna różnica między interfejsami API w wersji 1 i 2 polega na sposobie wysyłania korutyn:
- Interfejsy API w wersji 1 (
UnconfinedTestDispatcher): po uruchomieniu korutyny była ona natychmiast wykonywana w bieżącym wątku i często kończyła się przed uruchomieniem następnego wiersza kodu testu. W odróżnieniu od zachowania w wersji produkcyjnej to natychmiastowe wykonanie może nieumyślnie maskować rzeczywiste problemy z czasem lub warunki wyścigu, które wystąpiłyby w aktywnej aplikacji. - Interfejsy API w wersji 2 (
StandardTestDispatcher): po uruchomieniu korutyny jest ona umieszczana w kolejce i nie jest wykonywana, dopóki test nie przesunie wirtualnego zegara. Standardowe interfejsy API do testowania Compose (np.waitForIdle()) już obsługują tę synchronizację, więc większość testów korzystających z tych standardowych interfejsów API powinna nadal działać bez zmian.
Najczęstsze błędy i sposoby ich rozwiązywania
Jeśli po uaktualnieniu do wersji 2 testy się nie powiodą, prawdopodobnie będą miały następujący wzorzec:
- Niepowodzenie: uruchamiasz zadanie (np. ViewModel wczytuje dane), ale Twoje stwierdzenie natychmiast kończy się niepowodzeniem, ponieważ dane są nadal w stanie „Wczytywanie”.
- Przyczyna: w przypadku interfejsów API w wersji 2 korutyny są umieszczane w kolejce zamiast być wykonywane natychmiast. Zadanie zostało umieszczone w kolejce, ale nigdy nie zostało uruchomione przed sprawdzeniem wyniku.
- Napraw: wyraźnie przesuń czas do przodu. Musisz wyraźnie poinformować dyspozytora v2, kiedy ma wykonać pracę.
Poprzednie podejście
W wersji 1 zadanie uruchamiało się i kończyło natychmiast. W wersji 2 poniższy kod nie działa, ponieważ funkcja loadData() nie została jeszcze uruchomiona.
// In v1, this launched and finished immediately.
viewModel.loadData()
// In v2, this fails because loadData() hasn't actually run yet!
assertEquals(Success, viewModel.state.value)
Zalecane działania
Użyj waitForIdle lub runOnIdle, aby wykonać zadania w kolejce przed potwierdzeniem.
Opcja 1: użycie waitForIdle powoduje przesunięcie zegara do momentu, w którym interfejs użytkownika jest bezczynny, co potwierdza, że korutyna została uruchomiona.
viewModel.loadData()
// Explicitly run all queued tasks
composeTestRule.waitForIdle()
assertEquals(Success, viewModel.state.value)
Opcja 2: użycie runOnIdle powoduje wykonanie bloku kodu w wątku interfejsu po tym, jak interfejs przejdzie w stan bezczynności.
viewModel.loadData()
// Run the assertion after the UI is idle
composeTestRule.runOnIdle {
assertEquals(Success, viewModel.state.value)
}
Synchronizacja ręczna
W scenariuszach obejmujących ręczną synchronizację, np. gdy automatyczne przechodzenie do następnego kroku jest wyłączone, uruchomienie korutyny nie powoduje natychmiastowego wykonania, ponieważ zegar testowy jest wstrzymany. Aby wykonać w kolejce współprogramy bez przesuwania wirtualnego zegara, użyj interfejsu API runCurrent(). Uruchamia zadania zaplanowane na bieżący czas wirtualny.
composeTestRule.mainClock.scheduler.runCurrent()
W przeciwieństwie do funkcji waitForIdle(), która przesuwa zegar testu do momentu ustabilizowania się interfejsu, funkcja runCurrent() wykonuje oczekujące zadania, zachowując bieżący czas wirtualny. Takie działanie umożliwia weryfikację stanów pośrednich, które w przeciwnym razie zostałyby pominięte, gdyby zegar został przesunięty do stanu bezczynności.
Udostępniany jest podstawowy harmonogram testów używany w środowisku testowym. Ten harmonogram może być używany w połączeniu z interfejsem runTest API w języku Kotlin, aby synchronizować zegar testowy.
Migracja do runComposeUiTest
Jeśli używasz interfejsów API testów Compose razem z interfejsem API Kotlin runTest, zdecydowanie zalecamy przejście na runComposeUiTest.
Poprzednie podejście
Użycie createComposeRule w połączeniu z runTest tworzy 2 osobne zegary: jeden dla funkcji Compose i jeden dla zakresu testowej korutyny. Ta konfiguracja może wymusić ręczną synchronizację harmonogramu testów.
@get:Rule val composeTestRule = createComposeRule() @Test fun testWithCoroutines() { composeTestRule.setContent { var status by remember { mutableStateOf("Loading...") } LaunchedEffect(Unit) { delay(1000) status = "Done!" } Text(text = status) } // NOT RECOMMENDED // Fails: runTest creates a new, separate scheduler. // Advancing time here does NOT advance the compose clock. // To fix this without migrating, you would need to share the scheduler // by passing 'composeTestRule.mainClock.scheduler' to runTest. runTest { composeTestRule.onNodeWithText("Loading...").assertIsDisplayed() advanceTimeBy(1000) composeTestRule.onNodeWithText("Done!").assertIsDisplayed() } }
Zalecane działania
Interfejs runComposeUiTest API automatycznie wykonuje blok testowy w swoim zakresie.runTest Zegar testowy jest zsynchronizowany ze środowiskiem Compose, więc nie musisz już ręcznie zarządzać harmonogramem.
@Test fun testWithCoroutines() = runComposeUiTest { setContent { var status by remember { mutableStateOf("Loading...") } LaunchedEffect(Unit) { delay(1000) status = "Done!" } Text(text = status) } onNodeWithText("Loading...").assertIsDisplayed() mainClock.advanceTimeBy(1000 + 16 /* Frame buffer */) onNodeWithText("Done!").assertIsDisplayed() } }