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:
- Capturar registros do dispositivo (também conhecido como logcat do dispositivo)
- 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".