Esempio di test TF end-to-end

Questo tutorial ti guida attraverso la creazione di una configurazione di test "ciao mondo" della Federazione dei Mercanti (Tradefed o TF) e ti fornisce un'introduzione pratica al framework TF. Partendo da un ambiente di sviluppo, creerai una semplice configurazione e aggiungerai funzionalità.

Il tutorial presenta il processo di sviluppo del test come una serie di esercizi, ciascuno composto da diversi passaggi, che dimostrano 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 ciascun esercizio è annotato con una lettera che descrive i ruoli coinvolti in quel passaggio:

  • D per sviluppatore
  • Io per Integratore
  • R per Test Runner

Dopo aver completato il tutorial, avrai una configurazione TF funzionante e comprenderai molti concetti importanti nel framework TF.

Istituire la Federazione dei Mercanti

Per dettagli sulla configurazione dell'ambiente di sviluppo TF, vedere Impostazione macchina . Il resto di questo tutorial presuppone che tu abbia una shell aperta che è stata inizializzata nell'ambiente TF.

Per semplicità, questo tutorial illustra l'aggiunta di una configurazione e delle relative classi alla libreria principale del framework TF. Questo può essere esteso allo sviluppo di moduli al di fuori dell'albero dei sorgenti compilando il JAR tradefed, quindi compilando i moduli rispetto a quel JAR.

Creare una classe di prova (D)

Creiamo un test hello world che scarica semplicemente un messaggio su stdout. Un test tradefed generalmente 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 tradefed dalla tua shell:

m -jN

Tieni presente che CLog.i nell'esempio sopra viene utilizzato per indirizzare l'output alla console. Maggiori informazioni sulla registrazione in Trade Federation sono descritte in Registrazione (D, I, R) .

Se la creazione non riesce, consulta Impostazione macchina per assicurarti di non aver saltato un passaggio.

Creare una configurazione (I)

I test della Trade Federation vengono resi eseguibili creando una Configuration , un file XML che istruisce tradefed su quale test (o test) eseguire, nonché quali altri moduli eseguire e in quale ordine.

Creiamo una nuova configurazione per il nostro HelloWorldTest (nota 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 ovunque sul tuo filesystem locale (ad esempio /tmp/helloworld.xml ). TF analizzerà il file XML di configurazione (noto anche come config ), caricherà la classe specificata utilizzando la riflessione, ne creerà un'istanza, la trasmetterà a IRemoteTest e chiamerà il suo metodo run .

Esegui la configurazione (R)

Dalla tua 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> della console. Tentativo:

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 "Ciao, TF World!" uscita sul terminale.

Puoi confermare che l'esecuzione di un comando è terminata utilizzando list invocations o li nel prompt della console e non dovrebbe stampare 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 classpath (D, I, R)

Per comodità di distribuzione, puoi anche raggruppare le configurazioni negli stessi JAR tradefed. Tradefed riconosce automaticamente tutte le configurazioni inserite nelle cartelle di configurazione sul classpath.

Per illustrare, spostare il file helloworld.xml nella libreria core tradefed ( <tree>/tools/tradefederation/core/res/config/example/helloworld.xml ). Ricostruisci tradefed, riavvia la console tradefed, quindi chiedi a tradefed di visualizzare l'elenco delle configurazioni dal classpath:

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!

Interagisci con un dispositivo (D, R)

Finora, il nostro HelloWorldTest non sta facendo nulla di interessante. La specialità di Tradefed è l'esecuzione di test utilizzando dispositivi Android, quindi aggiungiamo 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 HelloWorldTest per 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 tradefed e controlla l'elenco dei dispositivi:

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

Prendere nota del numero di serie indicato come Disponibile ; questo è il dispositivo che dovrebbe essere assegnato 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.

Invia i risultati del test (D)

IRemoteTest segnala i risultati chiamando i metodi sull'istanza ITestInvocationListener fornita al metodo #run . Il framework TF stesso è responsabile della segnalazione dell'inizio (tramite ITestInvocationListener#invocationStarted ) e della fine (tramite ITestInvocationListener#invocationEnded ) di ciascuna invocazione.

Un'esecuzione di test è una raccolta logica di test. Per segnalare i risultati dei test, IRemoteTest è responsabile di segnalare l'inizio di un'esecuzione di test, l'inizio e la fine di ciascun test e la fine dell'esecuzione di test.

Ecco come potrebbe apparire l'implementazione HelloWorldTest con un singolo risultato del 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 IRemoteTest che è possibile riutilizzare invece di scriverne una propria da zero. Ad esempio, InstrumentationTest può eseguire i test di un'applicazione Android in remoto su un dispositivo Android, analizzare i risultati e inoltrarli a ITestInvocationListener ). Per i dettagli, vedere Tipi di test .

Memorizzare i risultati dei test (I)

L'implementazione del listener di test predefinita per una configurazione TF è TextResultReporter , che scarica i risultati di un'invocazione su stdout. Per illustrare, esegui la configurazione HelloWorldTest dalla 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 una chiamata altrove, ad esempio in un file, specificare un'implementazione ITestInvocationListener personalizzata utilizzando il tag result_reporter nella configurazione.

TF include anche il listener XmlResultReporter , che scrive i risultati dei test in un file XML in un formato simile a quello utilizzato dal writer XML ant JUnit. Per specificare result_reporter nella configurazione, modifica la configurazione …/res/config/example/helloworld.xml :

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

Notare il messaggio di registro che informa che è stato generato un file XML; il file generato dovrebbe assomigliare a questo:

<?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 chiamata personalizzati: devono semplicemente implementare l'interfaccia ITestInvocationListener .

Tradefed supporta più listener di invocazione, quindi puoi inviare i risultati dei test a più destinazioni indipendenti. Per fare ciò, specifica più tag <result_reporter> nel tuo file config.

Strutture di registrazione (D, I, R)

Le funzionalità di registrazione di TF includono la capacità di:

  1. Cattura i log dal dispositivo (noto anche come logcat del dispositivo)
  2. Registrare i log dal framework della 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 al listener di invocazione per l'elaborazione. XmlResultReporter salva quindi il logcat del dispositivo acquisito come file.

I log dell'host TF vengono segnalati utilizzando il wrapper CLog per la classe ddmlib Log. Convertiamo la precedente chiamata System.out.println 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 delle stringhe, in modo simile a String.format . Quando ricostruisci e riesegui TF, dovresti vedere il messaggio di registro su stdout:

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

Per impostazione predefinita, tradefed invia i messaggi di registro dell'host a stdout . TF include anche un'implementazione del log che scrive i messaggi in un file: FileLogger . Per aggiungere la registrazione dei file, aggiungi un tag logger alla configurazione, specificando il nome completo della classe di 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 nuovamente 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 di registro indica il percorso del registro dell'host che, se visualizzato, dovrebbe contenere il messaggio di registro 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 (noti anche come oggetti di configurazione ) possono anche ricevere dati da argomenti della riga di comando tramite l'uso dell'annotazione @Option .

Per partecipare, una classe di oggetto Configurazione applica l'annotazione @Option a un campo membro e gli fornisce un nome univoco. Ciò consente di popolare il valore del campo membro tramite un'opzione della riga di comando (e inoltre aggiunge automaticamente tale opzione al sistema di guida alla configurazione).

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

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

Successivamente, aggiungiamo un messaggio di registro per visualizzare il valore dell'opzione in HelloWorldTest in modo da poter 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 vedere un messaggio di registro con il valore predefinito my_option :

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

Passa valori dalla riga di comando

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

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

Le configurazioni TF includono anche un sistema di aiuto, che visualizza automaticamente il testo di aiuto per i campi @Option . Provalo adesso 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.

Nota il messaggio relativo alla "stampa solo delle opzioni importanti". Per ridurre la confusione dell'aiuto per le opzioni, TF utilizza l'attributo Option#importance per determinare se mostrare un particolare testo di aiuto per il campo @Option quando viene specificato --help . --help-all mostra sempre l'aiuto per tutti i campi @Option , indipendentemente dall'importanza. Per i dettagli, vedere Option.Importance .

Passare valori da una configurazione

Puoi anche specificare un valore Opzione 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 ricostruzione e l'esecuzione di helloworld ora dovrebbero produrre questo output:

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

Anche la guida alla configurazione dovrebbe essere aggiornata 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. In precedenza nel tutorial, potresti aver notato che il messaggio di registro "Ciao, TF World! Ho un dispositivo..." ha smesso di essere visualizzato su stdout dopo essere passati all'utilizzo FileLogger . È possibile aumentare la verbosità della registrazione su stdout passando --log-level-display arg.

Provalo ora e dovresti vedere riapparire il messaggio di registro "Ho il dispositivo" su stdout, oltre ad 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

È tutto gente!

Come promemoria, se sei bloccato su qualcosa, il codice sorgente della Trade Federation contiene molte informazioni utili che non sono esposte nella documentazione. Se tutto il resto fallisce, prova a chiedere sul gruppo Google sulla piattaforma Android , con "Trade Federation" nell'oggetto del messaggio.