Exemplo de teste de TF de ponta a ponta

Este tutorial orienta você na criação de uma configuração de teste "hello world" Trade Federation (TF) e fornece uma introdução prática à estrutura do TF. A partir de um ambiente de desenvolvimento, você criará uma configuração simples e adicionará recursos.

O tutorial apresenta o processo de desenvolvimento de teste como um conjunto de exercícios, cada um consistindo em várias etapas, que demonstram como construir e refinar gradualmente sua configuração. Todo o código de amostra necessário para concluir a configuração do teste é fornecido e o título de cada exercício é anotado com uma carta descrevendo as funções envolvidas nessa etapa:

  • D para Desenvolvedor
  • Eu para Integrador
  • R para Executor de Testes

Após concluir o tutorial, você terá uma configuração de TF em funcionamento e entenderá muitos conceitos importantes da estrutura do TF.

Criação da Federação do Comércio

Para obter detalhes sobre como configurar o ambiente de desenvolvimento do TF, consulte Configuração da máquina . O restante deste tutorial pressupõe que você tenha um shell aberto que foi inicializado no ambiente TF.

Para simplificar, este tutorial ilustra a adição de uma configuração e suas classes à biblioteca principal da estrutura do TF. Isso pode ser estendido para o desenvolvimento de módulos fora da árvore de origem compilando o JAR comercializado e, em seguida, compilando seus módulos contra esse JAR.

Criando uma classe de teste (D)

Vamos criar um teste hello world que apenas despeja uma mensagem para stdout. Um teste de tradefed geralmente implementa a interface IRemoteTest . Aqui está uma implementação para o 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!");
    }
}

Salve este código de amostra em <tree>/tools/tradefederation/core/src/com/android/tradefed/example/HelloWorldTest.java e reconstrua o tradefed de seu shell:

m -jN

Observe que CLog.i no exemplo acima é usado para direcionar a saída para o console. Mais informações sobre o registro na Federação de Comércio estão descritas em Registro (D, I, R) .

Se a compilação não for bem-sucedida, consulte a Configuração da máquina para garantir que você não tenha perdido uma etapa.

Criando uma configuração (I)

Os testes do Trade Federation são tornados executáveis ​​criando um Configuration , um arquivo XML que instrui o tradefed sobre qual teste (ou testes) executar, bem como quais outros módulos executar e em que ordem.

Vamos criar uma nova configuração para o nosso HelloWorldTest (observe o nome completo da classe do HelloWorldTest):

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

Salve esses dados em um arquivo helloworld.xml em qualquer lugar em seu sistema de arquivos local (por exemplo /tmp/helloworld.xml ). O TF analisará o arquivo XML de configuração (também conhecido como config ), carregará a classe especificada usando reflexão, instanciá-la, convertê-la em um IRemoteTest e chamar seu método run .

Executando a configuração (R)

A partir do seu shell, inicie o console do tradefed:

tradefed.sh

Certifique-se de que um dispositivo esteja conectado à máquina host e visível para o tradefed:

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

As configurações podem ser executadas usando o comando run <config> console. Tentar:

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!

Você deve ver "Hello, TF World!" saída no terminal.

Você pode confirmar que um comando foi executado usando list invocations ou li no prompt do console e ele não deve imprimir nada. Se os comandos estiverem em execução no momento, eles serão exibidos da seguinte forma:

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

Adicionando a configuração ao classpath (D, I, R)

Para conveniência de implantação, você também pode agrupar as configurações nos próprios JARs negociados. Tradefed reconhece automaticamente todas as configurações colocadas em pastas de configuração no caminho de classe.

Para ilustrar, mova o arquivo helloworld.xml para a biblioteca central do tradefed ( <tree>/tools/tradefederation/core/res/config/example/helloworld.xml ). Reconstrua o tradefed, reinicie o console do tradefed e peça ao tradefed para exibir a lista de configurações do caminho de classe:

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

Agora você pode executar a configuração do helloworld usando:

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!

Interagindo com um dispositivo (D, R)

Até agora, nosso HelloWorldTest não está fazendo nada de interessante. A especialidade da Tradefed é executar testes usando dispositivos Android, então vamos adicionar um dispositivo Android ao teste.

Os testes podem obter uma referência a um dispositivo Android usando TestInformation , fornecido pela estrutura quando o método IRemoteTest#run é chamado.

Vamos modificar a mensagem de impressão HelloWorldTest para exibir o número de série do dispositivo:

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

Agora reconstrua o tradefed e verifique a lista de dispositivos:

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

Anote o número de série listado como Disponível ; esse é o dispositivo que deve ser alocado para 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

Você deverá ver a nova mensagem de impressão exibindo o número de série do dispositivo.

Enviando resultados de testes (D)

IRemoteTest relata os resultados chamando métodos na instância ITestInvocationListener fornecida ao método #run . A própria estrutura do TF é responsável por relatar o início (via ITestInvocationListener#invocationStarted ) e o fim (via ITestInvocationListener#invocationEnded ) de cada Invocação.

Uma execução de teste é uma coleção lógica de testes. Para relatar os resultados do teste, o IRemoteTest é responsável por relatar o início de uma execução de teste, o início e o fim de cada teste e o final da execução de teste.

Veja como pode ser a implementação do HelloWorldTest com um único resultado de teste com falha.

@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());
}

O TF inclui várias implementações de IRemoteTest que você pode reutilizar em vez de escrever suas próprias do zero. Por exemplo, InstrumentationTest pode executar os testes de um aplicativo Android remotamente em um dispositivo Android, analisar os resultados e encaminhar esses resultados para o ITestInvocationListener ). Para obter detalhes, consulte Tipos de teste .

Armazenando os resultados do teste (I)

A implementação padrão do ouvinte de teste para uma configuração do TF é TextResultReporter , que despeja os resultados de uma invocação para stdout. Para ilustrar, execute a configuração HelloWorldTest da seção anterior:

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

Para armazenar os resultados de uma chamada em outro lugar, como em um arquivo, especifique uma implementação personalizada de ITestInvocationListener usando a tag result_reporter em sua configuração.

O TF também inclui o ouvinte XmlResultReporter , que grava os resultados do teste em um arquivo XML em um formato semelhante ao usado pelo ant JUnit XML writer. Para especificar o result_reporter na configuração, edite a configuração …/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>

Agora reconstrua o tradefed e execute novamente a amostra 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

Observe a mensagem de log informando que um arquivo XML foi gerado; o arquivo gerado deve ficar assim:

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

Você também pode escrever seus próprios ouvintes de invocação personalizados — eles simplesmente precisam implementar a interface ITestInvocationListener .

O Tradefed oferece suporte a vários ouvintes de invocação, para que você possa enviar resultados de teste para vários destinos independentes. Para fazer isso, basta especificar várias tags <result_reporter> em seu config.

Registro (D, I, R)

As instalações de registro do TF incluem a capacidade de:

  1. Capture logs do dispositivo (também conhecido como logcat do dispositivo)
  2. Registre os logs da estrutura da Federação de Comércio em execução na máquina host (também conhecida como log do host)

A estrutura do TF captura automaticamente o logcat do dispositivo alocado e o envia ao ouvinte de invocação para processamento. XmlResultReporter então salva o logcat do dispositivo capturado como um arquivo.

Os logs do host TF são relatados usando o wrapper CLog para a classe Log ddmlib. Vamos converter a chamada anterior de System.out.println em HelloWorldTest para uma chamada CLog :

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

CLog lida com a interpolação de strings diretamente, semelhante a String.format . Ao reconstruir e executar novamente o TF, você deverá ver a mensagem de log no stdout:

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

Por padrão, tradefed envia mensagens de log do host para stdout . O TF também inclui uma implementação de log que grava mensagens em um arquivo: FileLogger . Para adicionar log de arquivo, adicione uma tag logger à configuração, especificando o nome completo da classe de 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>

Agora, reconstrua e execute o exemplo helloworld novamente:

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
…

A mensagem de log indica o caminho do log do host, que, quando visualizado, deve conter sua mensagem de log HelloWorldTest:

more /tmp/0/inv_6390011618174565918/host_log_4255420317120216614.txt

Saída de exemplo:

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

Opções de manuseio (D, I, R)

Objetos carregados de uma configuração do TF (também conhecidos como objetos de configuração ) também podem receber dados de argumentos de linha de comando por meio do uso da anotação @Option .

Para participar, uma classe de objeto Configuration aplica a anotação @Option a um campo de membro e fornece a ele um nome exclusivo. Isso permite que o valor do campo de membro seja preenchido por meio de uma opção de linha de comando (e também adiciona automaticamente essa opção ao sistema de ajuda de configuração).

Nota: Nem todos os tipos de campo são suportados. Para obter uma descrição dos tipos com suporte, consulte OptionSetter .

Vamos adicionar uma @Option ao 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";

Em seguida, vamos adicionar uma mensagem de log para exibir o valor da opção em HelloWorldTest para que possamos demonstrar que ela foi recebida corretamente:

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

Finalmente, reconstrua o TF e execute helloworld; você deve ver uma mensagem de log com o valor padrão my_option :

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

Passando valores da linha de comando

Passe um valor para my_option ; você deve ver my_option preenchido com esse valor:

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

As configurações do TF também incluem um sistema de ajuda, que exibe automaticamente o texto de ajuda para os campos @Option . Experimente agora e você deverá ver o texto de ajuda para 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.

Observe a mensagem sobre "imprimir apenas as opções importantes". Para reduzir a confusão de ajuda de opções, o TF usa o atributo Option#importance para determinar se deve mostrar um texto de ajuda de campo @Option específico quando --help for especificado. --help-all sempre mostra ajuda para todos os campos @Option , independentemente da importância. Para obter detalhes, consulte Option.Importance .

Passando valores de uma configuração

Você também pode especificar um valor Option dentro da configuração adicionando um elemento <option name="" value=""> . Teste-o usando helloworld.xml :

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

Reconstruir e executar o helloworld agora deve produzir esta saída:

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

A ajuda de configuração também deve ser atualizada para indicar o valor padrão de my_option :

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

Outros objetos de configuração incluídos na configuração do helloworld, como FileLogger , também aceitam opções. A opção --log-level-display é interessante porque filtra os logs que aparecem no stdout. Anteriormente no tutorial, você deve ter notado que a mensagem de log "Hello, TF World! I have device …' parou de ser exibida no stdout depois que mudamos para o FileLogger . Você pode aumentar a verbosidade do log para stdout passando o --log-level-display arg --log-level-display .

Tente isso agora e você deverá ver a mensagem de log 'I have device' reaparecer no stdout, além de estar logado em um arquivo:

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

Isso é tudo, pessoal!

Como lembrete, se você estiver travado em alguma coisa, o código-fonte da Trade Federation tem muitas informações úteis que não estão expostas na documentação. Se tudo mais falhar, tente perguntar no Google Group da plataforma Android , com "Trade Federation" no assunto da mensagem.