Esempio di test TF end-to-end

Questo tutorial illustra la creazione di una configurazione di test "hello world" per Trade Federation (TradeFed o TF) e fornisce un'introduzione pratica al framework TF. A partire da un ambiente di sviluppo, creerai una configurazione semplice e aggiungerai funzionalità.

Il tutorial presenta il processo di sviluppo del test come un insieme di esercizi, ciascuno costituito da diversi passaggi, che mostrano come creare e perfezionare gradualmente la configurazione. Viene fornito tutto il codice di esempio necessario per completare la configurazione del test e il titolo di ogni esercizio è annotato con una lettera che descrive i ruoli coinvolti in quel passaggio:

  • D per sviluppatore
  • I per l'integratore
  • R per Test Runner

Al termine del tutorial, avrai una configurazione TF funzionante e avrai compreso molti concetti importanti del framework TF.

Configurare Trade Federation

Per informazioni dettagliate sulla configurazione dell'ambiente di sviluppo di TF, consulta Configurazione della macchina. Il resto di questo tutorial presuppone che tu abbia aperto una shell inizializzata per l'ambiente TF.

Per semplicità, questo tutorial illustra l'aggiunta di una configurazione e delle relative classi alla libreria di base del framework TF. Questa operazione può essere estesa allo sviluppo di moduli al di fuori dell'albero di origine compilando il JAR di TradeFed e poi compilando i moduli in base a quel JAR.

Crea una classe di test (D)

Creiamo un test di tipo "Hello World" che esamini un messaggio in stdout. Un test tradefed in genere implementa l'interfaccia IRemoteTest. Ecco un'implementazione per 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!");
    }
}

Salva questo codice di esempio in <tree>/tools/tradefederation/core/src/com/android/tradefed/example/HelloWorldTest.java e ricostruisci il pacchetto tradefed dalla shell:

m -jN

Tieni presente che CLog.i nell'esempio riportato sopra viene utilizzato per indirizzare l'output alla console. Ulteriori informazioni su come accedere a Trade Federation sono descritte in Logging (D, I, R).

Se la compilazione non va a buon fine, consulta la sezione Configurazione della macchina per assicurarti di non aver perso un passaggio.

Creare una configurazione (I)

I test di Trade Federation vengono resi eseguibili creando una configurazione, un file XML che indica a TradeFed quale test o quali test eseguire, nonché quali altri moduli eseguire e in quale ordine.

Creiamo una nuova configurazione per il nostro HelloWorldTest (tieni presente il nome completo della classe di HelloWorldTest):

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

Salva questi dati in un file helloworld.xml in un punto qualsiasi del file system locale (ad es. /tmp/helloworld.xml). TF analizzerà il file XML di configurazione (noto anche come config), caricherà la classe specificata utilizzando la riflessione, la eseguirà in un'istanza, la convertirà in un IRemoteTest e chiamerà il relativo metodo run.

Esegui la configurazione (R)

Dalla shell, avvia la console tradefed:

tradefed.sh

Assicurati che un dispositivo sia connesso alla macchina host e sia visibile a tradefed:

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

Le configurazioni possono essere eseguite utilizzando il comando run <config> console. Prova:

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!

Dovresti vedere l'output "Hello, TF World!" sul terminale.

Puoi verificare che l'esecuzione di un comando sia terminata utilizzando list invocations o l i nel prompt della console. Non dovrebbe essere stampato nulla. Se i comandi sono attualmente in esecuzione, vengono visualizzati come segue:

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}'

Aggiungi la configurazione al percorso di classe (D, I, R)

Per comodità di implementazione, puoi anche raggruppare le configurazioni nei JAR di tradefed. Tradefed riconosce automaticamente tutte le configurazioni inserite nelle cartelle config nel percorso di classe.

A titolo esemplificativo, sposta il file helloworld.xml nella libreria di base di tradefed (<tree>/tools/tradefederation/core/res/config/example/helloworld.xml). Ricostruisci tradefed, riavvia la console di tradefed e chiedi a tradefed di visualizzare l'elenco delle configurazioni dal percorso di classe:

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

Ora puoi eseguire la configurazione di helloworld utilizzando:

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!

Interagire con un dispositivo (D, R)

Finora, il nostro HelloWorldTest non fa nulla di interessante. La specialità di TradeFed è eseguire test utilizzando dispositivi Android, quindi aggiungi un dispositivo Android al test.

I test possono ottenere un riferimento a un dispositivo Android utilizzando TestInformation, fornito dal framework quando viene chiamato il metodo IRemoteTest#run.

Modifichiamo il messaggio di stampa di HelloWorldTest in modo da visualizzare il numero di serie del dispositivo:

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

Ora ricostruisci il tradefed e controlla l'elenco dei dispositivi:

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

Prendi nota del numero di serie indicato come Disponibile, ovvero il dispositivo che deve essere allocato a HelloWorld:

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

Dovresti vedere il nuovo messaggio di stampa che mostra il numero di serie del dispositivo.

Inviare i risultati del test (D)

IRemoteTest genera i risultati chiamando i metodi sull'istanza ITestInvocationListener fornita al metodo #run. Il framework TF stesso è responsabile di segnalare l'inizio (tramite ITestInvocationListener#invocationStarted) e la fine (tramite ITestInvocationListener#invocationEnded) di ogni chiamata.

Un esecuzione di test è una raccolta logica di test. Per segnalare i risultati dei test, IRemoteTest è responsabile della segnalazione dell'inizio di un'esecuzione del test, dell'inizio e della fine di ogni test e della fine dell'esecuzione del test.

Ecco come potrebbe essere l'implementazione di HelloWorldTest con un singolo risultato di test non riuscito.

@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 include diverse implementazioni di IRemoteTest che puoi riutilizzare invece di scriverne una da zero. Ad esempio, InstrumentationTest può eseguire i test di un'applicazione Android da remoto su un dispositivo Android, analizzare i risultati e inoltrarli al ITestInvocationListener). Per maggiori dettagli, consulta Tipi di test.

Risultati del test dello Store (I)

L'implementazione predefinita dell'ascoltatore di test per una configurazione TF è TextResultReporter, che esegue il dump dei risultati di un'invocazione in stdout. A titolo esemplificativo, esegui la configurazione HelloWorldTest della sezione precedente:

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

Per archiviare i risultati di un'invocazione altrove, ad esempio in un file, specifica un'implementazione ITestInvocationListener personalizzata utilizzando il tag result_reporter nella configurazione.

TF include anche l'ascoltatore XmlResultReporter che scrive i risultati dei test in un file XML in un formato simile a quello utilizzato dallo scrittore XML JUnit di ant. Per specificare result_reporter nella configurazione, modifica il file …/res/config/example/helloworld.xml config:

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

Ora ricostruisci tradefed ed esegui di nuovo l'esempio Hello World:

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

Nota il messaggio di log che indica che è stato generato un file XML. Il file generato dovrebbe avere il seguente aspetto:

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

Puoi anche scrivere i tuoi ascoltatori di invocazioni personalizzati, che devono semplicemente implementare l'interfaccia ITestInvocationListener.

Tradefed supporta più ascoltatori di chiamate, quindi puoi inviare i risultati dei test a più destinazioni indipendenti. Per farlo, specifica più tag <result_reporter> nella configurazione.

Strutture di logging (D, I, R)

Le strutture di registrazione di TF includono la possibilità di:

  1. Acquisisci i log dal dispositivo (ovvero logcat del dispositivo)
  2. Registra i log del framework Trade Federation in esecuzione sulla macchina host (noto anche come log host)

Il framework TF acquisisce automaticamente il logcat dal dispositivo allocato e lo invia all'ascoltatore di chiamate per l'elaborazione. XmlResultReporter salva quindi il logcat del dispositivo acquisito come file.

I log dell'host di TF vengono registrati utilizzando il wrapper CLog per la classe Log di ddmlib. Convertiamo la chiamata System.out.println precedente in HelloWorldTest in una chiamata CLog:

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

CLog gestisce direttamente l'interpolazione di stringhe, in modo simile a String.format. Quando ricostruisci ed esegui di nuovo TF, dovresti vedere il messaggio di log su stdout:

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

Per impostazione predefinita, tradefed esporta i messaggi del log dell'host su stdout. TF include anche un'implementazione di log che scrive i messaggi in un file: FileLogger. Per aggiungere il logging dei file, aggiungi un tag logger alla configurazione, specificando il nome completo della classe FileLogger:

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

Ora, ricostruisci ed esegui di nuovo l'esempio helloworld:

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
…

Il messaggio del log indica il percorso del log dell'host, che, se visualizzato, dovrebbe contenere il messaggio del log HelloWorldTest:

more /tmp/0/inv_6390011618174565918/host_log_4255420317120216614.txt

Esempio di output:

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

Opzioni di gestione (D, I, R)

Gli oggetti caricati da una configurazione TF (ovvero oggetti di configurazione) possono anche ricevere dati dagli argomenti della riga di comando tramite l'uso dell'annotazione @Option.

Per partecipare, una classe di oggetti Configuration applica l'annotazione @Option a un campo membro e gli fornisce un nome univoco. In questo modo, il valore del campo del membro può essere compilato tramite un'opzione a riga di comando (e l'opzione viene aggiunta automaticamente al sistema di guida alla configurazione).

Nota:non tutti i tipi di campo sono supportati. Per una descrizione dei tipi supportati, consulta OptionSetter.

Aggiungiamo un @Option a HelloWorldTest:

@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";

Aggiungiamo un messaggio di log per visualizzare il valore dell'opzione in HelloWorldTest in modo da dimostrare che è stato ricevuto correttamente:

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

Infine, ricostruisci TF ed esegui helloworld. Dovresti visualizzare un messaggio di log con il valore predefinito my_option:

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

Passare valori dalla riga di comando

Passa un valore per my_option; dovresti vedere my_option compilato con quel valore:

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

Le configurazioni di TF includono anche un sistema di guida che mostra automaticamente il testo di aiuto per i campi @Option. Prova subito e dovresti vedere il testo di aiuto per my_option:

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.

Tieni presente il messaggio relativo alla "stampa solo delle opzioni importanti". Per ridurre il numero di opzioni di guida, TF utilizza l'attributo Option#importance per determinare se mostrare un determinato testo di guida per il campo @Option quando viene specificato --help. --help-all mostra sempre la guida per tutti i campi @Option, indipendentemente dall'importanza. Per maggiori dettagli, consulta Option.Importance.

Passare valori da una configurazione

Puoi anche specificare un valore Option all'interno della configurazione aggiungendo un elemento <option name="" value="">. Provalo utilizzando helloworld.xml:

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

La nuova compilazione ed esecuzione di helloworld dovrebbe produrre questo output:

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

Anche la guida alla configurazione dovrebbe aggiornarsi per indicare il valore predefinito di my_option:

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

Anche altri oggetti di configurazione inclusi nella configurazione di helloworld, come FileLogger, accettano opzioni. L'opzione --log-level-display è interessante perché filtra i log visualizzati su stdout. All'inizio del tutorial, potresti aver notato il messaggio "Hello, TF World! Il messaggio di log "Ho il dispositivo…" non viene più visualizzato su stdout dopo che abbiamo iniziato a utilizzare FileLogger. Puoi aumentare la modalità dettagliata del logging in stdout passando l'argomento --log-level-display.

Prova subito e dovresti vedere il messaggio di log "Ho il dispositivo" riapparire su stdout, oltre a essere registrato in un file:

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

Abbiamo terminato.

Ti ricordiamo che, se hai difficoltà, il codice sorgente di Trade Federation contiene molte informazioni utili non riportate nella documentazione. Se non riesci a risolvere il problema, prova a chiedere nel gruppo Google android-platform, inserendo "Trade Federation" nell'oggetto del messaggio.