Beispiel für selbst instrumentierte Tests

Wenn ein Instrumentierungstest gestartet wird, wird das Zielpaket neu gestartet, wobei Instrumentierungscode eingefügt und die Ausführung gestartet 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 so deklariert werden, dass sie auf das eigene Testanwendungspaket ausgerichtet sind, wie im eigenen <manifest>-Tag von AndroidManifest.xml definiert.

Je nach Anforderungen können Test-Anwendungspakete in dieser Kategorie auch:

  • Für Tests erforderliche Set-Aktivitäten
  • Geben Sie die Nutzer-ID an das System weiter.
  • Sie müssen mit dem Plattformschlüssel signiert sein.
  • Sie werden anhand der Framework-Quelle und nicht des öffentlichen SDKs kompiliert.

Diese Kategorie von Instrumentierungstests wird manchmal auch 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:

Wir empfehlen, 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 haben ein einzelnes Git-Repository oder teilen sich eins mit anderen Teams, haben aber ein eigenes Unterverzeichnis, das den Quellcode der Komponenten enthält.

Angenommen, der Stammspeicherort Ihrer Komponentenquelle befindet sich unter <component source root>, haben die meisten Komponenten die Ordner src und tests darunter und einige zusätzliche Dateien wie Android.mk (oder in zusätzliche .mk-Dateien aufgeteilt), 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änderung von Gerrit ä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 Manifestdatei:

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

Außerdem entspricht dieses package-Attribut dem Wert, der von ComponentName#getPackageName() zurückgegeben wird. Es ist auch das Attribut, das du verwendest, wenn du mit verschiedenen pm-Unterbefehlen interagierst, die adb shell 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 aber auch den Java-Paketnamen der obersten Ebene in Ihrer Anwendung oder Ihrem Test mit dem Paketnamen der Anwendung identisch machen.

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 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 und erfordern dasselbe Signaturzertifikat.
  • 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 JAR-Bibliotheksdatei des Frameworks verpackt sind. Daher sind zusätzliche Klassenpfad-Einträge erforderlich, wenn das Testpaket vom Anwendungsframework aufgerufen wird.

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

Möglicherweise ist Ihnen aufgefallen, dass das targetPackage hier mit dem package-Attribut übereinstimmt, 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 Anwendungspaket zu verwenden, das nicht auf sich selbst ausgerichtet ist.

Einfache Konfigurationsdatei

Jedes neue Testmodul muss eine Konfigurationsdatei haben, um das Buildsystem mit Modulmetadaten, Abhängigkeiten zur Kompilierungszeit und Verpackungsanweisungen zu steuern. In den meisten Fällen reicht die Soong-basierte Blueprint-Datei aus. Weitere Informationen finden Sie unter Einfache Testkonfiguration.

Komplexe Konfigurationsdatei

In diesen komplexeren Fällen müssen Sie auch eine Testkonfigurationsdatei für den Test-Harness von Android, 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.

Hier ist ein Snapshot zur Veranschaulichung:

<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. Bei den Testmethoden muss der Methodenname im Gegensatz zu früheren JUnit-Versionen nicht mehr mit test beginnen. Stattdessen muss jede Methode 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 Instrumentierungsklasse

Im einfachen Hello World-Beispiel wird dies nicht behandelt, aber für Android-Tests ist es ziemlich häufig erforderlich, auf eine Instrumentation-Instanz zuzugreifen. Das ist die API-Schnittstelle, die unter anderem Zugriff auf Anwendungskontexte und Test-APIs für den Aktivitätszyklus 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 umfangreichere Anpassung erfordern, folgen Sie der Anleitung zur Instrumentierung.