Ejemplo de pruebas de autoinstrumentación

Cuando se inicia una prueba de instrumentación, su paquete de destino se reinicia con el código de instrumentación inyectado e iniciado para su ejecución. Una excepción es que el paquete de destino aquí no puede ser el marco de la aplicación de Android en sí mismo, es decir, el paquete android , porque hacerlo conduciría a la situación paradójica en la que sería necesario reiniciar el marco de Android, que es lo que admite las funciones del sistema, incluida la instrumentación. sí mismo.

Esto significa que una prueba de instrumentación no puede inyectarse en el marco de trabajo de Android, también conocido como el servidor del sistema, para su ejecución. Para probar el marco de trabajo de Android, el código de prueba puede invocar solo superficies de API públicas o aquellas expuestas a través del lenguaje de definición de interfaz de Android AIDL disponible en el árbol de fuentes de la plataforma. Para esta categoría de pruebas, no tiene sentido apuntar a ningún paquete en particular. Por lo tanto, es habitual que dichas instrumentaciones se declaren para apuntar 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 de esta categoría también pueden:

  • Agrupe las actividades necesarias para la prueba.
  • Comparta la ID de usuario con el sistema.
  • Estar firmado con la clave de la plataforma.
  • Estar compilado contra la fuente del marco en lugar del SDK público.

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

El ejemplo cubierto aquí es escribir una nueva prueba de instrumentación con el paquete de destino establecido en su propio paquete de aplicación de prueba. Esta guía utiliza la siguiente prueba como ejemplo:

Se recomienda examinar primero el código para obtener una impresión aproximada antes de continuar.

Decidir sobre una ubicación de origen

Por lo general, su 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 poseen un solo repositorio de git, o comparten uno con otros equipos, pero tienen un subdirectorio dedicado que contiene el código fuente del componente.

Suponiendo que la ubicación raíz del origen de su componente se encuentra en <component source root> , la mayoría de los componentes tienen carpetas src y tests debajo, 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'.

Dado que está agregando una nueva prueba, probablemente necesitará crear el directorio de tests al lado de su componente src y llenarlo con contenido.

En algunos casos, su equipo puede tener más estructuras de directorios bajo tests debido a la necesidad de empaquetar diferentes conjuntos de pruebas en aplicaciones individuales. Y en este caso, deberá crear un nuevo subdirectorio en tests .

Independientemente de la estructura, terminará poblando el directorio de tests o el subdirectorio recién creado con archivos similares a los que hay en el directorio de instrumentation en el cambio de gerrit de muestra. Las secciones a continuación explicarán en más detalles de cada archivo.

archivo de manifiesto

Al igual que una aplicación normal, cada módulo de prueba de instrumentación necesita un archivo de manifiesto. Si nombra el archivo como AndroidManifest.xml y lo proporciona junto a Android.mk para su módulo de prueba, se incluirá automáticamente en el archivo MAKE central BUILD_PACKAGE .

Antes de continuar, se recomienda encarecidamente revisar primero la descripción general del manifiesto de la aplicación .

Esto brinda una descripción general de los componentes básicos de un archivo de manifiesto y sus funcionalidades. Consulte el ejemplo en platform_testing/tests/example/instrumentation/AndroidManifest.xml .

Se incluye una instantánea aquí para mayor comodidad:

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

    <application/>

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

</manifest>

Algunos comentarios selectos sobre el archivo de manifiesto:

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

El atributo del package es el nombre del paquete de la aplicación: este es el identificador único que el marco de la aplicación de Android usa para identificar una aplicación (o en este contexto: su 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 ComponentName#getPackageName() , y también el mismo que usaría para interactuar con varios subcomandos pm a través adb shell .

Tenga en cuenta también que, aunque el nombre del paquete suele tener el mismo estilo que el nombre de un paquete de Java, en realidad tiene muy pocas cosas que ver con él. En otras palabras, su paquete de aplicación (o prueba) puede contener clases con cualquier nombre de paquete, aunque, por otro lado, puede optar por la simplicidad y tener el nombre de su paquete Java de nivel superior en su 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, a esta aplicación se le debe otorgar la misma identificación de usuario, es decir, identidad de tiempo de ejecución, que la plataforma central. Tenga en cuenta que esto depende de que el apk esté firmado con el mismo certificado que la plataforma central (consulte LOCAL_CERTIFICATE en la sección anterior), pero son conceptos diferentes:

  • algunos permisos o API están protegidos por firma, lo que requiere el mismo certificado de firma
  • algunos permisos o API requieren la identidad de usuario del system de la persona que llama, lo que requiere que el paquete de llamadas comparta la identificación de usuario con el system , si es un paquete separado de la plataforma central en sí
<uses-library android:name="android.test.runner" />

Esto es necesario para todas las pruebas de instrumentación, ya que las clases relacionadas se empaquetan en un archivo de biblioteca jar del marco de trabajo independiente, por lo que requiere entradas adicionales de ruta de acceso de clases cuando el marco de la aplicación invoca el paquete de prueba.

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

Es posible que haya notado que el targetPackage aquí se declara igual que el atributo del package declarado en la etiqueta de manifest de este archivo. Como se mencionó en los aspectos básicos de las pruebas , esta categoría de prueba de instrumentación generalmente está destinada a probar las API del marco, por lo que no es muy significativo para ellos tener un paquete de aplicación objetivo específico, aparte de sí mismo.

Archivo de configuración sencillo

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

Archivo de configuración complejo

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

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

Se incluye una instantánea aquí 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>

Algunos comentarios selectos 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 dice a Trade Federation que instale HelloWorldTests.apk en el dispositivo de destino utilizando un target_preparer especificado. Hay muchos preparadores de objetivos disponibles para los desarrolladores en Trade Federation y estos 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 marco del corredor de prueba, que es JUnit en este caso.

Para obtener más información, consulte Configuraciones del módulo de prueba .

Características de JUnit4

El uso de la biblioteca de 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 algunos usos muy básicos de sus características. Consulte 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 ya no es necesario que las pruebas hereden de una clase de prueba base común; en su lugar, escribe pruebas en clases simples de Java y usa anotaciones para indicar ciertas configuraciones y restricciones de prueba. En este ejemplo, indicamos que esta clase debe ejecutarse como una prueba JUnit4.

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

Las anotaciones @Before y @After se utilizan en los métodos de JUnit4 para realizar la configuración previa a la prueba y el desmontaje posterior a la prueba. De manera similar, las anotaciones @BeforeClass y @AfterClass se utilizan en los métodos de JUnit4 para realizar la configuración antes de ejecutar todas las pruebas en una clase de prueba y desmantelarlas después. Tenga en cuenta que los métodos de configuración y eliminación del ámbito de 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 comenzar el nombre del método con test , sino que cada uno de ellos debe anotarse con @Test . Como de costumbre, los métodos de prueba deben ser públicos, no declarar valor de retorno, no tomar parámetros y pueden generar excepciones.

Importante : los métodos de prueba en sí están anotados con la anotación @Test ; y tenga en cuenta que para que las pruebas se ejecuten a través de APCT, deben anotarse con tamaños de prueba: el método anotado de ejemplo testHelloWorld como @SmallTest . La anotación se puede aplicar en el ámbito del método o en el ámbito de la clase.

Acceso a instrumentation

Aunque no se cubre en el ejemplo básico de hola mundo, es bastante común que una prueba de Android requiera acceso a la instancia de Instrumentation : esta es la interfaz API central que proporciona acceso a los contextos de la aplicación, las API de prueba relacionadas con el ciclo de vida de la actividad y más.

Debido a que las pruebas JUnit4 ya no requieren una clase base común, ya no es necesario obtener la instancia de Instrumentation a través de InstrumentationTestCase#getInstrumentation() , sino que el nuevo ejecutor de pruebas la administra a través de InstrumentationRegistry , donde se almacena la configuración contextual y ambiental creada por el marco de instrumentación.

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

Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation()

Cree y pruebe localmente

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

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