Beispiel für selbstinstrumentierende Tests

Wenn ein Instrumentierungstest gestartet wird, wird das Zielpaket neu gestartet, wobei Instrumentierungscode eingefügt und die Ausführung eingeleitet wird. Eine Ausnahme ist, dass das Zielpaket hier nicht das Android-Anwendungsframework selbst sein kann, z. B. das Paket android. Andernfalls würde das Android-Framework neu gestartet werden müssen, das die Systemfunktionen unterstützt, einschließlich der Instrumentierung selbst.

Das bedeutet, dass ein Instrumentierungstest sich nicht zur Ausführung in das Android-Framework (Systemserver) einschleusen kann. Zum Testen des Android-Frameworks kann der Testcode nur öffentliche API-Oberflächen oder solche aufrufen, die mit der Android Interface Definition Language AIDL im Plattform-Quellbaum verfügbar sind. Bei dieser Kategorie von Tests ist es nicht sinnvoll, ein bestimmtes Paket zu verwenden. Daher ist es üblich, dass solche Instrumentierungen auf ein eigenes Testanwendungspaket ausgerichtet sind, wie in einem eigenen <manifest>-Tag von AndroidManifest.xml definiert.

Abhängig von den Anforderungen können Testanwendungspakete in dieser Kategorie auch:

  • Bündeln Sie Aktivitäten, die für Tests erforderlich sind.
  • Geben Sie die User-ID für das System frei.
  • Es muss mit dem Plattformschlüssel signiert sein.
  • Sie werden anhand der Framework-Quelle und nicht des öffentlichen SDKs kompiliert.

Diese Kategorie von Instrumentierungstests wird manchmal als Selbstinstrumentierung bezeichnet. Hier sind einige Beispiele für Tests zur Selbstinstrumentierung in der Plattformquelle:

Im hier beschriebenen Beispiel wird ein neuer Instrumentierungstest mit einem eigenen Testanwendungspaket als Zielpaket geschrieben. In diesem Leitfaden wird der folgende Test als Beispiel verwendet:

Es wird empfohlen, sich zuerst einen Überblick über den Code zu verschaffen, bevor Sie fortfahren.

Quellspeicherort festlegen

Normalerweise hat Ihr Team bereits ein etabliertes Muster für Stellen, an denen der Code geprüft und Tests hinzugefügt werden sollen. Die meisten Teams besitzen ein einzelnes Git-Repository oder teilen eins mit anderen Teams, haben aber ein dediziertes Unterverzeichnis, das den Quellcode der Komponenten enthält.

Wenn sich der Stammspeicherort der Komponentenquelle unter <component source root> befindet, befinden sich unter den meisten Komponenten die Ordner src und tests sowie einige zusätzliche Dateien wie Android.mk (oder aufgeteilt in zusätzliche .mk-Dateien), die Manifestdatei AndroidManifest.xml und die Testkonfigurationsdatei „AndroidTest.xml“.

Da Sie einen brandneuen Test hinzufügen, müssen Sie wahrscheinlich das Verzeichnis tests neben Ihrer Komponente src erstellen und mit Inhalten füllen.

In einigen Fällen hat Ihr Team unter tests möglicherweise weitere Verzeichnisstrukturen, da verschiedene Testpakete in einzelne APKs verpackt werden müssen. In diesem Fall müssen Sie unter tests ein neues Unterverzeichnis erstellen.

Unabhängig von der Struktur füllen Sie das Verzeichnis tests oder das neu erstellte Unterverzeichnis mit Dateien aus, die denen im Verzeichnis instrumentation in der Beispiel-Gerrit-Änderung ähneln. Die Details zu den einzelnen Dateien werden weiter unten in diesem Dokument erläutert.

Manifestdatei

Wie bei einem App-Projekt benötigt jedes Instrumentierungstestmodul eine Manifestdatei namens AndroidManifest.xml. Wenn Sie diese Datei automatisch über das BUILD_PACKAGE-Kern-Makefile einbeziehen möchten, legen Sie sie neben der Android.mk-Datei für Ihr Testmodul ab.

Wenn Sie mit der AndroidManifest.xml-Datei nicht vertraut sind, lesen Sie den Hilfeartikel App-Manifest – Übersicht.

Hier sehen Sie eine Beispieldatei für 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>

Einige ausgewählte Anmerkungen zur Manifest-Datei:

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

Das Attribut package ist der Name des Anwendungspakets. Dies ist die eindeutige Kennung, mit der das Android-Anwendungsframework eine Anwendung (oder in diesem Kontext: Ihre Testanwendung) identifiziert. Jeder Nutzer im System kann nur eine Anwendung mit diesem Paketnamen installieren.

Darüber hinaus entspricht dieses package-Attribut dem, was ComponentName#getPackageName() zurückgibt, und dasselbe, das Sie verwenden würden, um mit verschiedenen pm-Unterbefehlen zu interagieren und adb shell zu verwenden.

Der Paketname hat zwar normalerweise denselben Stil wie ein Java-Paketname, hat aber nur wenig damit zu tun. Mit anderen Worten, Ihr Anwendungs- oder Testpaket kann Klassen mit beliebigen Paketnamen enthalten. Sie können sich aber auch für die Einfachheit entscheiden und den Namen des Java-Pakets der obersten Ebene in Ihrer Anwendung oder Ihrem Test verwenden, der mit dem Namen des Anwendungspakets identisch ist.

android:sharedUserId="android.uid.system"

Damit wird angegeben, dass dieser APK-Datei bei der Installation dieselbe Nutzer-ID, d.h. dieselbe Laufzeitidentität, wie der Hauptplattform zugewiesen werden soll. Beachten Sie, dass dies davon abhängt, dass die APK-Datei mit demselben Zertifikat wie die Kernplattform signiert ist (siehe LOCAL_CERTIFICATE in einem vorherigen Abschnitt). Es handelt sich jedoch um unterschiedliche Konzepte:

  • Einige Berechtigungen oder APIs sind signaturgeschützt, wofür dasselbe Signaturzertifikat erforderlich ist.
  • Für einige Berechtigungen oder APIs ist die system-Nutzeridentität des Aufrufers erforderlich. Das aufrufende Paket muss die Nutzer-ID mit system teilen, wenn es sich um ein separates Paket von der Kernplattform handelt.
<uses-library android:name="android.test.runner" />

Dies ist für alle Instrumentierungstests erforderlich, da die zugehörigen Klassen in einer separaten Framework-JAR-Bibliotheksdatei gepackt sind. Daher sind zusätzliche Klassenpfadeinträge erforderlich, wenn das Testpaket vom Anwendungs-Framework aufgerufen wird.

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

Vielleicht hast du bemerkt, dass das targetPackage hier dasselbe wie das Attribut package deklariert ist, das im manifest-Tag dieser Datei deklariert ist. Wie bereits in den Grundlagen des Testens erwähnt, ist diese Kategorie von Instrumentierungstests in der Regel für das Testen von Framework-APIs vorgesehen. Daher ist es nicht sinnvoll, ein bestimmtes anvisiertes Anwendungspaket zu haben, außer sich selbst.

Einfache Konfigurationsdatei

Jedes neue Testmodul muss eine Konfigurationsdatei haben, die das Build-System mit Modulmetadaten, Abhängigkeiten zur Kompilierungszeit und Anweisungen für die Paketerstellung anweist. In den meisten Fällen reicht die Soong-basierte Blueprint-Datei aus. Weitere Informationen finden Sie unter Einfache Testkonfiguration.

Komplexe Konfigurationsdatei

Für diese komplexeren Fälle müssen Sie auch eine Testkonfigurationsdatei für den Android-Testbereich Trade Federation schreiben.

In der Testkonfiguration können spezielle Optionen für die Geräteeinrichtung und Standardargumente für die Testklasse angegeben werden. Siehe Beispiel unter /platform_testing/tests/example/instrumentation/AndroidTest.xml.

Der Einfachheit halber finden Sie hier eine Übersicht:

<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>

Einige ausgewählte Anmerkungen zur Testkonfigurationsdatei:

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

Dadurch wird Trade Federation angewiesen, das HelloWorldTests.apk mit einem bestimmten target_preparer auf dem Zielgerät zu installieren. In Trade Federation stehen Entwicklern viele Zielvorbereitungstools zur Verfügung, mit denen sie dafür sorgen können, dass das Gerät vor der Testausführung richtig eingerichtet ist.

<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>

Hier wird die Trade Federation-Testklasse für die Ausführung des Tests angegeben. Außerdem werden das Paket auf dem Gerät, das ausgeführt werden soll, und das Test-Runner-Framework übergeben, in diesem Fall JUnit.

Weitere Informationen finden Sie unter Modulkonfigurationen testen.

JUnit4-Funktionen

Die Verwendung der android-support-test-Bibliothek als Test-Runner ermöglicht die Verwendung neuer JUnit4-Testklassen. Die Beispieländerung in Gerrit enthält einige sehr grundlegende Verwendungen ihrer Funktionen. Siehe Beispiel unter /platform_testing/tests/example/instrumentation/src/android/test/example/helloworld/HelloWorldTest.java.

Testmuster sind in der Regel spezifisch für Komponententeams, aber es gibt einige allgemein nützliche Nutzungsmuster.

@RunWith(JUnit4.class)
public class HelloWorldTest {

Ein wesentlicher Unterschied bei JUnit 4 besteht darin, dass Tests nicht mehr von einer gemeinsamen Basistestklasse erben müssen. Stattdessen schreiben Sie Tests in einfachen Java-Klassen und verwenden Anmerkungen, um bestimmte Testkonfigurationen und -einschränkungen anzugeben. In diesem Beispiel wird angeordnet, dass diese Klasse als JUnit4-Test ausgeführt werden soll.

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

Die Anmerkungen @Before und @After werden von JUnit4 auf Methoden angewendet, um die Einrichtung vor dem Test und die Deaktivierung nach dem Test durchzuführen. Ebenso werden die Anmerkungen @BeforeClass und @AfterClass von JUnit4 auf Methoden angewendet, um vor der Ausführung aller Tests in einer Testklasse die Einrichtung und danach die Deaktivierung durchzuführen. Die Einrichtungs- und Deaktivierungsmethoden auf Klassenebene müssen statisch sein. Die Testmethoden müssen im Gegensatz zu früheren Versionen von JUnit den Methodennamen nicht mehr mit test beginnen, sondern müssen jeweils mit @Test annotiert werden. Wie gewohnt müssen Testmethoden öffentlich sein, keinen Rückgabewert angeben, keine Parameter annehmen und dürfen Ausnahmen auslösen.

Zugriff auf Instrumentierungsklassen

Obwohl im grundlegenden Hello World-Beispiel nicht beschrieben, ist es für einen Android-Test relativ üblich, auf die Instanz Instrumentation zuzugreifen. Dies ist die zentrale API-Schnittstelle, die unter anderem Zugriff auf Anwendungskontexte und Test-APIs im Zusammenhang mit dem Aktivitätslebenszyklus bietet.

Da für JUnit 4-Tests keine gemeinsame Basisklasse mehr erforderlich ist, ist es nicht mehr nötig, die Instrumentation-Instanz über InstrumentationTestCase#getInstrumentation() abzurufen. Stattdessen wird sie vom neuen Test-Runner über InstrumentationRegistry verwaltet, wo die vom Instrumentierungs-Framework erstellte Kontext- und Umgebungskonfiguration gespeichert wird.

Wenn Sie auf die Instanz der Klasse Instrumentation zugreifen möchten, rufen Sie einfach die statische Methode getInstrumentation() der Klasse InstrumentationRegistry auf:

Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation()

Lokal erstellen und testen

Verwenden Sie für die häufigsten Anwendungsfälle Atest.

Bei komplexeren Fällen, die eine umfassendere Anpassung erfordern, folgen Sie der Anleitung zur Instrumentierung.