Ejemplo de prueba TF de un extremo a otro

Este tutorial lo guía a través de la creación de una configuración de prueba de "hola mundo" de la Federación de Comercio (Tradefed o TF) y le brinda una introducción práctica al marco de trabajo de TF. A partir de un entorno de desarrollo, creará una configuración simple 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 perfeccionar gradualmente su configuración. Se proporciona todo el código de muestra que necesita para completar la configuración de la prueba y el título de cada ejercicio está anotado con una letra que describe los roles involucrados en ese paso:

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

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

Configurar la Federación de Comercio

Para obtener detalles sobre cómo configurar el entorno de desarrollo 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 compilando el JAR comercializado y luego compilando sus módulos contra ese JAR.

Crear una clase de prueba (D)

Creemos una prueba de Hola mundo que simplemente envíe un mensaje a la salida estándar. Una prueba comercializada 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 utiliza para dirigir la salida a la consola. Se describe más información sobre cómo iniciar sesión en Trade Federation en Registro (D, I, R) .

Si la compilación no se realiza correctamente, consulte Configuración de la máquina para asegurarse de no omitir ningún paso.

Crear una configuración (yo)

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.

Creemos 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 usando la reflexión, creará una instancia, la convertirá en un IRemoteTest y llamará a su método run .

Ejecute la configuración (R)

Desde tu shell, inicia 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> . Intentar:

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

Agregue 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 las 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 puedes 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!

Interactuar con un dispositivo (D, R)

Hasta ahora, nuestro HelloWorldTest no está haciendo nada interesante. La especialidad de Tradefed es realizar pruebas utilizando 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 aparece como Disponible ; ese es el dispositivo que debería 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 impreso que muestra el número de serie del dispositivo.

Enviar resultados de prueba (D)

IRemoteTest informa los resultados llamando a métodos en la instancia ITestInvocationListener proporcionada al método #run . El propio marco 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 la prueba.

Así es como podría verse 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 IRemoteTest que puede reutilizar en lugar de escribir la suya propia 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 reenviarlos a ITestInvocationListener ). Para obtener más información, consulte Tipos de prueba .

Almacenar resultados de pruebas (I)

La implementación predeterminada del detector de prueba para una configuración TF es TextResultReporter , que vuelca los resultados de una invocación a la salida estándar. Para ilustrar, ejecute la configuración 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 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 XML ant JUnit. 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 el ejemplo de hola mundo:

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 escuchas de invocación, por lo que puede enviar resultados de pruebas a múltiples destinos independientes. Para hacer esto, simplemente especifique varias etiquetas <result_reporter> en su configuración.

Instalaciones de 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 ejecuta 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. XmlResultReporter luego guarda el logcat del dispositivo capturado como un archivo.

Los registros del host TF se informan mediante el contenedor CLog para la clase Log 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 reconstruyas y vuelvas a ejecutar TF, deberías 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 logger a la configuración, especificando el nombre completo de la clase 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 ve, debe contener su 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 TF (también conocidos como objetos de configuración ) también pueden recibir datos de argumentos de línea de comando 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 miembro y le proporciona un nombre único. Esto permite que el valor del campo miembro se complete mediante 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 campos. 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ías 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ías 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 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 sólo las opciones importantes". Para reducir el desorden de ayuda de opciones, TF usa el atributo Option#importance para determinar si se muestra un texto de ayuda de campo @Option 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 Opción.Importancia .

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ébelo 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 hayas notado que el mensaje de registro "¡Hola, TF World! Tengo un dispositivo..." dejó de mostrarse en la salida estándar después de que cambiamos a usar FileLogger . Puedes aumentar la detalle del registro en la salida estándar pasando el --log-level-display arg.

Pruebe esto ahora y debería ver reaparecer el mensaje de registro "Tengo dispositivo" en la salida estándar, 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 plataforma Android , con "Federación de Comercio" en el asunto del mensaje.