Exemplo de teste TF ponta a ponta

Este tutorial orienta você na criação de uma configuração de teste da Federação de Comércio "olá mundo" (Tradefed ou 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 composto por diversas etapas, que demonstram como construir e refinar gradualmente sua configuração. Todo o código de exemplo necessário para concluir a configuração do teste é fornecido, e o título de cada exercício é anotado com uma letra descrevendo as funções envolvidas nessa etapa:

  • D para desenvolvedor
  • Eu para Integrador
  • R para executor de teste

Depois de concluir o tutorial, você terá uma configuração do TF funcional e compreenderá muitos conceitos importantes da estrutura do TF.

Configurar Federação Comercial

Para obter detalhes sobre como configurar o ambiente de desenvolvimento 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 TF. Isso pode ser estendido ao desenvolvimento de módulos fora da árvore de origem, compilando o JAR tradefed e, em seguida, compilando seus módulos nesse JAR.

Crie uma classe de teste (D)

Vamos criar um teste hello world que apenas envia uma mensagem para stdout. Um teste 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 exemplo em <tree>/tools/tradefederation/core/src/com/android/tradefed/example/HelloWorldTest.java e reconstrua o tradefed a partir do seu shell:

m -jN

Observe que CLog.i no exemplo acima é usado para direcionar a saída para o console. Mais informações sobre registro na Federação Comercial 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 perdeu nenhuma etapa.

Crie uma configuração (I)

Os testes da Trade Federation tornam-se executáveis ​​através da criação de 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 nosso 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 ). TF irá analisar o arquivo XML de configuração (também conhecido como config ), carregar a classe especificada usando reflexão, instanciá-la, convertê-la em IRemoteTest e chamar seu método run .

Execute a configuração (R)

No seu shell, inicie o console tradefed:

tradefed.sh

Certifique-se de que um dispositivo esteja conectado à máquina host e visível para 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 do console run <config> . 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ê deverá ver "Olá, TF World!" saída no terminal.

Você pode confirmar se a execução de um comando foi concluída 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}'

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

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

Para ilustrar, mova o arquivo helloworld.xml para a biblioteca principal 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 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!

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

Enviar resultados de testes (D)

IRemoteTest relata resultados chamando métodos na instância ITestInvocationListener fornecida ao método #run . A própria estrutura 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, 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.

Esta é a aparência da 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());
}

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

Armazenar resultados de testes (I)

A implementação de ouvinte de teste padrão para uma configuração 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 ITestInvocationListener personalizada 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 gravador Ant JUnit XML. 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 .

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

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

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

  1. Capture logs do dispositivo (também conhecido como logcat do dispositivo)
  2. Registrar logs da estrutura da Trade Federation em execução na máquina host (também conhecido como log de host)

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

Os logs do host TF são relatados usando o wrapper CLog para a classe ddmlib Log. 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 lida diretamente com a interpolação de strings, 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 . TF também inclui uma implementação de log 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, 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

Exemplo de saída:

…
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 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 membro e fornece a ele um nome exclusivo. Isso permite que o valor do campo 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 suportados, 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";

A seguir, 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ê deverá 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'

Passar valores da linha de comando

Passe um valor para my_option ; você deverá 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ê 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, TF usa o atributo Option#importance para determinar se deve mostrar um texto de ajuda de campo @Option específico quando --help é especificado. --help-all sempre mostra ajuda para todos os campos @Option , independentemente da importância. Para obter detalhes, consulte Option.Importance .

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

A reconstrução e execução do 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 helloworld, como FileLogger , também aceitam opções. A opção --log-level-display é interessante porque filtra os logs que aparecem no stdout. No início do tutorial, você deve ter notado que a mensagem de log "Olá, TF World! Tenho um dispositivo…' parou de ser exibida no stdout depois que passamos a usar FileLogger . Você pode aumentar o detalhamento do registro no stdout passando o --log-level-display arg --log-level-display .

Tente fazer isso agora e você verá a mensagem de log 'Tenho dispositivo' reaparecer no stdout, além de ser 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 preso em alguma coisa, o código-fonte da Trade Federation contém muitas informações úteis que não estão expostas na documentação. Se tudo mais falhar, tente perguntar no Grupo Google da plataforma Android , com "Trade Federation" no assunto da mensagem.