Ejemplo de prueba de TF de extremo a extremo

Este tutorial lo guía a través de la creación de una configuración de prueba de Trade Federation (TF) "hola mundo" y le brinda una introducción práctica al marco TF. A partir de un entorno de desarrollo, creará una configuración sencilla y agregará funciones.

El tutorial presenta el proceso de desarrollo de pruebas como un conjunto de ejercicios, cada uno de los cuales consta de varios pasos, que demuestran cómo construir y refinar gradualmente su configuración. Se proporciona todo el código de muestra que necesita para completar la configuración de prueba, y el título de cada ejercicio se anota con una letra que describe los roles involucrados en ese paso:

  • D para desarrollador
  • yo para integrador
  • R para corredor de pruebas

Después de completar el tutorial, tendrá una configuración de TF en funcionamiento y comprenderá muchos conceptos importantes en el marco de TF.

Configuración de la Federación de Comercio

Para obtener detalles sobre cómo configurar el entorno de desarrollo de TF, consulte Configuración de la máquina . El resto de este tutorial asume que tiene un shell abierto que se ha inicializado en el entorno TF.

Para simplificar, este tutorial ilustra cómo agregar una configuración y sus clases a la biblioteca principal del marco TF. Esto se puede extender al desarrollo de módulos fuera del árbol fuente al compilar el JAR tradefed y luego compilar sus módulos contra ese JAR.

Crear una clase de prueba (D)

Vamos a crear una prueba de hola mundo que simplemente descargue un mensaje en la salida estándar. Una prueba tradefed generalmente implementa la interfaz IRemoteTest . Aquí hay una implementación para 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!");
    }
}

Guarde este código de muestra en <tree>/tools/tradefederation/core/src/com/android/tradefed/example/HelloWorldTest.java y reconstruya tradefed desde su shell:

m -jN

Tenga en cuenta que CLog.i en el ejemplo anterior se usa para dirigir la salida a la consola. Se describe más información sobre el inicio de sesión en Trade Federation en Registro (D, I, R) .

Si la compilación no tiene éxito, consulte Configuración de la máquina para asegurarse de que no se saltó ningún paso.

Crear una configuración (I)

Las pruebas de Trade Federation se hacen ejecutables mediante la creación de una configuración , un archivo XML que indica a tradefed qué prueba (o pruebas) ejecutar, así como qué otros módulos ejecutar y en qué orden.

Vamos a crear una nueva configuración para nuestro HelloWorldTest (tenga en cuenta el nombre completo de la clase de HelloWorldTest):

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

Guarde estos datos en un archivo helloworld.xml en cualquier lugar de su sistema de archivos local (por ejemplo /tmp/helloworld.xml ). TF analizará el archivo XML de configuración (también conocido como config ), cargará la clase especificada mediante la reflexión, la creará una instancia, la enviará a un IRemoteTest y llamará a su método run .

Ejecutando la configuración (R)

Desde su shell, inicie la consola tradefed:

tradefed.sh

Asegúrese de que un dispositivo esté conectado a la máquina host y sea visible para tradefed:

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

Las configuraciones se pueden ejecutar usando el comando de consola run <config> . Probar:

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!

Deberías ver "¡Hola, TF World!" salida en el terminal.

Puede confirmar que un comando ha terminado de ejecutarse usando list invocations o li en el indicador de la consola, y no debería imprimir nada. Si los comandos se están ejecutando actualmente, se muestran de la siguiente manera:

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

Agregar la configuración al classpath (D, I, R)

Para facilitar la implementación, también puede agrupar configuraciones en los propios JAR comercializados. Tradefed reconoce automáticamente todas las configuraciones colocadas en carpetas de configuración en el classpath.

Para ilustrar, mueva el archivo helloworld.xml a la biblioteca principal de tradefed ( <tree>/tools/tradefederation/core/res/config/example/helloworld.xml ). Reconstruya tradefed, reinicie la consola de tradefed, luego solicite a tradefed que muestre la lista de configuraciones del classpath:

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

Ahora puede ejecutar la configuración de 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!

Interactuando con un dispositivo (D, R)

Hasta ahora, nuestro HelloWorldTest no está haciendo nada interesante. La especialidad de Tradefed es ejecutar pruebas con dispositivos Android, así que agreguemos un dispositivo Android a la prueba.

Las pruebas pueden obtener una referencia a un dispositivo Android mediante TestInformation , proporcionada por el marco cuando se llama al método IRemoteTest#run .

Modifiquemos el mensaje de impresión HelloWorldTest para mostrar el número de serie del dispositivo:

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

Ahora reconstruya tradefed y verifique la lista de dispositivos:

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

Tome nota del número de serie que figura como Disponible ; ese es el dispositivo que debe asignarse 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

Debería ver el nuevo mensaje de impresión que muestra el número de serie del dispositivo.

Envío de resultados de pruebas (D)

IRemoteTest informa los resultados llamando a métodos en la instancia de ITestInvocationListener proporcionada al método #run . El propio marco de TF es responsable de informar el inicio (a través de ITestInvocationListener#invocationStarted ) y el final (a través de ITestInvocationListener#invocationEnded ) de cada Invocación.

Una ejecución de prueba es una colección lógica de pruebas. Para informar los resultados de las pruebas, IRemoteTest es responsable de informar el inicio de una ejecución de prueba, el inicio y el final de cada prueba y el final de la ejecución de prueba.

Este es el aspecto que podría tener la implementación de HelloWorldTest con un único resultado de prueba fallido.

@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 incluye varias implementaciones de IRemoteTest que puede reutilizar en lugar de escribir las suyas propias desde cero. Por ejemplo, InstrumentationTest puede ejecutar las pruebas de una aplicación de Android de forma remota en un dispositivo Android, analizar los resultados y enviar esos resultados a ITestInvocationListener ). Para obtener más información, consulte Tipos de prueba .

Almacenamiento de resultados de pruebas (I)

La implementación predeterminada del oyente de prueba para una configuración de TF es TextResultReporter , que vuelca los resultados de una invocación en stdout. Para ilustrar, ejecute la configuración de HelloWorldTest de la sección 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 almacenar los resultados de una invocación en otro lugar, como en un archivo, especifique una implementación personalizada de ITestInvocationListener utilizando la etiqueta result_reporter en su configuración.

TF también incluye el oyente XmlResultReporter , que escribe los resultados de las pruebas en un archivo XML en un formato similar al utilizado por el escritor ant JUnit XML. Para especificar result_reporter en la configuración, edite la configuración …/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>

Ahora reconstruya tradefed y vuelva a ejecutar la muestra 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 el mensaje de registro que indica que se ha generado un archivo XML; el archivo generado debería verse así:

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

También puede escribir sus propios oyentes de invocación personalizados: simplemente necesitan implementar la interfaz ITestInvocationListener .

Tradefed admite múltiples oyentes de invocación, por lo que puede enviar los resultados de las pruebas a múltiples destinos independientes. Para hacer esto, simplemente especifique múltiples etiquetas <result_reporter> en su configuración.

Registro (D, I, R)

Las instalaciones de registro de TF incluyen la capacidad de:

  1. Capture registros del dispositivo (también conocido como logcat del dispositivo)
  2. Registre registros del marco de la Federación de comercio que se ejecutan en la máquina host (también conocido como registro de host)

El marco TF captura automáticamente el logcat del dispositivo asignado y lo envía al oyente de invocación para su procesamiento. Luego, XmlResultReporter guarda el logcat del dispositivo capturado como un archivo.

Los registros del host TF se notifican mediante el contenedor CLog para la clase de registro ddmlib. Convirtamos la llamada System.out.println anterior en HelloWorldTest en una llamada CLog :

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

CLog maneja la interpolación de cadenas directamente, similar a String.format . Cuando reconstruya y vuelva a ejecutar TF, debería ver el mensaje de registro en la salida estándar:

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

De forma predeterminada, tradefed envía mensajes de registro del host a stdout . TF también incluye una implementación de registro que escribe mensajes en un archivo: FileLogger . Para agregar el registro de archivos, agregue una etiqueta de logger a la configuración, especificando el nombre de clase completo 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>

Ahora, reconstruya y ejecute el ejemplo de helloworld nuevamente:

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
…

El mensaje de registro indica la ruta del registro del host que, cuando se visualiza, debe contener el mensaje de registro de HelloWorldTest:

more /tmp/0/inv_6390011618174565918/host_log_4255420317120216614.txt

Salida de ejemplo:

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

Opciones de manejo (D, I, R)

Los objetos cargados desde una configuración de TF (también conocidos como objetos de configuración ) también pueden recibir datos de los argumentos de la línea de comandos mediante el uso de la anotación @Option .

Para participar, una clase de objeto de configuración aplica la anotación @Option a un campo de miembro y le proporciona un nombre único. Esto permite que el valor del campo de miembro se complete a través de una opción de línea de comando (y también agrega automáticamente esa opción al sistema de ayuda de configuración).

Nota: No se admiten todos los tipos de campo. Para obtener una descripción de los tipos admitidos, consulte OptionSetter .

Agreguemos 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";

A continuación, agreguemos un mensaje de registro para mostrar el valor de la opción en HelloWorldTest para que podamos demostrar que se recibió correctamente:

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

Finalmente, reconstruya TF y ejecute helloworld; debería ver un mensaje de registro con el valor predeterminado my_option :

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

Pasar valores desde la línea de comando

Pase un valor para my_option ; debería ver my_option con ese valor:

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

Las configuraciones de TF también incluyen un sistema de ayuda, que muestra automáticamente el texto de ayuda para los campos @Option . Pruébelo ahora, y debería ver el texto de ayuda 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.

Tenga en cuenta el mensaje sobre "imprimir solo las opciones importantes". Para reducir el desorden de ayuda de opciones, TF usa el atributo Option#importance para determinar si mostrar un texto de ayuda de campo @Option en particular cuando se especifica --help . --help-all siempre muestra ayuda para todos los campos @Option , independientemente de su importancia. Para obtener más información, consulte Option.Importance .

Pasar valores de una configuración

También puede especificar un valor de opción dentro de la configuración agregando un elemento <option name="" value=""> . Pruébalo usando helloworld.xml :

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

Reconstruir y ejecutar helloworld ahora debería producir este resultado:

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

La ayuda de configuración también debería actualizarse para indicar el valor predeterminado de my_option :

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

Otros objetos de configuración incluidos en la configuración de helloworld, como FileLogger , también aceptan opciones. La opción --log-level-display es interesante porque filtra los registros que aparecen en la salida estándar. Anteriormente en el tutorial, es posible que haya notado que el mensaje de registro "¡Hola, mundo de TF! Tengo un dispositivo..." dejó de mostrarse en la salida estándar después de que cambiamos a usar FileLogger . Puede aumentar la verbosidad del registro en la salida estándar al pasar --log-level-display arg.

Intente esto ahora, y debería ver el mensaje de registro 'Tengo dispositivo' reaparecer en stdout, además de estar registrado en un archivo:

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

¡Eso es todo amigos!

Como recordatorio, si está atascado en algo, el código fuente de la Federación de Comercio tiene mucha información útil que no está expuesta en la documentación. Si todo lo demás falla, intente preguntar en el Grupo de Google de la plataforma Android , con "Federación de comercio" en el asunto del mensaje.