Beispiel für selbst instrumentierte Tests

Wenn ein Instrumentierungstest gestartet wird, wird das zugehörige Zielpaket mit eingefügtem und zur Ausführung initiiertem Instrumentierungscode neu gestartet. Eine Ausnahme besteht darin, 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, was paradoxerweise die Systemfunktionen, einschließlich der Instrumentierung selbst, unterstützt.

Das bedeutet, dass ein Instrumentierungstest sich nicht in das Android-Framework, also den Systemserver, einfügen kann, um ausgeführt zu werden. Zum Testen des Android-Frameworks kann der Testcode nur öffentliche APIs oder solche aufrufen, die über die Android Interface Definition Language AIDL im Quellbaum der Plattform verfügbar sind. Für diese Kategorie von Tests ist es nicht sinnvoll, auf ein bestimmtes Paket auszurichten. 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 den Anforderungen können Testanwendungspakete in dieser Kategorie auch Folgendes:

  • Bündeln Sie Aktivitäten, die für Tests erforderlich sind.
  • Geben Sie die Nutzer-ID an das System weiter.
  • Sie müssen mit dem Plattformschlüssel signiert sein.
  • Sie müssen mit der Framework-Quelle und nicht mit dem öffentlichen SDK kompiliert werden.

Diese Kategorie von Instrumentierungstests wird manchmal als „Selbstinstrumentierung“ bezeichnet. Hier sind einige Beispiele für Tests mit Selbstinstrumentierung im Plattformquellcode:

In diesem Beispiel wird ein neuer Instrumentierungstest mit dem Zielpaket erstellt, das auf das eigene Testanwendungspaket festgelegt ist. In diesem Leitfaden wird der folgende Test als Beispiel verwendet:

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

Quellspeicherort auswählen

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

Angenommen, der Stammverzeichnis für den Quellcode Ihrer Komponente befindet sich unter <component source root>. Die meisten Komponenten haben darunter 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 völlig neuen Test hinzufügen, müssen Sie wahrscheinlich das Verzeichnis tests neben Ihrer Komponente src erstellen und mit Inhalt füllen.

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

Unabhängig von der Struktur füllen Sie das Verzeichnis tests oder das neu erstellte Unterverzeichnis mit Dateien, die denen im Verzeichnis instrumentation in der Beispieländerung in Gerrit ähneln. Die Details der 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 mit dem Core-MakefileBUILD_PACKAGE einfügen möchten, stellen Sie sie neben der DateiAndroid.mk für Ihr Testmodul bereit.

Wenn Sie mit der Datei AndroidManifest.xml nicht vertraut sind, lesen Sie die Übersicht zum App-Manifest.

Hier sehen Sie eine AndroidManifest.xml-Beispieldatei:

<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 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, die das Android-Anwendungsframework zur Identifizierung einer Anwendung (oder in diesem Kontext: Ihrer Testanwendung) verwendet. Jeder Nutzer im System kann nur eine Anwendung mit diesem Paketnamen installieren.

Außerdem entspricht dieses package-Attribut dem, was ComponentName#getPackageName() zurückgibt, und dem, das Sie für die Interaktion mit verschiedenen pm-Unterbefehlen verwenden. Verwenden Sie adb shell.

Der Paketname hat zwar in der Regel denselben Stil wie ein Java-Paketname, hat aber tatsächlich nur sehr wenig damit zu tun. Das bedeutet, dass Ihr Anwendungs- oder Testpaket Klassen mit beliebigen Paketnamen enthalten kann. Sie können aber auch den Namen des Java-Pakets der obersten Ebene in Ihrer Anwendung oder Ihrem Test mit dem Namen des Anwendungspakets identisch machen.

android:sharedUserId="android.uid.system"

Damit wird deklariert, dass dieser APK-Datei bei der Installation dieselbe Nutzer-ID, d.h. Laufzeitidentität, wie der Kernplattform zugewiesen werden soll. Das hängt davon ab, ob das APK mit demselben Zertifikat wie die Kernplattform signiert ist (siehe LOCAL_CERTIFICATE im vorherigen Abschnitt). Es handelt sich jedoch um unterschiedliche Konzepte:

  • Einige Berechtigungen oder APIs sind signaturgeschützt. Dafür ist dasselbe Signaturzertifikat erforderlich.
  • Für einige Berechtigungen oder APIs ist die system-Nutzeridentität des Aufrufers erforderlich. Dazu muss das aufrufende Paket 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 Instrumentation-Tests erforderlich, da die zugehörigen Klassen in einer separaten Framework-JAR-Bibliotheksdatei enthalten sind. Daher sind zusätzliche Klassenpfadeinträge erforderlich, wenn das Testpaket vom Anwendungsframework aufgerufen wird.

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

Möglicherweise haben Sie bemerkt, dass targetPackage hier genauso deklariert wird wie das Attribut package, das im manifest-Tag dieser Datei deklariert wird. Wie in den Grundlagen zum Testen erwähnt, sind diese Instrumentierungstests in der Regel für das Testen von Framework-APIs vorgesehen. Daher ist es nicht sehr sinnvoll, wenn sie ein bestimmtes Zielanwendungspaket haben, das nicht das eigene ist.

Einfache Konfigurationsdatei

Jedes neue Testmodul muss eine Konfigurationsdatei haben, um das Build-System mit Modulmetadaten, Compile-Time-Abhängigkeiten und Verpackungsanweisungen zu versorgen. In den meisten Fällen ist die Soong-basierte Blueprint-Dateioption ausreichend. Weitere Informationen finden Sie unter Einfache Testkonfiguration.

Komplexe Konfigurationsdatei

Für diese komplexeren Fälle müssen Sie auch eine Testkonfigurationsdatei für die Testumgebung 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. Ein Beispiel finden Sie 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 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, die Datei „HelloWorldTests.apk“ mithilfe eines angegebenen „target_preparer“ auf dem Zielgerät zu installieren. Entwicklern stehen in Trade Federation viele Zielvorbereiter zur Verfügung, mit denen sie dafür sorgen können, dass das Gerät vor der Ausführung des Tests 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>

Damit wird die Trade Federation-Testklasse angegeben, die zum Ausführen des Tests verwendet werden soll. Außerdem wird das Paket auf dem Gerät übergeben, das ausgeführt werden soll, sowie das Test-Runner-Framework, in diesem Fall JUnit.

Weitere Informationen finden Sie unter Testmodulkonfigurationen.

JUnit4-Funktionen

Wenn Sie die android-support-test-Bibliothek als Testrunner verwenden, können Sie neue Testklassen im JUnit4-Stil übernehmen. Die Beispiel-Gerrit-Änderung enthält einige sehr einfache Anwendungsbeispiele für die Funktionen. Ein Beispiel finden Sie unter /platform_testing/tests/example/instrumentation/src/android/test/example/helloworld/HelloWorldTest.java.

Testmuster sind in der Regel spezifisch für Komponententeams. Es gibt jedoch einige allgemein nützliche Nutzungsmuster.

@RunWith(JUnit4.class)
public class HelloWorldTest {

Ein wesentlicher Unterschied zu JUnit4 besteht darin, dass Tests nicht mehr von einer gemeinsamen Basistestklasse abgeleitet werden müssen. Stattdessen schreiben Sie Tests in einfachen Java-Klassen und verwenden Annotationen, um bestimmte Testkonfigurationen und Einschränkungen anzugeben. In diesem Beispiel wird angegeben, 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 Annotationen @Before und @After werden von JUnit4 für Methoden verwendet, um die Einrichtung vor dem Test und die Bereinigung nach dem Test durchzuführen. Die Annotationen @BeforeClass und @AfterClass werden von JUnit4 für Methoden verwendet, um vor der Ausführung aller Tests in einer Testklasse die Einrichtung und danach die Bereinigung durchzuführen. Die Einrichtungs- und Bereinigungsmethoden für den Klassenbereich müssen statisch sein. Im Gegensatz zu früheren Versionen von JUnit müssen Testmethoden nicht mehr mit test beginnen. Stattdessen muss jede von ihnen mit @Test annotiert werden. Wie gewohnt müssen Testmethoden öffentlich sein, keinen Rückgabewert deklarieren, keine Parameter annehmen und können Ausnahmen auslösen.

Zugriff auf die Instrumentierungsklasse

Obwohl dies nicht im einfachen „Hello World“-Beispiel behandelt wird, ist es ziemlich üblich, dass für einen Android-Test Zugriff auf eine Instrumentation-Instanz erforderlich ist. Dies ist die zentrale API-Schnittstelle, die Zugriff auf Anwendungskontexte, Test-APIs für den Aktivitätslebenszyklus und mehr bietet.

Da für JUnit4-Tests keine gemeinsame Basisklasse mehr erforderlich ist, ist es nicht mehr notwendig, eine Instrumentation-Instanz über InstrumentationTestCase#getInstrumentation() abzurufen. Stattdessen wird sie vom neuen Test-Runner über InstrumentationRegistry verwaltet, in dem die vom Instrumentierungs-Framework erstellte kontextbezogene und umgebungsbezogene Einrichtung gespeichert wird.

Um auf die Instanz der Klasse Instrumentation zuzugreifen, rufen Sie einfach die statische Methode getInstrumentation() für die 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 stärkere Anpassung erfordern, folgen Sie der Anleitung zur Instrumentierung.