Ejemplo de pruebas de instrumentación autónoma

Cuando se inicia una prueba de instrumentación, su paquete de destino se reinicia con 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 de Android, como el paquete android, porque hacerlo genera la situación paradójica en la que se debe reiniciar el framework de Android, que es lo que admite las funciones del sistema, incluida la 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 plataformas de API públicas o aquellas expuestas con el lenguaje de definición de interfaces de Android AIDL disponible en el árbol de origen de la plataforma. Para esta categoría de pruebas, no tiene sentido segmentar ningún paquete en particular. Por lo tanto, es habitual que dichas 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:

  • Agrupa las actividades necesarias para las pruebas.
  • Comparte el ID de usuario con el sistema.
  • Debe estar firmado con la clave de la plataforma.
  • Compilar con la fuente del framework, en lugar del SDK público

Esta categoría de pruebas de instrumentación a veces se conoce como autoinstrumentación. Estos son algunos ejemplos de pruebas de autoinstrumentación en la fuente de la plataforma:

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

Te recomendamos que primero explores el código para obtener una impresión aproximada antes de continuar.

Elige una ubicación de origen

Por lo general, tu equipo ya tendrá un patrón establecido de lugares para verificar 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 dedicado que contiene el código fuente del componente.

Si suponemos que la ubicación raíz de la 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 nueva, es probable que debas crear el directorio tests junto a tu componente src y propagarlo 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 paquetes de pruebas en apks individuales. Y, en este caso, deberás crear un nuevo subdirectorio en tests.

Sin importar la estructura, terminarás propagando el directorio tests o el subdirectorio recién creado con archivos similares a lo que hay 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 makefile principal BUILD_PACKAGE, proporciónalo junto al archivo Android.mk de tu módulo de prueba.

Si no estás familiarizado con 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>

Estas son algunas observaciones 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: es el identificador único que usa el framework de aplicaciones de 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 muestra ComponentName#getPackageName() y el mismo que usarías para interactuar con varios subcomandos pm que usan adb shell.

Ten en cuenta que, aunque 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 de paquete de Java de nivel superior en tu aplicación o prueba idéntico al nombre de paquete de la aplicación.

android:sharedUserId="android.uid.system"

De esta manera, se declara que, en el momento de la instalación, a este archivo APK se le debe otorgar el mismo ID de usuario (es decir, la identidad del entorno 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 firmas, lo que requiere el mismo certificado de firma.
  • Algunos permisos o APIs requieren la identidad del usuario system del llamador, lo que requiere que el paquete de 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 obligatorio para todas las pruebas de instrumentación, ya que las clases relacionadas se empaquetan en un archivo de biblioteca JAR del 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 targetPackage se declara igual que el atributo package declarado en la etiqueta manifest de este archivo. Como se menciona en los conceptos básicos de las pruebas, esta categoría de pruebas de instrumentación suele estar destinada a probar APIs de frameworks, por lo que no es muy significativo que tengan un paquete de aplicación específico, además 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 del tiempo de compilación. En la mayoría de los casos, la opción de archivo de Blueprint basada en Soong es suficiente. Para obtener más información, consulta Configuración de prueba simple.

Archivo de configuración complejo

En estos casos más complejos, también debes escribir un archivo de configuración de pruebas para el conjunto de pruebas de Android, Trade Federation.

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

Aquí se incluye un resumen para tu 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>

Estas son algunas observaciones sobre el archivo de configuración de prueba:

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

Esto le indica a la Federación de Comercio que instale HelloWorldTests.apk en el dispositivo de destino usando un target_preparer especificado. Hay muchos preparadores de destino disponibles para los desarrolladores en Trade Federation, que se pueden usar para garantizar que el dispositivo esté configurado correctamente antes de ejecutar 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>

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

Para obtener más información, consulta Cómo probar configuraciones del módulo.

Funciones de JUnit4

El uso de la biblioteca android-support-test como ejecutor de pruebas permite la adopción de 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 de los equipos de componentes, existen algunos patrones de uso generalmente útiles.

@RunWith(JUnit4.class)
public class HelloWorldTest {

Una diferencia significativa en JUnit4 es que las pruebas ya no deben heredar de una clase de prueba base común. En su lugar, escribes pruebas en clases de Java simples y usas anotaciones para indicar ciertas configuraciones 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 baja posterior a la prueba. De manera similar, 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, luego, realizar la baja. Ten en cuenta que los métodos de configuración y desmantelamiento del alcance de la clase deben ser estáticos. En cuanto a los métodos de prueba, a diferencia de la versión anterior de JUnit, ya no necesitan iniciar el nombre del método con test, sino que cada uno debe anotarse con @Test. Como de costumbre, los métodos de prueba deben ser públicos, no declarar ningún valor que se muestre, no tomar parámetros y pueden arrojar excepciones.

Acceso a las clases de instrumentación

Aunque no se trata en el ejemplo básico de Hello World, es bastante común que una prueba de Android solicite acceso a una instancia Instrumentation: esta es la interfaz principal de la API que proporciona acceso a contextos de aplicaciones, 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 una instancia de Instrumentation a través de InstrumentationTestCase#getInstrumentation(). En su lugar, el nuevo ejecutor de pruebas la administra a través de InstrumentationRegistry, donde se almacena la configuración contextual y del entorno creada por el framework 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 manera local

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

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