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, dh das Paket android , da dies zu der paradoxen Situation führen würde, dass das Android-Framework neu gestartet werden müsste, das die Systemfunktionen einschließlich der Instrumentierung unterstützt selbst.

Dies bedeutet, dass sich ein Instrumentierungstest nicht selbst zur Ausführung in das Android-Framework, auch bekannt als Systemserver, einschleusen kann. Um das Android-Framework zu testen, kann der Testcode nur öffentliche API-Oberflächen aufrufen oder solche, die über die im Quellbaum der Plattform verfügbare Android Interface Definition Language AIDL verfügbar gemacht werden. Für diese Kategorie von Tests ist es nicht sinnvoll, auf ein bestimmtes Paket abzuzielen. Daher ist es üblich, dass solche Instrumentierungen so deklariert werden, dass sie auf ein eigenes Testanwendungspaket abzielen, wie im eigenen <manifest> -Tag von AndroidManifest.xml .

Testapplikationspakete dieser Kategorie können je nach Anforderung auch:

  • Bündeln Sie Aktivitäten, die zum Testen benötigt werden.
  • Teilen Sie die Benutzer-ID mit dem System.
  • Mit dem Plattformschlüssel signieren.
  • 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 Selbstinstrumentierungstests in der Plattformquelle:

Das hier behandelte Beispiel schreibt einen neuen Instrumentierungstest mit einem Zielpaket, das in einem eigenen Testanwendungspaket festgelegt ist. Dieser Leitfaden verwendet den folgenden Test als Beispiel:

Es wird empfohlen, zuerst den Code zu durchsuchen, um einen groben Eindruck zu bekommen, bevor Sie fortfahren.

Entscheidung für einen Quellort

In der Regel verfügt Ihr Team bereits über ein etabliertes Muster von Stellen zum Einchecken von Code und Stellen zum Hinzufügen von Tests. Die meisten Teams besitzen ein einzelnes Git-Repository oder teilen sich eines mit anderen Teams, haben aber ein dediziertes Unterverzeichnis, das den Quellcode der Komponenten enthält.

Angenommen, der Stammspeicherort für Ihre Komponentenquelle ist <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 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. Und in diesem Fall müssen Sie unter tests ein neues Unterverzeichnis erstellen.

Unabhängig von der Struktur werden Sie am Ende das tests -Verzeichnis oder das neu erstellte Unterverzeichnis mit Dateien füllen, die denen ähneln, die sich im instrumentation -Verzeichnis in der Beispiel-Gerrit-Änderung befinden. In den folgenden Abschnitten werden weitere Einzelheiten zu jeder Datei erläutert.

Manifest-Datei

Genau wie eine normale Anwendung benötigt jedes Instrumentierungstestmodul eine Manifestdatei. Wenn Sie die Datei als AndroidManifest.xml und neben Android.mk für Ihr Testmodul bereitstellen, wird sie automatisch vom Kern-Makefile BUILD_PACKAGE .

Bevor Sie fortfahren, wird dringend empfohlen, zuerst die App-Manifest-Übersicht durchzugehen .

Dies gibt einen Überblick über grundlegende Komponenten einer Manifestdatei und deren Funktionalitäten. Siehe das Beispiel unter platform_testing/tests/example/instrumentation/AndroidManifest.xml .

Der Einfachheit halber ist hier ein Schnappschuss enthalten:

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

    <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 Anwendungspaketname: 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 verwenden würden, um mit verschiedenen pm sub-Befehlen über adb shell zu interagieren.

Bitte beachten Sie auch, 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, obwohl Sie sich andererseits für die Einfachheit entscheiden und Ihren Java-Paketnamen der obersten Ebene in Ihrer Anwendung oder Ihrem Test identisch mit dem Namen des Anwendungspakets haben könnten.

android:sharedUserId="android.uid.system"

Dies erklärt, dass dieser apk zum Zeitpunkt der Installation dieselbe Benutzer-ID, dh Laufzeitidentität, wie der Kernplattform gewährt werden sollte. Beachten Sie, dass dies davon abhängt, dass die apk mit demselben Zertifikat wie die Kernplattform signiert ist (siehe LOCAL_CERTIFICATE im obigen Abschnitt), es handelt sich jedoch um unterschiedliche Konzepte:

  • Einige Berechtigungen oder APIs sind signaturgeschützt, was dasselbe Signaturzertifikat erfordert
  • Einige Berechtigungen oder APIs erfordern die system des Aufrufers, was erfordert, dass das aufrufende Paket die Benutzer-ID mit system teilt, wenn es sich um ein separates Paket von der Kernplattform selbst handelt
<uses-library android:name="android.test.runner" />

Dies ist für alle Instrumentationstests 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"

Sie haben vielleicht bemerkt, dass das targetPackage hier als dasselbe wie das package deklariert ist, das im manifest -Tag dieser Datei deklariert ist. Wie in den Testgrundlagen erwähnt, ist diese Kategorie von Instrumentierungstests normalerweise 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 Kompilierzeit und Paketierungsanweisungen 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 die Android-Testumgebung Trade Federation schreiben.

Die Testkonfiguration kann spezielle Geräteeinrichtungsoptionen und Standardargumente angeben, um die Testklasse bereitzustellen. Siehe das Beispiel unter /platform_testing/tests/example/instrumentation/AndroidTest.xml .

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 mithilfe eines angegebenen target_preparer auf dem Zielgerät zu installieren. Entwicklern in der Trade Federation stehen viele Target-Vorbereiter 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 und dem Test-Runner-Framework, in diesem Fall JUnit.

Weitere Informationen finden Sie unter Testmodulkonfigurationen .

JUnit4-Funktionen

Die Verwendung android-support-test Bibliothek als Test-Runner ermöglicht die Übernahme neuer Testklassen im JUnit4-Stil, und die Beispiel-Gerrit-Änderung enthält eine sehr grundlegende Verwendung ihrer Funktionen. Siehe das Beispiel unter /platform_testing/tests/example/instrumentation/src/android/test/example/helloworld/HelloWorldTest.java .

Während Testmuster normalerweise spezifisch für Komponententeams sind, gibt es einige allgemein nützliche Verwendungsmuster.

@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 Anmerkungen, 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 bei Methoden von JUnit4 verwendet, um Pre-Test-Setup und Post-Test-Teardown durchzuführen. In ähnlicher Weise werden die Annotationen @BeforeClass und @AfterClass für Methoden von JUnit4 verwendet, um das Setup vor der Ausführung aller Tests in einer Testklasse und danach den Teardown durchzuführen. Beachten Sie, dass die Setup- und Teardown-Methoden im Klassenbereich statisch sein müssen. Anders als in früheren Versionen von JUnit müssen die Testmethoden den Methodennamen nicht mehr mit test beginnen, sondern müssen alle mit @Test kommentiert werden. Wie üblich müssen Testmethoden öffentlich sein, keinen Rückgabewert deklarieren, keine Parameter annehmen und dürfen Ausnahmen auslösen.

Wichtig : Die Testmethoden selbst sind mit der Annotation @Test versehen; und beachten Sie, dass Tests, die über APCT ausgeführt werden sollen, mit Testgrößen kommentiert werden müssen: die kommentierte Beispielmethode testHelloWorld als @SmallTest . Die Anmerkung kann im Methodenbereich oder im Klassenbereich angewendet werden.

Zugriff auf instrumentation

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

Da die JUnit4-Tests keine gemeinsame Basisklasse mehr erfordern, ist es nicht mehr erforderlich, die Instrumentation -Instanz über InstrumentationTestCase#getInstrumentation() , stattdessen verwaltet der neue Test Runner sie über InstrumentationRegistry , wo die vom Instrumentation-Framework erstellte Kontext- und Umgebungseinrichtung gespeichert wird.

Um auf die Instanz der Instrumentation -Klasse zuzugreifen, rufen Sie einfach die statische Methode getInstrumentation() in der InstrumentationRegistry -Klasse auf:

Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation()

Erstellen und testen Sie lokal

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

Für komplexere Fälle, die umfangreichere Anpassungen erfordern, befolgen Sie die Anweisungen zur Instrumentierung .