Beispiel für selbstinstrumentierende Tests

Wenn ein Instrumentierungstest gestartet wird, wird sein Zielpaket mit eingefügtem Instrumentierungscode neu gestartet und zur Ausführung initiiert. Eine Ausnahme besteht darin, dass das Zielpaket hier nicht das Android-Anwendungsframework selbst sein kann, beispielsweise das Paket android , da dies zu der paradoxen Situation führt, dass das Android-Framework neu gestartet werden müsste, das die Systemfunktionen unterstützt, einschließlich die Instrumentierung selbst.

Das bedeutet, dass sich ein Instrumentierungstest nicht zur Ausführung in das Android-Framework, also den Systemserver, einschleusen kann. Um das Android-Framework zu testen, kann der Testcode nur öffentliche API-Oberflächen aufrufen oder solche, die über die im Plattformquellbaum verfügbare Android Interface Definition Language AIDL verfügbar gemacht werden. Für diese Testkategorie ist es nicht sinnvoll, auf ein bestimmtes Paket abzuzielen. Daher ist es üblich, dass solche Instrumentierungen als Ziel für ein eigenes Testanwendungspaket deklariert werden, wie im eigenen <manifest> -Tag von AndroidManifest.xml definiert.

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

  • Bündeln Sie die zum Testen erforderlichen Aktivitäten.
  • Teilen Sie die Benutzer-ID mit dem System.
  • Mit dem Plattformschlüssel signiert sein.
  • Muss anhand der Framework-Quelle und nicht anhand des öffentlichen SDK kompiliert werden.

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

Das hier behandelte Beispiel ist das Schreiben eines neuen Instrumentierungstests, bei dem das Zielpaket auf ein eigenes Testanwendungspaket eingestellt ist. Als Beispiel dient in diesem Leitfaden der folgende Test:

Es empfiehlt sich, zunächst den Code durchzublättern, um sich einen groben Eindruck zu verschaffen, bevor Sie fortfahren.

Entscheiden Sie sich für einen Quellstandort

Normalerweise verfügt Ihr Team bereits über ein etabliertes Muster an Orten zum Einchecken von Code und Orten zum Hinzufügen von Tests. Die meisten Teams besitzen ein einzelnes Git-Repository oder teilen eines mit anderen Teams, verfügen jedoch über ein eigenes Unterverzeichnis, das den Quellcode der Komponenten enthält.

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

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

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

Unabhängig von der Struktur werden Sie am Ende das tests oder das neu erstellte Unterverzeichnis mit Dateien füllen, die denen im instrumentation in der Beispiel-Gerrit-Änderung ähneln. Die Details jeder Datei werden später in diesem Dokument erläutert.

Manifestdatei

Wie bei einem App-Projekt erfordert jedes Instrumentierungstestmodul eine Manifestdatei namens AndroidManifest.xml . Um diese Datei mithilfe des BUILD_PACKAGE Kern-Makefiles automatisch einzubinden, stellen Sie diese Datei neben der Android.mk Datei für Ihr Testmodul bereit.

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

Im Folgenden finden Sie eine Beispieldatei 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 package ist der Name des Anwendungspakets: Dies ist die eindeutige Kennung, die das Android-Anwendungsframework verwendet, um eine Anwendung (oder in diesem Zusammenhang: Ihre Testanwendung) zu identifizieren. Jeder Benutzer im System kann nur eine Anwendung mit diesem Paketnamen installieren.

Darüber hinaus ist dieses package dasselbe wie das, was ComponentName#getPackageName() zurückgibt, und auch dasselbe, das Sie zur Interaktion mit verschiedenen pm Unterbefehlen verwenden würden, indem Sie adb shell verwenden.

Beachten Sie, dass der Paketname zwar normalerweise den gleichen Stil wie ein Java-Paketname hat, aber eigentlich nur sehr wenig damit zu tun hat. Mit anderen Worten: Ihr Anwendungs- (oder Test-)Paket kann Klassen mit beliebigen Paketnamen enthalten. Sie können sich jedoch auch für Einfachheit entscheiden und den Namen Ihres Java-Pakets der obersten Ebene in Ihrer Anwendung oder Ihrem Test mit dem Namen des Anwendungspakets identisch machen.

android:sharedUserId="android.uid.system"

Dadurch wird erklärt, dass dieser APK-Datei zum Zeitpunkt der Installation dieselbe Benutzer-ID, also Laufzeitidentität, wie der Kernplattform gewährt 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, was dasselbe Signaturzertifikat erfordert
  • Für einige Berechtigungen oder APIs ist die system des Aufrufers erforderlich. Dies erfordert, dass das aufrufende Paket die Benutzer-ID mit system teilt, wenn es sich um ein von der Kernplattform selbst getrenntes Paket 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 und daher zusätzliche Klassenpfadeinträge erforderlich sind, wenn das Testpaket vom Anwendungsframework aufgerufen wird.

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

Möglicherweise ist Ihnen aufgefallen, dass das targetPackage hier genauso deklariert ist wie das package , das im manifest Tag dieser Datei deklariert ist. Wie in den Testgrundlagen erwähnt, ist diese Kategorie von Instrumentierungstests in der Regel zum Testen von Framework-APIs gedacht. Daher ist es für sie nicht sehr sinnvoll, ein bestimmtes Zielanwendungspaket außer sich selbst zu haben.

Einfache Konfigurationsdatei

Jedes neue Testmodul muss über eine Konfigurationsdatei verfügen, um das Build-System mit Modulmetadaten, Abhängigkeiten zur Kompilierungszeit und Verpackungsanweisungen zu steuern. In den meisten Fällen ist die Soong-basierte Blueprint-Dateioption ausreichend. Einzelheiten finden Sie unter Einfache Testkonfiguration .

Komplexe Konfigurationsdatei

Für diese komplexeren Fälle müssen Sie auch eine Testkonfigurationsdatei für Androids Testumgebung Trade Federation schreiben.

Die Testkonfiguration kann spezielle Geräte-Setup-Optionen und Standardargumente zur Bereitstellung der Testklasse angeben. Sehen Sie sich das Beispiel unter /platform_testing/tests/example/instrumentation/AndroidTest.xml an.

Der Einfachheit halber ist hier ein Schnappschuss enthalten:

<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, HelloWorldTests.apk mit einem angegebenen target_preparer auf dem Zielgerät zu installieren. Den Entwicklern in Trade Federation stehen viele Zielvorbereiter zur Verfügung, mit denen sichergestellt werden kann, dass das Gerät vor der Testausführung ordnungsgemäß 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>

Dies gibt die Trade Federation-Testklasse an, die zum Ausführen des Tests verwendet werden soll, und übergibt das Paket auf dem auszuführenden Gerät sowie das Test-Runner-Framework, in diesem Fall JUnit.

Weitere Informationen finden Sie unter Testmodulkonfigurationen .

JUnit4-Funktionen

Die Verwendung android-support-test Bibliothek als Testläufer ermöglicht die Einführung neuer Testklassen im JUnit4-Stil, und die Beispiel-Gerrit-Änderung enthält einige sehr grundlegende Verwendungen ihrer Funktionen. Sehen Sie sich das Beispiel unter /platform_testing/tests/example/instrumentation/src/android/test/example/helloworld/HelloWorldTest.java an.

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

@RunWith(JUnit4.class)
public class HelloWorldTest {

Ein wesentlicher Unterschied in JUnit4 besteht darin, dass Tests nicht mehr von einer gemeinsamen Basistestklasse erben müssen; Stattdessen schreiben Sie Tests in einfachen Java-Klassen und verwenden Annotationen, um bestimmte Testeinstellungen und Einschränkungen anzugeben. In diesem Beispiel weisen wir an, 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 den Abbau nach dem Test durchzuführen. In ähnlicher Weise werden die Annotationen @BeforeClass und @AfterClass für Methoden von JUnit4 verwendet, um vor der Ausführung aller Tests in einer Testklasse ein Setup durchzuführen und anschließend den Abbau durchzuführen. Beachten Sie, dass die Setup- und Teardown-Methoden für den Klassenbereich statisch sein müssen. Was die Testmethoden betrifft, so müssen sie im Gegensatz zu früheren Versionen von JUnit den Methodennamen nicht mehr mit test beginnen, sondern jede von ihnen muss mit @Test annotiert werden. Wie üblich müssen Testmethoden öffentlich sein, keinen Rückgabewert deklarieren, keine Parameter annehmen und dürfen Ausnahmen auslösen.

Zugriff auf Instrumentierungsklassen

Auch wenn dies im einfachen „Hello World“-Beispiel nicht behandelt wird, ist es ziemlich üblich, dass ein Android-Test Zugriff auf Instrumentation erfordert: Dies ist die Kern-API-Schnittstelle, die Zugriff auf Anwendungskontexte, aktivitätslebenszyklusbezogene Test-APIs und mehr bietet.

Da für die JUnit4-Tests keine gemeinsame Basisklasse mehr erforderlich ist, ist es nicht mehr erforderlich, Instrumentation über InstrumentationTestCase#getInstrumentation() abzurufen. Stattdessen verwaltet der neue Testläufer sie über InstrumentationRegistry , wo vom Instrumentierungsframework erstellte Kontext- und Umgebungseinstellungen gespeichert werden.

Um auf die Instanz der Instrumentation Klasse zuzugreifen, rufen Sie einfach die statische Methode getInstrumentation() für InstrumentationRegistry Klasse auf:

Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation()

Lokal erstellen und testen

Für die häufigsten Anwendungsfälle verwenden Sie Atest .

Befolgen Sie bei komplexeren Fällen, die eine umfassendere Anpassung erfordern, die Anweisungen zur Instrumentierung .