Strona główna  /  Android  /  Service test Android – jak przeprowadzić testy usług?

Testowanie wydajności baterii i szybkości ładowania nowoczesnego smartfona z systemem Android.

Service test Android – jak przeprowadzić testy usług?

Testy usług Androida najwygodniej uruchamiać jako testy instrumentacji z użyciem AndroidJUnitRunner. Android Test Orchestrator izoluje kolejne przypadki testowe, a Atest automatyzuje lokalny cykl kompilacji, instalacji i uruchamiania.

Jak działa test instrumentacji Androida?

Test instrumentacji działa na urządzeniu lub emulatorze i może korzystać z kontekstu aplikacji, komponentów systemu oraz mechanizmów platformy. AndroidJUnitRunner ładuje pakiet testowy i aplikację docelową, uruchamia testy JUnit 4, a następnie przekazuje wyniki do narzędzia wykonującego testy.

Współczesne testy powinny używać stylu JUnit 4 z adnotacją @RunWith(AndroidJUnit4::class). To odróżnia je od starszych klas, takich jak InstrumentationTestCase. W przypadku testów interfejsu można korzystać między innymi z Espresso, UI Automator lub Compose.

Podstawowa struktura testu z AndroidJUnitRunner

W teście Kotlin kontekst aplikacji pobierzesz przez ApplicationProvider.getApplicationContext(). Jeżeli aplikacja ma własną klasę Application, metoda zwróci kontekst tej implementacji.

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertNotNull
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class ServiceTest {

    private val context: Context =
        ApplicationProvider.getApplicationContext()

    @Test
    fun applicationContext_isAvailable() {
        assertNotNull(context)
    }
}

Gdy potrzebujesz niższego poziomu API testowego, użyj InstrumentationRegistry. Udostępnia ono obiekt Instrumentation, kontekst aplikacji docelowej, kontekst aplikacji testowej oraz argumenty przekazane z wiersza poleceń.

import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertNotNull
import org.junit.Test

class InstrumentationAccessTest {

    @Test
    fun instrumentation_isAvailable() {
        val instrumentation =
            InstrumentationRegistry.getInstrumentation()

        assertNotNull(instrumentation.targetContext)
    }
}

W konfiguracji modułu testowego ustaw runner:

android {
    defaultConfig {
        testInstrumentationRunner =
            "androidx.test.runner.AndroidJUnitRunner"
    }
}

dependencies {
    androidTestImplementation("androidx.test:runner:1.6.2")
    androidTestImplementation("androidx.test:core:1.6.1")
    androidTestImplementation("androidx.test.ext:junit:1.2.1")
}

Numery wersji bibliotek zmieniają się wraz z AndroidX. W projekcie używaj wersji zgodnych z pozostałymi zależnościami i konfiguracją Gradle. Sama zasada pozostaje taka sama: runner trafia do defaultConfig, a biblioteki testowe do konfiguracji androidTestImplementation.

Android Test Orchestrator i izolacja stanu

Android Test Orchestrator uruchamia poszczególne testy w osobnych wywołaniach instrumentacji. Dzięki temu stan utworzony przez jeden przypadek testowy nie powinien wpływać na kolejny. Jest to szczególnie przydatne przy testowaniu usług, które zapisują dane, utrzymują procesy lub korzystają ze współdzielonych singletonów.

Izolacja nie jest bezpłatna. Orchestrator uruchamia aplikację dla każdego testu, więc pełny zestaw może działać dłużej. Zyskujesz jednak bardziej wiarygodne wyniki i łatwiejsze wskazanie testu, który pozostawił niepoprawny stan.

Najważniejsze korzyści tego trybu są następujące:

  • Każdy test otrzymuje oddzielną instancję instrumentacji.
  • Błąd jednego przypadku nie kończy automatycznie pozostałych testów.
  • Flagi czyszczenia mogą usuwać dane pakietu po każdym uruchomieniu.
  • Testy usług są mniej zależne od kolejności wykonywania.

Włącz Orchestrator w konfiguracji modułu, wykonując kolejno te czynności:

  1. Ustaw AndroidJUnitRunner w sekcji defaultConfig.
  2. Włącz wykonanie ANDROIDX_TEST_ORCHESTRATOR.
  3. Dodaj pakiet Orchestratora jako androidTestUtil.
  4. Uruchom testy zadaniem connectedCheck na podłączonym urządzeniu.
android {
    defaultConfig {
        testInstrumentationRunner =
            "androidx.test.runner.AndroidJUnitRunner"

        testInstrumentationRunnerArguments["clearPackageData"] =
            "true"
    }

    testOptions {
        execution = "ANDROIDX_TEST_ORCHESTRATOR"
    }
}

dependencies {
    androidTestImplementation("androidx.test:runner:1.6.2")
    androidTestUtil("androidx.test:orchestrator:1.5.1")
}
./gradlew connectedCheck

Argument clearPackageData powoduje czyszczenie danych pakietu pomiędzy wywołaniami testów. W przypadku usług, które zapisują konfigurację lub stan w pamięci trwałej, sprawdź, czy takie czyszczenie nie usuwa danych wymaganych przez scenariusz. Dla prostych testów instrumentacji bez współdzielonego stanu bezpośrednie wykonanie może być szybsze.

Moduł testowy i self-instrumentation

Self-instrumentation stosuje się przede wszystkim przy testowaniu API platformy lub kodu systemowego, gdy nie ma pojedynczej aplikacji, w którą można sensownie wstrzyknąć instrumentację. Pakiet testowy staje się wtedy własnym pakietem docelowym.

Nie oznacza to automatycznego dostępu do wewnętrznych interfejsów platformy. Kod korzystający z publicznego SDK może używać publicznych API. Symbole oznaczone @hide nie są częścią publicznego interfejsu i wymagają odpowiedniego środowiska budowania AOSP, zależności oraz uprawnień. Instrumentacja nie może po prostu wstrzyknąć się do pakietu frameworka Androida, aby testować jego implementację wewnętrzną.

Minimalny manifest modułu testowego może wyglądać tak:

<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.servicetests">

    <application>
        <uses-library android:name="android.test.runner" />
    </application>

    <instrumentation
        android:name="androidx.test.runner.AndroidJUnitRunner"
        android:targetPackage="com.example.servicetests"
        android:label="Service tests" />
</manifest>

Atrybut package identyfikuje zainstalowany pakiet testowy. W wariancie self-instrumentation android:targetPackage wskazuje ten sam pakiet. W AOSP obok manifestu zwykle znajdują się katalog źródłowy testów oraz plik AndroidTest.xml, który opisuje konfigurację Trade Federation.

<configuration description="Runs service instrumentation tests.">
    <target_preparer
        class="com.android.tradefed.targetprep.TestAppInstallSetup">
        <option
            name="test-file-name"
            value="ServiceTests.apk" />
    </target_preparer>

    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
        <option name="package"
            value="com.example.servicetests" />
        <option name="runner"
            value="androidx.test.runner.AndroidJUnitRunner" />
    </test>
</configuration>

Dokładne klasy preparerów i nazwy artefaktów zależą od konfiguracji AOSP. W projekcie aplikacyjnym najczęściej wystarczy moduł androidTest Gradle. Plik AndroidTest.xml jest typowy dla modułów platformowych uruchamianych przez Trade Federation.

Uruchamianie testów usług za pomocą Atest

Atest jest narzędziem wiersza poleceń używanym w drzewie źródłowym Androida. Upraszcza cykl budowania, instalowania i uruchamiania testów, więc dobrze sprawdza się podczas szybkiej iteracji nad testem usługi.

Przed pierwszym uruchomieniem skonfiguruj środowisko AOSP, wybierz cel kompilacji, zbuduj wymagane artefakty i podłącz urządzenie lub uruchom emulator. Podstawowa składnia wygląda tak:

atest test-to-run

Najczęściej używane argumenty Atest przedstawia tabela:

Opcja Długa opcja Opis
-b --build Buduje docelowe artefakty testowe.
-i --install Instaluje artefakty testowe na urządzeniu.
-t --test Uruchamia testy.
-s --serial Wskazuje numer seryjny urządzenia.
-d --disable-teardown Wyłącza końcowe czyszczenie testu.
-w --wait-for-debugger Czeka na dołączenie debugera.

Test możesz wskazać na kilka sposobów:

  • Nazwa modułu, na przykład FrameworksServicesTests.
  • Moduł i klasa, na przykład FrameworksServicesTests:ScreenDecorWindowTests.
  • Nazwa klasy, gdy Atest potrafi znaleźć ją jednoznacznie.
  • Ścieżka do katalogu, pliku Java lub konfiguracji Trade Federation.
  • Nazwa pakietu, na przykład com.android.server.wm.

Przykładowe polecenia dla modułu i pojedynczej klasy:

atest FrameworksServicesTests
atest FrameworksServicesTests:ScreenDecorWindowTests
atest frameworks/base/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java

Domyślnie Atest wykonuje wszystkie trzy etapy. Możesz rozdzielić je podczas debugowania:

# tylko kompilacja
atest -b FrameworksServicesTests

# tylko uruchomienie istniejących artefaktów
atest -t FrameworksServicesTests

# instalacja i uruchomienie bez ponownej kompilacji
atest -it FrameworksServicesTests

# kompilacja i uruchomienie bez instalacji
atest -bt FrameworksServicesTests

Opcja -t bywa użyteczna przy iteracji, ale wcześniejsze czyszczenie testu może usunąć artefakty potrzebne do kolejnego uruchomienia. W takim przypadku użyj --disable-teardown:

atest -d FrameworksServicesTests
atest -t FrameworksServicesTests

Adnotacje i filtrowanie testów

AndroidJUnitRunner rozpoznaje standardowe adnotacje JUnit 4 oraz adnotacje AndroidX, które pomagają ograniczyć zakres wykonania. Najczęściej używane oznaczenia mają takie działanie:

Adnotacja Efekt wykonania Typowe zastosowanie
@LargeTest Oznacza test wymagający większego środowiska lub czasu. Test usługi z pełną konfiguracją aplikacji.
@RequiresDevice Ogranicza test do fizycznego urządzenia. Funkcje zależne od sprzętu.
@SdkSuppress(minSdkVersion = 23) Pomija test na niższych poziomach API. API dostępne dopiero od wskazanej wersji.
@SmallTest Klasyfikuje krótki test o małym zakresie. Prosta weryfikacja lokalnego zachowania.

Klasyfikację rozmiaru można wykorzystać także z argumentem runnera, na przykład:

./gradlew connectedAndroidTest \
    -Pandroid.testInstrumentationRunnerArguments.size=small

Przy bezpośrednim użyciu adb shell am instrument można dzielić zestaw na fragmenty. Przykład uruchamia drugi fragment z dziesięciu:

adb shell am instrument -w \
    -e numShards 10 \
    -e shardIndex 2 \
    com.example.servicetests/androidx.test.runner.AndroidJUnitRunner

Debugowanie, pojedyncze metody i niestabilność

Do szybkiej diagnostyki uruchamiaj najmniejszy możliwy zakres. Atest pozwala wskazać metodę po znaku #:

atest FrameworksServicesTests:ScreenDecorWindowTests#testFlagChange
atest FrameworksServicesTests:ScreenDecorWindowTests#testFlagChange,testRemoval

Cały moduł nadal musi zostać zbudowany, ale wykonanie tylko jednej metody skraca etap uruchamiania. Podczas oczekiwania na debuger użyj:

atest FrameworksServicesTests:ServiceTest \
    --wait-for-debugger

Jeśli podejrzewasz wyścig, zależność od kolejności lub niepełne czyszczenie stanu, powtarzaj testy z kontrolowaną liczbą iteracji:

# dokładnie pięć iteracji
atest FrameworksServicesTests:ServiceTest --iterations 5

# wszystkie testy do pierwszej porażki
atest FrameworksServicesTests:ServiceTest --rerun-until-failure 100

# ponawianie tylko przypadków zakończonych błędem
atest FrameworksServicesTests:ServiceTest --retry-any-failure 100

Wynik powtarzalny tylko bez Orchestratora może wskazywać na współdzielony stan. Wynik zależny od urządzenia może z kolei wynikać z poziomu API, uprawnień, dostępnych usług systemowych albo różnic między emulatorem a sprzętem fizycznym. Najpierw rozdziel problem konfiguracji od błędu usługi, a potem uruchom minimalny przypadek z włączonym czyszczeniem danych.

Gotowy przepływ pracy wygląda następująco: skonfiguruj AndroidJUnitRunner, przygotuj test JUnit 4, włącz Orchestrator dla scenariuszy ze współdzielonym stanem, a następnie używaj Atest do kolejnych cykli build, install i test. Przy pojedynczym błędzie zawężaj identyfikator do klasy lub metody i dopiero później uruchamiaj pełny moduł.

Redakcja portone.pl

Portone to poradnik dla użytkowników telefonów komórkowych. Pokazujemy krok po kroku, jak zmienić ustawienia telefonu, wysyłać SMS-y i MMS-y, sprawdzić historię połączeń i ustawić przekierowanie. Wyjaśniamy też, co oznaczają ikony i komunikaty na ekranie. Podpowiadamy, jak działają sieci komórkowe i jak rozwiązywać typowe problemy z Androidem.

Może Cię również zainteresować

Potrzebujesz więcej informacji?