Exemplo de teste de TF completo

Este tutorial orienta você na criação de uma configuração de teste "hello world" da Trade Federation (Tradefed ou TF, na sigla em inglês) e oferece uma introdução prática ao framework da TF. Começando com um ambiente de desenvolvimento, você vai criar uma configuração simples e adicionar recursos.

O tutorial apresenta o processo de desenvolvimento de testes como um conjunto de exercícios, cada um com várias etapas, que demonstram como criar e refinar gradualmente sua configuração. Todos os exemplos de código necessários para concluir a configuração do teste são fornecidos, e o título de cada exercício é anotado com uma letra que descreve os papéis envolvidos nessa etapa:

  • D para desenvolvedor
  • I para integrador
  • R para Test Runner

Depois de concluir o tutorial, você terá uma configuração funcional do TF e entenderá muitos conceitos importantes no framework do TF.

Configurar a federação comercial

Para mais detalhes sobre a configuração do ambiente de desenvolvimento do TF, consulte Configuração da máquina. No restante deste tutorial, presumimos que você tenha um shell aberto que foi inicializado no ambiente do TF.

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

Criar uma classe de teste (D)

Vamos criar um teste Hello World que apenas despeje uma mensagem no stdout. Um teste de trade-in geralmente implementa a interface IRemoteTest. Confira uma implementação do 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 no shell:

m -jN

O CLog.i no exemplo acima é usado para direcionar a saída para o console. Mais informações sobre a geração de registros no Trade Federation estão descritas em Gerenciamento de registros (D, I, R).

Se o build não for bem-sucedido, consulte Configuração da máquina para garantir que você não tenha esquecido de uma etapa.

Criar uma configuração (I)

Os testes da Trade Federation são executados pela criação de uma configuração, um arquivo XML que instrui o TradeFed sobre quais testes executar, além de quais outros módulos executar e em que ordem.

Vamos criar uma nova configuração para o HelloWorldTest (observe o nome completo da classe 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 do seu sistema de arquivos local (por exemplo, /tmp/helloworld.xml). O TF vai analisar o arquivo XML de configuração (também conhecido como config), carregar a classe especificada usando a reflexão, instanciar, converter em um IRemoteTest e chamar o método run.

Executar a configuração (R)

No shell, inicie o console tradefed:

tradefed.sh

Confira se um dispositivo está 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 de console run <config>. Tente:

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ê vai ver a mensagem "Hello, TF World!" no terminal.

É possível confirmar que um comando foi executado usando list invocations ou l i no prompt do console. Ele não deve imprimir nada. Se os comandos estiverem em execução, eles serão mostrados da seguinte maneira:

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

Adicionar a configuração ao caminho de classe (D, I, R)

Para facilitar a implantação, você também pode agrupar as configurações nos próprios JARs do tradefed. O Tradefed reconhece automaticamente todas as configurações colocadas nas pastas config no classpath.

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

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

Agora você pode executar a configuração 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!

Interagir com um dispositivo (D, R)

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

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

Vamos modificar a mensagem de impressão HelloWorldTest para mostrar 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, recrie 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 Available, que é o dispositivo que precisa 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

A nova mensagem de impressão vai aparecer com o número de série do dispositivo.

Enviar resultados de teste (D)

O IRemoteTest informa os resultados chamando métodos na instância ITestInvocationListener fornecida para o método #run. O framework do TF é responsável por informar o início (usando ITestInvocationListener#invocationStarted) e o fim (usando ITestInvocationListener#invocationEnded) de cada invocação.

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

Confira como a implementação do HelloWorldTest pode ficar 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 podem ser reutilizadas em vez de criar uma 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 saber mais, consulte Tipos de teste.

Armazenar resultados de teste (I)

A implementação padrão do listener de teste para uma configuração do TF é TextResultReporter, que armazena os resultados de uma invocação no 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 invocação em outro lugar, como em um arquivo, especifique uma implementação ITestInvocationListener personalizada usando a tag result_reporter na configuração.

O TF também inclui o listener XmlResultReporter, que grava os resultados do teste em um arquivo XML em um formato semelhante ao usado pelo gravador XML JUnit ant. 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 o exemplo "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 registro informando que um arquivo XML foi gerado. O arquivo gerado tem esta aparência:

<?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 listeners de invocação personalizados. Eles simplesmente precisam implementar a interface ITestInvocationListener.

O Tradefed oferece suporte a vários listeners 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> na configuração.

Instalações de registro (D, I, R)

As instalações de geração de registros do TF incluem a capacidade de:

  1. Capturar registros do dispositivo (também conhecido como logcat do dispositivo)
  2. Gravar registros do framework da Trade Federation em execução na máquina host (também conhecido como registro do host)

O framework do TF captura automaticamente o logcat do dispositivo alocado e o envia ao listener de invocação para processamento. O XmlResultReporter salva o logcat do dispositivo capturado como um arquivo.

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

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

CLog processa a interpolação de strings diretamente, semelhante a String.format. Quando você recriar e executar o TF novamente, a mensagem de registro vai aparecer no stdout:

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

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

Agora, recrie 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 registro indica o caminho do registro do host, que, quando visualizado, deve conter a mensagem de registro HelloWorldTest:

more /tmp/0/inv_6390011618174565918/host_log_4255420317120216614.txt

Exemplo de saída:

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

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

Os 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 usando a anotação @Option.

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

Observação:nem todos os tipos de campo são aceitos. Para uma descrição dos tipos com suporte, consulte OptionSetter.

Vamos adicionar um @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 registro para mostrar o valor da opção no HelloWorldTest para demonstrar que ela foi recebida corretamente:

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

Por fim, reconstrua o TF e execute o helloworld. Você vai receber uma mensagem de registro com o valor padrão my_option:

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

Transmitir valores da linha de comando

Transmita um valor para my_option. O my_option vai ser 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 campos @Option. Teste agora e o texto de ajuda para my_option vai aparecer:

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 desordem de opções, o TF usa o atributo Option#importance para determinar se um texto de ajuda de campo @Option específico será mostrado quando --help for especificado. O --help-all sempre mostra ajuda para todos os campos @Option, independente da importância. Para mais detalhes, consulte Option.Importance.

Transmitir valores de uma configuração

Também é possível especificar um valor de opção na configuração adicionando um elemento <option name="" value="">. Teste usando helloworld.xml:

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

A reconstrução e a execução do helloworld agora vão produzir esta saída:

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

A ajuda de configuração também precisa 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 helloworld, como FileLogger, também aceitam opções. A opção --log-level-display é interessante porque filtra os registros que aparecem no stdout. No início do tutorial, você pode ter notado a mensagem "Hello, TF World! A mensagem de registro "I have device" parou de ser exibida no stdout depois que passamos a usar FileLogger. É possível aumentar o nível de detalhamento do registro no stdout transmitindo o argumento --log-level-display.

Faça isso agora, e a mensagem de registro "I have device" vai reaparecer no stdout, além de ser registrada 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!

Lembre-se de que, se você estiver com alguma dúvida, o código-fonte da Federação de Comércio tem muitas informações úteis que não estão expostas na documentação. Se nada mais funcionar, tente perguntar no Grupo do Google android-platform com o assunto "Trade Federation".