Przykład testów z samourządzeniem

Po rozpoczęciu testu instrumentacji pakiet docelowy jest ponownie uruchamiany z wstrzykniętym kodem instrumentacji i inicjowany do wykonania. Wyjątkiem jest to, że pakiet docelowy nie może być samym frameworkiem aplikacji na Androida, np. pakietem android, ponieważ prowadzi to do paradoksalnej sytuacji, w której framework Androida musiałby zostać ponownie uruchomiony, a to on obsługuje funkcje systemowe, w tym samo narzędzie.

Oznacza to, że test z instrumentacją nie może wstrzyknąć się do platformy Androida, czyli serwera systemowego, w celu wykonania. Aby przetestować platformę Androida, kod testowy może wywoływać tylko publiczne interfejsy API lub te, które są udostępniane za pomocą języka definicji interfejsu Androida AIDL dostępnego w drzewie źródłowym platformy. W przypadku tej kategorii testów kierowanie na konkretny pakiet nie ma sensu. Dlatego takie instrumentacje są zwykle deklarowane jako kierowane na własny pakiet aplikacji testowej, zgodnie z definicją w tagu <manifest> elementu AndroidManifest.xml.

W zależności od wymagań pakiety aplikacji testowych w tej kategorii mogą też:

  • Aktywności pakietu potrzebne do testowania.
  • Udostępnij identyfikator użytkownika systemowi.
  • musi być podpisany kluczem platformy;
  • być kompilowane na podstawie źródła platformy, a nie publicznego pakietu SDK;

Ta kategoria testów instrumentacyjnych jest czasami nazywana samodzielnym instrumentowaniem. Oto kilka przykładów testów samodzielnego instrumentowania w kodzie źródłowym platformy:

Przykładem jest napisanie nowego testu instrumentacji z ustawionym pakietem docelowym na własny pakiet aplikacji testowej. W tym przewodniku jako przykład posłuży nam ten test:

Zanim przejdziesz dalej, zapoznaj się z kodem, aby uzyskać ogólne pojęcie o tym, co się w nim znajduje.

Wybierz lokalizację źródłową

Zazwyczaj zespół ma już ustalone miejsca, w których należy sprawdzać kod, i miejsca, w których należy dodawać testy. Większość zespołów ma jedno repozytorium Git lub udostępnia je innym zespołom, ale ma dedykowany podkatalog zawierający kod źródłowy komponentu.

Zakładając, że lokalizacja źródła komponentu to <component source root>, większość komponentów ma w niej foldery srctests oraz dodatkowe pliki, takie jak Android.mk (lub podzielone na dodatkowe pliki .mk), plik manifestu AndroidManifest.xml i plik konfiguracji testu „AndroidTest.xml”.

Ponieważ dodajesz zupełnie nowy test, prawdopodobnie musisz utworzyć katalog testsobok komponentusrc i wypełnić go treścią.

W niektórych przypadkach zespół może mieć dodatkowe struktury katalogów w tests ze względu na konieczność spakowania różnych zestawów testów w osobne pliki APK. W takim przypadku musisz utworzyć nowy podkatalog w tests.

Niezależnie od struktury w katalogu tests lub w nowo utworzonym podkatalogu znajdą się pliki podobne do tych, które znajdują się w katalogu instrumentation w przykładowej zmianie Gerrit. Szczegóły każdego pliku wyjaśniamy w dalszej części tego dokumentu.

Plik manifestu

Podobnie jak w przypadku projektu aplikacji, każdy moduł testu instrumentacji wymaga pliku manifestu o nazwie AndroidManifest.xml. Aby automatycznie uwzględnić ten plik za pomocą podstawowego pliku makefile BUILD_PACKAGE, umieść go obok pliku Android.mk modułu testowego.

Jeśli nie znasz pliku AndroidManifest.xml, zapoznaj się z omówieniem pliku manifestu aplikacji.

Oto przykładowy plik AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  android:sharedUserId="android.uid.system"
  package="android.test.example.helloworld" >

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

    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                     android:targetPackage="android.test.example.helloworld"
                     android:label="Hello World Test"/>

</manifest>

Kilka uwag na temat pliku manifestu:

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

Atrybut package to nazwa pakietu aplikacji. Jest to unikalny identyfikator, którego platforma aplikacji na Androida używa do identyfikowania aplikacji (w tym kontekście: aplikacji testowej). Każdy użytkownik w systemie może zainstalować tylko jedną aplikację o danej nazwie pakietu.

Ponadto ten atrybut package jest taki sam jak ten, który zwraca ComponentName#getPackageName(), i taki sam, jakiego używasz do interakcji z różnymi podpoleceniami pm, które używają adb shell.

Pamiętaj, że chociaż nazwa pakietu jest zwykle w tym samym stylu co nazwa pakietu Java, w rzeczywistości ma z nią niewiele wspólnego. Innymi słowy, pakiet aplikacji (lub testu) może zawierać klasy o dowolnych nazwach pakietów. Z drugiej strony możesz uprościć sprawę i użyć w aplikacji lub teście nazwy pakietu Java najwyższego poziomu, która będzie identyczna z nazwą pakietu aplikacji.

android:sharedUserId="android.uid.system"

Oznacza to, że podczas instalacji ten plik APK powinien otrzymać ten sam identyfikator użytkownika, czyli tożsamość w czasie działania, co platforma podstawowa. Pamiętaj, że zależy to od tego, czy plik APK jest podpisany tym samym certyfikatem co platforma podstawowa (patrz LOCAL_CERTIFICATE w poprzedniej sekcji), ale są to różne pojęcia:

  • niektóre uprawnienia lub interfejsy API są chronione podpisem, co wymaga tego samego certyfikatu podpisu;
  • niektóre uprawnienia lub interfejsy API wymagają systemtożsamości użytkownika wywołującegosystem, co wymaga udostępnienia identyfikatora użytkownika przez pakiet wywołujący, jeśli jest to pakiet oddzielny od samej platformy podstawowej;
<uses-library android:name="android.test.runner" />

Jest to wymagane w przypadku wszystkich testów instrumentacji, ponieważ powiązane klasy są spakowane w osobnym pliku biblioteki JAR platformy, dlatego podczas wywoływania pakietu testowego przez platformę aplikacji wymagane są dodatkowe wpisy ścieżki klasy.

android:targetPackage="android.test.example.helloworld"

Możesz zauważyć, że targetPackage jest zadeklarowany tak samo jak atrybut package zadeklarowany w tagu manifest tego pliku. Jak wspomnieliśmy w podstawach testowania, ta kategoria testów instrumentacji jest zwykle przeznaczona do testowania interfejsów API platformy, więc nie ma większego znaczenia, czy mają one określony pakiet aplikacji docelowej inny niż one same.

Prosty plik konfiguracji

Każdy nowy moduł testowy musi mieć plik konfiguracyjny, który zawiera metadane modułu, zależności w czasie kompilacji i instrukcje pakowania, aby kierować systemem kompilacji. W większości przypadków wystarczy opcja pliku Blueprint opartego na Soongu. Więcej informacji znajdziesz w sekcji Simple Test Configuration (Konfiguracja prostego testu).

Złożony plik konfiguracji

W bardziej złożonych przypadkach musisz też napisać plik konfiguracji testu dla platformy testowej Androida, Trade Federation.

Konfiguracja testu może określać specjalne opcje konfiguracji urządzenia i domyślne argumenty, które mają być przekazywane do klasy testu. Przykład znajdziesz w pliku /platform_testing/tests/example/instrumentation/AndroidTest.xml.

Dla wygody dołączyliśmy zrzut ekranu:

<configuration description="Runs sample instrumentation test.">
  <target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup"/>
  <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
    <option name="test-file-name" value="HelloWorldTests.apk"/>
  </target_preparer>
  <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/>
  <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/>
  <option name="test-suite-tag" value="apct"/>
  <option name="test-tag" value="SampleInstrumentationTest"/>

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

Kilka uwag na temat pliku konfiguracji testu:

<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
  <option name="test-file-name" value="HelloWorldTests.apk"/>
</target_preparer>

Informuje to Trade Federation, aby zainstalować plik HelloWorldTests.apk na urządzeniu docelowym za pomocą określonego preparera docelowego. W Trade Federation deweloperzy mają do dyspozycji wiele narzędzi do przygotowywania urządzeń docelowych, które mogą służyć do zapewnienia prawidłowej konfiguracji urządzenia przed wykonaniem testu.

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

Określa klasę testową Trade Federation, która ma być używana do wykonania testu, oraz przekazuje pakiet na urządzenie, na którym ma być wykonany test, i platformę uruchamiającą testy, która w tym przypadku jest JUnit.

Więcej informacji znajdziesz w artykule Testowanie konfiguracji modułów.

Funkcje JUnit4

Używanie biblioteki android-support-test jako narzędzia do uruchamiania testów umożliwia stosowanie nowych klas testowych w stylu JUnit4. Przykładowa zmiana w Gerrit zawiera bardzo podstawowe wykorzystanie jej funkcji. Przykład znajdziesz w tym pliku: /platform_testing/tests/example/instrumentation/src/android/test/example/helloworld/HelloWorldTest.java.

Wzorce testowania są zwykle specyficzne dla zespołów komponentów, ale istnieją pewne ogólnie przydatne wzorce użycia.

@RunWith(JUnit4.class)
public class HelloWorldTest {

Istotną różnicą w JUnit4 jest to, że testy nie muszą już dziedziczyć po wspólnej klasie testu podstawowego. Zamiast tego piszesz testy w zwykłych klasach Java i używasz adnotacji do wskazywania określonych ustawień i ograniczeń testu. W tym przykładzie informujemy, że ta klasa powinna być uruchamiana jako test JUnit4.

    @BeforeClass
    public static void beforeClass() {
    ...
    @AfterClass
    public static void afterClass() {
    ...
    @Before
    public void before() {
    ...
    @After
    public void after() {
    ...
    @Test
    @SmallTest
    public void testHelloWorld() {
    ...

Adnotacje @Before@After są używane w metodach przez JUnit4 do wykonywania konfiguracji przed testem i czyszczenia po teście. Podobnie adnotacje @BeforeClass i @AfterClass są używane w metodach przez JUnit4 do wykonania konfiguracji przed uruchomieniem wszystkich testów w klasie testowej i do wyczyszczenia po ich zakończeniu. Pamiętaj, że metody konfiguracji i czyszczenia w zakresie klasy muszą być statyczne. Jeśli chodzi o metody testowe, w przeciwieństwie do wcześniejszych wersji JUnit nie muszą one już zaczynać nazwy metody od test. Zamiast tego każda z nich musi być opatrzona adnotacją @Test. Jak zwykle metody testowe muszą być publiczne, nie mogą deklarować wartości zwracanej, nie mogą przyjmować parametrów i mogą zgłaszać wyjątki.

Dostęp do klasy instrumentacji

Chociaż nie jest to uwzględnione w podstawowym przykładzie „hello world”, test Androida często wymaga dostępu do instancji Instrumentation: jest to podstawowy interfejs API, który zapewnia dostęp do kontekstów aplikacji, interfejsów API testów związanych z cyklem życia aktywności i innych.

Testy JUnit4 nie wymagają już wspólnej klasy bazowej, więc nie trzeba już uzyskiwać instancji Instrumentation za pomocą InstrumentationTestCase#getInstrumentation(). Zamiast tego nowy program uruchamiający testy zarządza nią za pomocą InstrumentationRegistry, w którym przechowywane są konfiguracje kontekstowe i środowiskowe utworzone przez platformę instrumentacji.

Aby uzyskać dostęp do instancji klasy Instrumentation, wystarczy wywołać metodę statyczną getInstrumentation() w klasie InstrumentationRegistry:

Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation()

Kompilowanie i testowanie lokalnie

W przypadku najczęstszych zastosowań używaj funkcji Atest.

W bardziej złożonych przypadkach wymagających większego dostosowania postępuj zgodnie z instrukcjami dotyczącymi implementacji.