Ejemplo de pruebas de instrumentación automática

Cuando se inicia una prueba de instrumentación, se reinicia su paquete objetivo con el código de instrumentación insertado y se inicia para su ejecución. Una excepción es que el paquete de destino aquí no puede ser el framework de la aplicación para Android en sí, como el paquete android, ya que esto conduce a la situación paradójica en la que se debería reiniciar el framework de Android, que es lo que admite las funciones del sistema, incluida la propia instrumentación.

Esto significa que una prueba de instrumentación no puede insertarse en el framework de Android, también conocido como el servidor del sistema, para su ejecución. Para probar el framework de Android, el código de prueba solo puede invocar superficies de API públicas o aquellas expuestas con el lenguaje de definición de interfaz de Android AIDL disponible en el árbol de código fuente de la plataforma. Para esta categoría de pruebas, no es significativo segmentar ningún paquete en particular. Por lo tanto, es habitual que estas instrumentaciones se declaren para orientarse a su propio paquete de aplicación de prueba, como se define en su propia etiqueta <manifest> de AndroidManifest.xml.

Según los requisitos, los paquetes de aplicaciones de prueba en esta categoría también pueden hacer lo siguiente:

  • Actividades del paquete necesarias para las pruebas.
  • Comparte el ID del usuario con el sistema.
  • Estar firmado con la clave de la plataforma
  • Compilarse con la fuente del framework en lugar del SDK público

A veces, esta categoría de pruebas de instrumentación se denomina auto-instrumentación. Estos son algunos ejemplos de pruebas de autoinstrumentación en el código fuente de la plataforma:

En el ejemplo que se aborda aquí, se escribe una nueva prueba de instrumentación con el paquete de destino establecido en su propio paquete de aplicación de prueba. En esta guía, se usa la siguiente prueba como ejemplo:

Se recomienda que primero explores el código para tener una idea general antes de continuar.

Decide una ubicación de origen

Por lo general, tu equipo ya tendrá un patrón establecido de lugares para registrar el código y lugares para agregar pruebas. La mayoría de los equipos tienen un solo repositorio de Git o comparten uno con otros equipos, pero tienen un subdirectorio exclusivo que contiene el código fuente del componente.

Si suponemos que la ubicación raíz del código fuente de tu componente está en <component source root>, la mayoría de los componentes tienen carpetas src y tests debajo de ella, y algunos archivos adicionales, como Android.mk (o divididos en archivos .mk adicionales), el archivo de manifiesto AndroidManifest.xml y el archivo de configuración de prueba "AndroidTest.xml".

Como agregarás una prueba completamente nueva, es probable que debas crear el directorio tests junto a tu componente src y completarlo con contenido.

En algunos casos, es posible que tu equipo tenga más estructuras de directorios en tests debido a la necesidad de empaquetar diferentes conjuntos de pruebas en APKs individuales. En este caso, deberás crear un subdirectorio nuevo en tests.

Independientemente de la estructura, terminarás propagando el directorio tests o el subdirectorio recién creado con archivos similares a los que se encuentran en el directorio instrumentation en el cambio de Gerrit de muestra. Los detalles de cada archivo se explican más adelante en este documento.

Archivo de manifiesto

Al igual que con un proyecto de app, cada módulo de prueba de instrumentación requiere un archivo de manifiesto llamado AndroidManifest.xml. Para incluir automáticamente este archivo con el archivo make principal de BUILD_PACKAGE, proporciona este archivo junto al archivo Android.mk de tu módulo de prueba.

Si no conoces el archivo AndroidManifest.xml, consulta la Descripción general del manifiesto de la app.

A continuación, se muestra un archivo AndroidManifest.xml de ejemplo:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  android:sharedUserId="android.uid.system"
  package="android.test.example.helloworld" >

    <application>
       <uses-library android:name="android.test.runner"/>
    </application>

    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                     android:targetPackage="android.test.example.helloworld"
                     android:label="Hello World Test"/>

</manifest>

Algunos comentarios sobre el archivo de manifiesto:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="android.test.example.helloworld" >

El atributo package es el nombre del paquete de la aplicación, que es el identificador único que usa el framework de aplicaciones para Android para identificar una aplicación (o, en este contexto, tu aplicación de prueba). Cada usuario del sistema solo puede instalar una aplicación con ese nombre de paquete.

Además, este atributo package es el mismo que devuelve ComponentName#getPackageName() y el mismo que usarías para interactuar con varios subcomandos pm que usan adb shell.

Ten en cuenta que, si bien el nombre del paquete suele tener el mismo estilo que un nombre de paquete de Java, en realidad tiene muy poco que ver con él. En otras palabras, el paquete de tu aplicación (o prueba) puede contener clases con cualquier nombre de paquete, aunque, por otro lado, puedes optar por la simplicidad y tener el nombre del paquete Java de nivel superior en tu aplicación o prueba idéntico al nombre del paquete de la aplicación.

android:sharedUserId="android.uid.system"

Esto declara que, en el momento de la instalación, se debe otorgar a este archivo APK el mismo ID de usuario, es decir, la misma identidad de tiempo de ejecución, que la plataforma principal. Ten en cuenta que esto depende de que el APK esté firmado con el mismo certificado que la plataforma principal (consulta LOCAL_CERTIFICATE en una sección anterior), pero son conceptos diferentes:

  • Algunos permisos o APIs están protegidos por firma, lo que requiere el mismo certificado de firma.
  • Algunos permisos o APIs requieren la identidad de usuario system del llamador, lo que requiere que el paquete que realiza la llamada comparta el ID de usuario con system si es un paquete independiente de la plataforma principal.
<uses-library android:name="android.test.runner" />

Esto es necesario para todas las pruebas de Instrumentation, ya que las clases relacionadas se empaquetan en un archivo JAR de biblioteca de framework independiente, por lo que se requieren entradas de ruta de clase adicionales cuando el framework de la aplicación invoca el paquete de prueba.

android:targetPackage="android.test.example.helloworld"

Es posible que hayas notado que el targetPackage aquí se declara de la misma manera que el atributo package declarado en la etiqueta manifest de este archivo. Como se mencionó en los conceptos básicos de las pruebas, esta categoría de pruebas de instrumentación suele estar destinada a probar las APIs del framework, por lo que no tiene mucho sentido que tengan un paquete de aplicación específico, más allá de sí mismo.

Archivo de configuración simple

Cada módulo de prueba nuevo debe tener un archivo de configuración para dirigir el sistema de compilación con metadatos del módulo, instrucciones de empaquetado y dependencias de tiempo de compilación. En la mayoría de los casos, la opción de archivo de Blueprint basado en Soong es suficiente. Para obtener más detalles, consulta Configuración de prueba simple.

Archivo de configuración complejo

Para estos casos más complejos, también debes escribir un archivo de configuración de prueba para el arnés de prueba de Android, Trade Federation.

La configuración de la prueba puede especificar opciones especiales de configuración del dispositivo y argumentos predeterminados para proporcionar la clase de prueba. Consulta el ejemplo en /platform_testing/tests/example/instrumentation/AndroidTest.xml.

Aquí se incluye una instantánea para mayor comodidad:

<configuration description="Runs sample instrumentation test.">
  <target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup"/>
  <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
    <option name="test-file-name" value="HelloWorldTests.apk"/>
  </target_preparer>
  <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/>
  <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/>
  <option name="test-suite-tag" value="apct"/>
  <option name="test-tag" value="SampleInstrumentationTest"/>

  <test class="com.android.tradefed.testtype.AndroidJUnitTest">
    <option name="package" value="android.test.example.helloworld"/>
    <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
  </test>
</configuration>

Algunas observaciones sobre el archivo de configuración de la prueba:

<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
  <option name="test-file-name" value="HelloWorldTests.apk"/>
</target_preparer>

Esto le indica a Trade Federation que instale HelloWorldTests.apk en el dispositivo de destino con un target_preparer especificado. Hay muchos preparadores de destino disponibles para los desarrolladores en Trade Federation, y se pueden usar para garantizar que el dispositivo esté configurado correctamente antes de la ejecución de la prueba.

<test class="com.android.tradefed.testtype.AndroidJUnitTest">
  <option name="package" value="android.test.example.helloworld"/>
  <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
</test>

Esto especifica la clase de prueba de Trade Federation que se usará para ejecutar la prueba y pasa el paquete en el dispositivo que se ejecutará y el framework del ejecutor de pruebas, que es JUnit en este caso.

Para obtener más información, consulta Test Module Configs.

Funciones de JUnit4

Usar la biblioteca de android-support-test como ejecutor de pruebas permite adoptar nuevas clases de prueba de estilo JUnit4, y el cambio de Gerrit de muestra contiene un uso muy básico de sus funciones. Consulta el ejemplo en /platform_testing/tests/example/instrumentation/src/android/test/example/helloworld/HelloWorldTest.java.

Si bien los patrones de prueba suelen ser específicos para los equipos de componentes, existen algunos patrones de uso generalmente útiles.

@RunWith(JUnit4.class)
public class HelloWorldTest {

Una diferencia significativa en JUnit4 es que ya no se requiere que las pruebas hereden de una clase de prueba base común. En cambio, escribes pruebas en clases Java simples y usas anotaciones para indicar cierta configuración y restricciones de prueba. En este ejemplo, indicamos que esta clase se debe ejecutar como una prueba de JUnit4.

    @BeforeClass
    public static void beforeClass() {
    ...
    @AfterClass
    public static void afterClass() {
    ...
    @Before
    public void before() {
    ...
    @After
    public void after() {
    ...
    @Test
    @SmallTest
    public void testHelloWorld() {
    ...

JUnit4 usa las anotaciones @Before y @After en los métodos para realizar la configuración previa a la prueba y la limpieza posterior a la prueba. Del mismo modo, JUnit4 usa las anotaciones @BeforeClass y @AfterClass en los métodos para realizar la configuración antes de ejecutar todas las pruebas en una clase de prueba y la desconexión después. Ten en cuenta que los métodos de configuración y desmontaje del alcance de la clase deben ser estáticos. En cuanto a los métodos de prueba, a diferencia de las versiones anteriores de JUnit, ya no es necesario que comiencen el nombre del método con test. En cambio, cada uno de ellos debe estar anotado con @Test. Como de costumbre, los métodos de prueba deben ser públicos, no declarar ningún valor de devolución, no tomar parámetros y pueden arrojar excepciones.

Acceso a la clase de instrumentación

Si bien no se incluye en el ejemplo básico de Hello World, es bastante común que una prueba de Android requiera una instancia de Instrumentation: esta es la interfaz de la API principal que proporciona acceso a los contextos de la aplicación, las APIs de prueba relacionadas con el ciclo de vida de la actividad y mucho más.

Como las pruebas de JUnit4 ya no requieren una clase base común, ya no es necesario obtener la instancia de Instrumentation a través de InstrumentationTestCase#getInstrumentation(). En cambio, el nuevo corredor de pruebas la administra a través de InstrumentationRegistry, donde se almacena la configuración contextual y del entorno creada por el marco de trabajo de instrumentación.

Para acceder a la instancia de la clase Instrumentation, simplemente llama al método estático getInstrumentation() en la clase InstrumentationRegistry:

Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation()

Compila y prueba de forma local

Para los casos de uso más comunes, emplea Atest.

Para casos más complejos que requieren una mayor personalización, sigue las instrucciones de instrumentación.