Beispiel für einen End-to-End-TF-Test

In dieser Anleitung wird beschrieben, wie Sie eine „Hello World“-Testkonfiguration für Trade Federation (Tradefed oder TF) erstellen. Außerdem erhalten Sie eine praktische Einführung in das TF-Framework. Sie beginnen mit einer Entwicklungsumgebung, erstellen eine einfache Konfiguration und fügen Funktionen hinzu.

In der Anleitung wird der Testentwicklungsprozess in Form von Übungen dargestellt, die jeweils aus mehreren Schritten bestehen und zeigen, wie Sie Ihre Konfiguration erstellen und nach und nach optimieren. Der gesamte Beispielcode, der für die Testkonfiguration erforderlich ist, wird bereitgestellt. Der Titel jeder Übung ist mit einem Buchstaben gekennzeichnet, der die an diesem Schritt beteiligten Rollen beschreibt:

  • D für Entwickler
  • I für Integrator
  • R für Test-Ausführer

Nach Abschluss der Anleitung haben Sie eine funktionierende TF-Konfiguration und verstehen viele wichtige Konzepte im TF-Framework.

Trade Federation einrichten

Weitere Informationen zum Einrichten der TF-Entwicklungsumgebung finden Sie unter Rechner einrichten. Im weiteren Verlauf dieser Anleitung wird davon ausgegangen, dass Sie eine Shell geöffnet haben, die für die TF-Umgebung initialisiert wurde.

Zur Vereinfachung wird in dieser Anleitung gezeigt, wie der TF-Framework-Kernbibliothek eine Konfiguration und ihre Klassen hinzugefügt werden. Dies kann auf die Entwicklung von Modulen außerhalb des Quellbaums ausgeweitet werden, indem Sie das Tradefed-JAR kompilieren und dann Ihre Module mit diesem JAR kompilieren.

Testklasse erstellen (D)

Erstellen wir einen Hello World-Test, der nur eine Nachricht auf stdout ausgibt. Ein Tradefed-Test implementiert in der Regel die Schnittstelle IRemoteTest. Hier ist eine Implementierung für den HelloWorldTest:

package com.android.tradefed.example;

import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.testtype.IRemoteTest;

public class HelloWorldTest implements IRemoteTest {
    @Override
    public void run(TestInformation testInfo, ITestInvocationListener listener) throws DeviceNotAvailableException {
        CLog.i("Hello, TF World!");
    }
}

Speichern Sie diesen Beispielcode unter <tree>/tools/tradefederation/core/src/com/android/tradefed/example/HelloWorldTest.java und erstellen Sie Tradefed über die Shell neu:

m -jN

Im obigen Beispiel wird CLog.i verwendet, um die Ausgabe an die Konsole umzuleiten. Weitere Informationen zum Anmelden in der Trade Federation findest du unter Anmelden (D, I, R).

Wenn der Build nicht erfolgreich ist, lesen Sie den Hilfeartikel Computereinrichtung, um sicherzustellen, dass Sie keinen Schritt übersprungen haben.

Konfiguration erstellen (I)

Trade Federation-Tests werden ausführbar, indem eine Konfiguration erstellt wird. Dabei handelt es sich um eine XML-Datei, die TradeFed anweist, welche Tests ausgeführt werden sollen, sowie welche anderen Module und in welcher Reihenfolge.

Erstellen wir eine neue Konfiguration für unseren HelloWorldTest. Achten Sie auf den vollständigen Klassennamen von HelloWorldTest:

<configuration description="Runs the hello world test">
    <test class="com.android.tradefed.example.HelloWorldTest" />
</configuration>

Speichern Sie diese Daten in einer helloworld.xml-Datei an einer beliebigen Stelle im lokalen Dateisystem (z.B. /tmp/helloworld.xml). TF analysiert die Konfigurations-XML-Datei (config), lädt die angegebene Klasse mithilfe der Reflection, erstellt eine Instanz davon, wandelt sie in eine IRemoteTest um und ruft die run-Methode auf.

Konfiguration ausführen (R)

Starten Sie die TradeFed-Konsole über Ihre Shell:

tradefed.sh

Prüfen Sie, ob ein Gerät mit dem Hostcomputer verbunden und für Tradefed sichtbar ist:

tf> list devices
Serial            State      Product   Variant   Build   Battery
004ad9880810a548  Available  mako      mako      JDQ39   100

Konfigurationen können mit dem Befehl run <config> ausgeführt werden. Hier sind ein paar Vorschläge:

tf> run /tmp/helloworld.xml
05-12 13:19:36 I/TestInvocation: Starting invocation for target stub on build 0 on device 004ad9880810a548
Hello, TF World!

Im Terminal sollte die Ausgabe „Hello, TF World!“ zu sehen sein.

Sie können prüfen, ob ein Befehl beendet wurde, indem Sie list invocations oder l i in der Befehlszeile eingeben. Es sollte nichts ausgegeben werden. Wenn Befehle gerade ausgeführt werden, werden sie so angezeigt:

tf >l i
Command Id  Exec Time  Device       State
10          0m:00      [876X00GNG]  running stub on build(s) 'BuildInfo{bid=0, target=stub, serial=876X00GNG}'

Fügen Sie die Konfiguration dem Pfad hinzu (D, I, R)

Zur Vereinfachung der Bereitstellung können Sie Konfigurationen auch in den Tradefed-JARs selbst bündeln. Tradefed erkennt automatisch alle Konfigurationen, die im Pfad config abgelegt sind.

Verschieben Sie zur Veranschaulichung die Datei helloworld.xml in die TradeFed-Kernbibliothek (<tree>/tools/tradefederation/core/res/config/example/helloworld.xml). Erstellen Sie TradeFed neu, starten Sie die TradeFed-Konsole neu und bitten Sie TradeFed, die Liste der Konfigurationen aus dem Klassenpfad anzuzeigen:

tf> list configs
[…]
example/helloworld: Runs the hello world test

Sie können die helloworld-Konfiguration jetzt mit folgendem Befehl ausführen:

tf> run example/helloworld
05-12 13:21:21 I/TestInvocation: Starting invocation for target stub on build 0 on device 004ad9880810a548
Hello, TF World!

Mit einem Gerät interagieren (D, R)

Bisher passiert mit unserem HelloWorldTest nichts Interessantes. Tradefed ist auf Tests mit Android-Geräten spezialisiert. Fügen Sie dem Test also ein Android-Gerät hinzu.

Tests können eine Referenz auf ein Android-Gerät mithilfe von TestInformation abrufen, die vom Framework bereitgestellt wird, wenn die Methode IRemoteTest#run aufgerufen wird.

Ändern wir die Drucknachricht von HelloWorldTest, damit die Seriennummer des Geräts angezeigt wird:

@Override
public void run(TestInformation testInfo, ITestInvocationListener listener) throws DeviceNotAvailableException {
    CLog.i("Hello, TF World! I have device " + testInfo.getDevice().getSerialNumber());
}

Erstellen Sie jetzt tradefed neu und prüfen Sie die Geräteliste:

tradefed.sh
tf> list devices
Serial            State      Product   Variant   Build   Battery
004ad9880810a548  Available  mako      mako      JDQ39   100

Notieren Sie sich die Seriennummer, die als Verfügbar aufgeführt ist. Das ist das Gerät, das HelloWorld zugewiesen werden soll:

tf> run example/helloworld
05-12 13:26:18 I/TestInvocation: Starting invocation for target stub on build 0 on device 004ad9880810a548
Hello, TF World! I have device 004ad9880810a548

Die neue Druckmeldung mit der Seriennummer des Geräts sollte angezeigt werden.

Testergebnisse senden (D)

IRemoteTest meldet Ergebnisse, indem Methoden für die ITestInvocationListener-Instanz aufgerufen werden, die der #run-Methode übergeben wird. Das TF-Framework selbst ist für die Meldung des Beginns (über ITestInvocationListener#invocationStarted) und des Endes (über ITestInvocationListener#invocationEnded) jeder Aufrufe verantwortlich.

Ein Testlauf ist eine logische Sammlung von Tests. Für die Berichterstellung zu Testergebnissen ist IRemoteTest verantwortlich. Sie muss den Beginn eines Testlaufs, den Beginn und das Ende jedes Tests sowie das Ende des Testlaufs melden.

Hier sehen Sie, wie die Implementierung von HelloWorldTest mit einem einzelnen fehlgeschlagenen Testergebnis aussehen könnte.

@Override
public void run(TestInformation testInfo, ITestInvocationListener listener) throws DeviceNotAvailableException {
    CLog.i("Hello, TF World! I have device " + testInfo.getDevice().getSerialNumber());

    TestDescription testId = new TestDescription("com.example.TestClassName", "sampleTest");
    listener.testRunStarted("helloworldrun", 1);
    listener.testStarted(testId);
    listener.testFailed(testId, "oh noes, test failed");
    listener.testEnded(testId, Collections.emptyMap());
    listener.testRunEnded(0, Collections.emptyMap());
}

TF enthält mehrere IRemoteTest-Implementierungen, die Sie wiederverwenden können, anstatt eigene von Grund auf neu zu erstellen. Mit InstrumentationTest können Sie beispielsweise die Tests einer Android-Anwendung aus der Ferne auf einem Android-Gerät ausführen, die Ergebnisse analysieren und an die ITestInvocationListener weiterleiten. Weitere Informationen finden Sie unter Testtypen.

Testergebnisse für Ladengeschäfte (I)

Die Standardimplementierung des Testlisteners für eine TF-Konfiguration ist TextResultReporter, der die Ergebnisse einer Aufrufung in stdout druckt. Führen Sie zur Veranschaulichung die Konfiguration „HelloWorldTest“ aus dem vorherigen Abschnitt aus:

./tradefed.sh
tf> run example/helloworld
04-29 18:25:55 I/TestInvocation: Invocation was started with cmd: /tmp/helloworld.xml
04-29 18:25:55 I/TestInvocation: Starting invocation for 'stub' with '[ BuildInfo{bid=0, target=stub, serial=876X00GNG} on device '876X00GNG']
04-29 18:25:55 I/HelloWorldTest: Hello, TF World! I have device 876X00GNG
04-29 18:25:55 I/InvocationToJUnitResultForwarder: Running helloworldrun: 1 tests
04-29 18:25:55 W/InvocationToJUnitResultForwarder:
Test com.example.TestClassName#sampleTest failed with stack:
 oh noes, test failed
04-29 18:25:55 I/InvocationToJUnitResultForwarder: Run ended in 0 ms

Wenn Sie die Ergebnisse einer Aufrufung an anderer Stelle speichern möchten, z. B. in einer Datei, geben Sie in Ihrer Konfiguration eine benutzerdefinierte ITestInvocationListener-Implementierung mit dem result_reporter-Tag an.

TF enthält auch den XmlResultReporter-Listener, der Testergebnisse in einer XML-Datei in einem Format schreibt, das dem des ant-JUnit-XML-Writers ähnelt. Wenn Sie den result_reporter in der Konfiguration angeben möchten, bearbeiten Sie die …/res/config/example/helloworld.xml-Konfiguration:

<configuration description="Runs the hello world test">
    <test class="com.android.tradefed.example.HelloWorldTest" />
    <result_reporter class="com.android.tradefed.result.XmlResultReporter" />
</configuration>

Erstellen Sie jetzt tradefed neu und führen Sie das Hello World-Beispiel noch einmal aus:

tf> run example/helloworld
05-16 21:07:07 I/TestInvocation: Starting invocation for target stub on build 0 on device 004ad9880810a548
Hello, TF World! I have device 004ad9880810a548
05-16 21:07:07 I/XmlResultReporter: Saved device_logcat log to /tmp/0/inv_2991649128735283633/device_logcat_6999997036887173857.txt
05-16 21:07:07 I/XmlResultReporter: Saved host_log log to /tmp/0/inv_2991649128735283633/host_log_6307746032218561704.txt
05-16 21:07:07 I/XmlResultReporter: XML test result file generated at /tmp/0/inv_2991649128735283633/test_result_536358148261684076.xml. Total tests 1, Failed 1, Error 0

Beachten Sie die Protokollmeldung, dass eine XML-Datei generiert wurde. Die generierte Datei sollte so aussehen:

<?xml version='1.0' encoding='UTF-8' ?>
<testsuite name="stub" tests="1" failures="1" errors="0" time="9" timestamp="2011-05-17T04:07:07" hostname="localhost">
  <properties />
  <testcase name="sampleTest" classname="com.example.TestClassName" time="0">
    <failure>oh noes, test failed
    </failure>
  </testcase>
</testsuite>

Sie können auch eigene benutzerdefinierte Aufruf-Listener schreiben. Sie müssen lediglich die Schnittstelle ITestInvocationListener implementieren.

Tradefed unterstützt mehrere Aufrufempfänger, sodass Sie Testergebnisse an mehrere unabhängige Ziele senden können. Geben Sie dazu einfach mehrere <result_reporter>-Tags in Ihrer Konfiguration an.

Logging-Einrichtungen (D, I, R)

Die Logging-Funktionen von TF bieten folgende Möglichkeiten:

  1. Protokolle vom Gerät erfassen (auch als Geräte-Logcat bezeichnet)
  2. Protokolle des Trade Federation-Frameworks auf dem Hostcomputer aufzeichnen (Hostprotokoll)

Das TF-Framework erfasst automatisch das Logcat vom zugewiesenen Gerät und sendet es zur Verarbeitung an den Aufrufempfänger. XmlResultReporter speichert dann das erfasste Geräte-Logcat als Datei.

TF-Hostprotokolle werden mit dem CLog-Wrapper für die ddmlib-Log-Klasse gemeldet. Konvertieren wir den vorherigen System.out.println-Aufruf in HelloWorldTest in einen CLog-Aufruf:

@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
    CLog.i("Hello, TF World! I have device %s", getDevice().getSerialNumber());

CLog verarbeitet Stringinterpolationen direkt, ähnlich wie String.format. Wenn Sie TF neu erstellen und wieder ausführen, sollte die Protokollmeldung auf stdout angezeigt werden:

tf> run example/helloworld
…
05-16 21:30:46 I/HelloWorldTest: Hello, TF World! I have device 004ad9880810a548
…

Standardmäßig gibt tradefed Host-Lognachrichten in stdout aus. TF enthält auch eine Protokollimplementierung, die Meldungen in eine Datei schreibt: FileLogger. Wenn Sie die Dateiprotokollierung hinzufügen möchten, fügen Sie der Konfiguration ein logger-Tag hinzu und geben Sie den vollständigen Klassennamen von FileLogger an:

<configuration description="Runs the hello world test">
    <test class="com.android.tradefed.example.HelloWorldTest" />
    <result_reporter class="com.android.tradefed.result.XmlResultReporter" />
    <logger class="com.android.tradefed.log.FileLogger" />
</configuration>

Erstellen Sie das Beispiel „helloworld“ jetzt noch einmal und führen Sie es aus:

tf >run example/helloworld
…
05-16 21:38:21 I/XmlResultReporter: Saved device_logcat log to /tmp/0/inv_6390011618174565918/device_logcat_1302097394309452308.txt
05-16 21:38:21 I/XmlResultReporter: Saved host_log log to /tmp/0/inv_6390011618174565918/host_log_4255420317120216614.txt
…

Die Protokollmeldung gibt den Pfad zum Hostprotokoll an, das die Protokollmeldung „HelloWorldTest“ enthalten sollte:

more /tmp/0/inv_6390011618174565918/host_log_4255420317120216614.txt

Beispielausgabe:

…
05-16 21:38:21 I/HelloWorldTest: Hello, TF World! I have device 004ad9880810a548

Umgangsoptionen (D, I, R)

Objekte, die aus einer TF-Konfiguration geladen werden (Konfigurationsobjekte), können über die Anmerkung @Option auch Daten aus Befehlszeilenargumenten empfangen.

Dazu muss die Klasse des Konfigurationsobjekts die Annotation @Option auf ein Mitgliedsfeld anwenden und ihm einen eindeutigen Namen geben. So kann der Wert des Mitgliedsfelds über eine Befehlszeilenoption ausgefüllt werden. Außerdem wird diese Option automatisch in die Konfigurationshilfe aufgenommen.

Hinweis:Nicht alle Feldtypen werden unterstützt. Eine Beschreibung der unterstützten Typen finden Sie unter OptionSetter.

Fügen wir HelloWorldTest eine @Option hinzu:

@Option(name="my_option",
        shortName='m',
        description="this is the option's help text",
        // always display this option in the default help text
        importance=Importance.ALWAYS)
private String mMyOption = "thisisthedefault";

Fügen wir als Nächstes eine Protokollmeldung hinzu, um den Wert der Option in HelloWorldTest anzuzeigen, damit wir sehen können, ob sie richtig empfangen wurde:

@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
    …
    CLog.logAndDisplay(LogLevel.INFO, "I received option '%s'", mMyOption);

Erstellen Sie abschließend TF neu und führen Sie „helloworld“ aus. Sie sollten eine Protokollmeldung mit dem Standardwert my_option sehen:

tf> run example/helloworld
…
05-24 18:30:05 I/HelloWorldTest: I received option 'thisisthedefault'

Werte über die Befehlszeile übergeben

Geben Sie einen Wert für my_option ein. my_option sollte dann mit diesem Wert ausgefüllt werden:

tf> run example/helloworld --my_option foo
…
05-24 18:33:44 I/HelloWorldTest: I received option 'foo'

TF-Konfigurationen enthalten auch ein Hilfesystem, das automatisch Hilfetext für @Option-Felder anzeigt. Probieren Sie es jetzt aus. Sie sollten den Hilfetext für my_option sehen:

tf> run example/helloworld --help
Printing help for only the important options. To see help for all options, use the --help-all flag

  cmd_options options:
    --[no-]help          display the help text for the most important/critical options. Default: false.
    --[no-]help-all      display the full help text for all options. Default: false.
    --[no-]loop          keep running continuously. Default: false.

  test options:
    -m, --my_option      this is the option's help text Default: thisisthedefault.

  'file' logger options:
    --log-level-display  the minimum log level to display on stdout. Must be one of verbose, debug, info, warn, error, assert. Default: error.

Beachten Sie die Meldung „Nur die wichtigen Optionen drucken“. Um die Optionenhilfe übersichtlich zu gestalten, verwendet TF das Attribut Option#importance, um zu bestimmen, ob ein bestimmter Hilfetext für das Feld @Option angezeigt werden soll, wenn --help angegeben ist. --help-all zeigt immer Hilfe zu allen @Option-Feldern an, unabhängig von ihrer Wichtigkeit. Weitere Informationen finden Sie unter Option.Importance.

Werte aus einer Konfiguration übergeben

Sie können auch einen Optionswert in der Konfiguration angeben, indem Sie ein <option name="" value="">-Element hinzufügen. Testen Sie es mit helloworld.xml:

<test class="com.android.tradefed.example.HelloWorldTest" >
    <option name="my_option" value="fromxml" />
</test>

Wenn Sie helloworld noch einmal erstellen und ausführen, sollte jetzt diese Ausgabe erscheinen:

05-24 20:38:25 I/HelloWorldTest: I received option 'fromxml'

Die Konfigurationshilfe sollte ebenfalls aktualisiert werden, um den Standardwert von my_option anzugeben:

tf> run example/helloworld --help
  test options:
    -m, --my_option      this is the option's help text Default: fromxml.

Auch andere Konfigurationsobjekte in der helloworld-Konfiguration, z. B. FileLogger, akzeptieren Optionen. Die Option --log-level-display ist interessant, da sie die Protokolle filtert, die in stdout angezeigt werden. Zu Beginn des Tutorials haben Sie vielleicht die Meldung „Hello, TF World! Die Protokollmeldung „Gerät …“ wird nicht mehr auf stdout angezeigt, nachdem wir zu FileLogger gewechselt sind. Sie können die Detaillierung des Loggings in stdout erhöhen, indem Sie das Argument --log-level-display übergeben.

Wenn Sie das jetzt versuchen, sollte die Protokollmeldung „I have device“ (Ich habe ein Gerät) nicht nur in der Datei, sondern auch im stdout-Stream wieder angezeigt werden:

tf> run example/helloworld --log-level-display info
…
05-24 18:53:50 I/HelloWorldTest: Hello, TF World! I have device 004ad9880810a548

Das war's für heute.

Zur Erinnerung: Wenn Sie nicht weiterkommen, enthält der Trade Federation-Quellcode viele nützliche Informationen, die in der Dokumentation nicht enthalten sind. Wenn alles andere fehlschlägt, können Sie Ihre Frage in der Google-Gruppe android-platform stellen. Geben Sie dazu „Trade Federation“ in den Betreff der Nachricht ein.