Pruebas de instrumentación de Android

Informar un problema Ver fuente

Si es la primera vez que usas Bazel, comienza con el instructivo Compila Android con Bazel.

Cómo ejecutar pruebas de instrumentación de Android en paralelo

Figura 1: Ejecución de pruebas de instrumentación de Android paralelas

android_instrumentation_test permite a los desarrolladores probar sus apps en emuladores y dispositivos de Android. Utiliza API reales del framework de Android y la biblioteca de prueba de Android.

Por cuestiones de hermeticidad y reproducibilidad, Bazel crea e inicia emuladores de Android en una zona de pruebas, lo que garantiza que las pruebas siempre se ejecuten desde un estado limpio. Cada prueba obtiene una instancia aislada del emulador, lo que permite que se ejecuten en paralelo sin pasar estados entre ellas.

Si deseas obtener más información sobre las pruebas de instrumentación de Android, consulta la documentación para desarrolladores de Android.

Informa los problemas en la Herramienta de seguimiento de errores de GitHub.

Cómo funciona

Cuando ejecutas bazel test en un destino android_instrumentation_test por primera vez, Bazel realiza los siguientes pasos:

  1. Compila el APK de prueba, el APK que se está probando y sus dependencias transitivas.
  2. Crea, inicia y almacena en caché los estados limpios del emulador
  3. Inicia el emulador
  4. Instala los APK
  5. Ejecuta pruebas con Android Test Orchestrator.
  6. Cierra el emulador
  7. Informa los resultados

En las ejecuciones de prueba posteriores, Bazel inicia el emulador desde el estado limpio y almacenado en caché que se creó en el paso 2, por lo que no queda ningún estado de las ejecuciones anteriores. Almacenar el estado del emulador en caché también acelera las ejecuciones de pruebas.

Requisitos previos

Asegúrate de que tu entorno cumpla con los siguientes requisitos previos:

  • Linux Se probó en Ubuntu 16.04 y 18.04.

  • Bazel 0.12.0 o una versión posterior Ejecuta bazel info release para verificar la versión.

bazel info release

Esto da como resultado un resultado similar al siguiente:

release 4.1.0

Para verificar que KVM tenga la configuración correcta, ejecuta el siguiente comando:

apt-get install cpu-checker && kvm-ok

Si imprime el siguiente mensaje, tienes la configuración correcta:

INFO: /dev/kvm exists
KVM acceleration can be used

Para instalarlo, ejecuta el siguiente comando:

apt-get install xvfb

Ejecuta el siguiente comando para verificar que Xvfb se haya instalado de forma correcta y en /usr/bin/Xvfb:

which Xvfb

Esta es la salida:

/usr/bin/Xvfb
  • Bibliotecas de 32 bits. Algunos de los objetos binarios que usa la infraestructura de prueba son de 32 bits. Por lo tanto, en máquinas de 64 bits, asegúrate de que se puedan ejecutar objetos binarios de 32 bits. En Ubuntu, instala las siguientes bibliotecas de 32 bits:
sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2-1.0:i386

Primeros pasos

A continuación, se muestra un gráfico de dependencia objetivo típico de un android_instrumentation_test:

Gráfico de dependencia objetivo en una prueba de instrumentación de Android

Figura 2: Gráfico de dependencia de destino de un elemento android_instrumentation_test.

Archivo BUILD

El gráfico se traduce en un archivo BUILD de la siguiente manera:

android_instrumentation_test(
    name = "my_test",
    test_app = ":my_test_app",
    target_device = "@android_test_support//tools/android/emulated_devices/generic_phone:android_23_x86",
)

# Test app and library
android_binary(
    name = "my_test_app",
    instruments = ":my_app",
    manifest = "AndroidTestManifest.xml",
    deps = [":my_test_lib"],
    # ...
)

android_library(
    name = "my_test_lib",
    srcs = glob(["javatest/**/*.java"]),
    deps = [
        ":my_app_lib",
        "@maven//:androidx_test_core",
        "@maven//:androidx_test_runner",
        "@maven//:androidx_test_espresso_espresso_core",
    ],
    # ...
)

# Target app and library under test
android_binary(
    name = "my_app",
    manifest = "AndroidManifest.xml",
    deps = [":my_app_lib"],
    # ...
)

android_library(
    name = "my_app_lib",
    srcs = glob(["java/**/*.java"]),
    deps = [
        "@maven//:androidx_appcompat_appcompat",
        "@maven//:androidx_annotation_annotation",
    ]
    # ...
)

Los atributos principales de la regla android_instrumentation_test son los siguientes:

  • test_app: Es un objetivo android_binary. Este objetivo contiene el código de prueba y las dependencias como Espresso y UIAutomator. El destino android_binary seleccionado debe especificar un atributo instruments que apunte a otro android_binary, que es la app que se está probando.

  • target_device: Es un objetivo android_device. En este destino, se describen las especificaciones del emulador de Android que Bazel usa para crear, iniciar y ejecutar las pruebas. Consulta la sección sobre cómo elegir un dispositivo Android para obtener más información.

El AndroidManifest.xml de la app de prueba debe incluir una etiqueta <instrumentation>. Esta etiqueta debe especificar los atributos del paquete de la app de destino y el nombre de clase completamente calificado del ejecutor de pruebas de instrumentación, androidx.test.runner.AndroidJUnitRunner.

A continuación, se muestra un AndroidTestManifest.xml de ejemplo para la app de prueba:

<?xml version="1.0" encoding="UTF-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          package="com.example.android.app.test"
    android:versionCode="1"
    android:versionName="1.0">

    <instrumentation
        android:name="androidx.test.runner.AndroidJUnitRunner"
        android:targetPackage="com.example.android.app" />

    <uses-sdk
        android:minSdkVersion="16"
        android:targetSdkVersion="27" />

    <application >
       <!-- ... -->
    </application>
</manifest>

Dependencias de WORKSPACE

Para usar esta regla, tu proyecto debe depender de estos repositorios externos:

  • @androidsdk: Es el SDK de Android. Descárgala mediante Android Studio.

  • @android_test_support: Aloja el ejecutor de pruebas, el selector del emulador y los destinos de android_device. Puedes encontrar la versión más reciente aquí.

Para habilitar estas dependencias, agrega las siguientes líneas al archivo WORKSPACE:

# Android SDK
android_sdk_repository(
    name = "androidsdk",
    path = "/path/to/sdk", # or set ANDROID_HOME
)

# Android Test Support
ATS_COMMIT = "$COMMIT_HASH"
http_archive(
    name = "android_test_support",
    strip_prefix = "android-test-%s" % ATS_COMMIT,
    urls = ["https://github.com/android/android-test/archive/%s.tar.gz" % ATS_COMMIT],
)
load("@android_test_support//:repo.bzl", "android_test_repositories")
android_test_repositories()

Dependencias de Maven

Para administrar dependencias en artefactos de Maven desde repositorios, como Google Maven o Maven Central, debes usar un agente de resolución de Maven, como rules_jvm_external.

En el resto de esta página, se muestra cómo usar rules_jvm_external para resolver y recuperar dependencias de repositorios de Maven.

Cómo elegir una orientación para android_device

android_instrumentation_test.target_device especifica en qué dispositivo Android se deben ejecutar las pruebas. Estos destinos android_device se definen en @android_test_support.

Por ejemplo, puedes ejecutar el siguiente comando para consultar las fuentes de un destino en particular:

bazel query --output=build @android_test_support//tools/android/emulated_devices/generic_phone:android_23_x86

Esto da como resultado un resultado similar al siguiente:

# .../external/android_test_support/tools/android/emulated_devices/generic_phone/BUILD:43:1
android_device(
  name = "android_23_x86",
  visibility = ["//visibility:public"],
  tags = ["requires-kvm"],
  generator_name = "generic_phone",
  generator_function = "make_device",
  generator_location = "tools/android/emulated_devices/generic_phone/BUILD:43",
  vertical_resolution = 800,
  horizontal_resolution = 480,
  ram = 2048,
  screen_density = 240,
  cache = 32,
  vm_heap = 256,
  system_image = "@android_test_support//tools/android/emulated_devices/generic_phone:android_23_x86_images",
  default_properties = "@android_test_support//tools/android/emulated_devices/generic_phone:_android_23_x86_props",
)

Los nombres de las orientaciones por dispositivo usan esta plantilla:

@android_test_support//tools/android/emulated_devices/device_type:system_api_level_x86_qemu2

Para iniciar un android_device, se requiere el system_image del nivel de API seleccionado. Para descargar la imagen del sistema, usa tools/bin/sdkmanager del SDK de Android. Por ejemplo, para descargar la imagen del sistema de generic_phone:android_23_x86, ejecuta $sdk/tools/bin/sdkmanager "system-images;android-23;default;x86".

Para ver la lista completa de destinos android_device compatibles con @android_test_support, ejecuta el siguiente comando:

bazel query 'filter("x86_qemu2$", kind(android_device, @android_test_support//tools/android/emulated_devices/...:*))'

Actualmente, Bazel solo admite emuladores basados en x86. Para obtener un mejor rendimiento, utiliza objetivos de android_device QEMU2 en lugar de QEMU.

Cómo ejecutar pruebas

Para ejecutar pruebas, agrega estas líneas al archivo project root:/.bazelrc de tu proyecto.

# Configurations for testing with Bazel
# Select a configuration by running
# `bazel test //my:target --config={headless, gui, local_device}`

# Headless instrumentation tests (No GUI)
test:headless --test_arg=--enable_display=false

# Graphical instrumentation tests. Ensure that $DISPLAY is set.
test:gui --test_env=DISPLAY
test:gui --test_arg=--enable_display=true

# Testing with a local emulator or device. Ensure that `adb devices` lists the
# device.
# Run tests serially.
test:local_device --test_strategy=exclusive
# Use the local device broker type, as opposed to WRAPPED_EMULATOR.
test:local_device --test_arg=--device_broker_type=LOCAL_ADB_SERVER
# Uncomment and set $device_id if there is more than one connected device.
# test:local_device --test_arg=--device_serial_number=$device_id

Luego, usa una de las configuraciones para ejecutar pruebas:

  • bazel test //my/test:target --config=gui
  • bazel test //my/test:target --config=headless
  • bazel test //my/test:target --config=local_device

Usa una sola configuración o las pruebas fallarán.

Pruebas sin interfaz gráfica

Con Xvfb, es posible realizar pruebas con emuladores sin la interfaz gráfica, lo que también se conoce como prueba sin interfaz gráfica. Para inhabilitar la interfaz gráfica cuando ejecutas pruebas, pasa el argumento de prueba --enable_display=false a Bazel:

bazel test //my/test:target --test_arg=--enable_display=false

Pruebas de la GUI

Si se configura la variable de entorno $DISPLAY, es posible habilitar la interfaz gráfica del emulador mientras se ejecuta la prueba. Para hacerlo, pasa estos argumentos de prueba a Bazel:

bazel test //my/test:target --test_arg=--enable_display=true --test_env=DISPLAY

Cómo realizar pruebas con un emulador o dispositivo local

Bazel también admite pruebas directamente en un emulador o dispositivo conectado iniciado de forma local. Pasa las marcas --test_strategy=exclusive y --test_arg=--device_broker_type=LOCAL_ADB_SERVER para habilitar el modo de prueba local. Si hay más de un dispositivo conectado, pasa la marca --test_arg=--device_serial_number=$device_id, en la que $device_id es el ID del dispositivo o emulador que aparece en adb devices.

Proyectos de muestra

Si buscas muestras de proyectos canónicos, consulta las muestras de pruebas de Android para proyectos que usan Espresso y UIAutomator.

Configuración de Espresso

Si escribes pruebas de IU con Espresso (androidx.test.espresso), puedes usar los siguientes fragmentos para configurar el lugar de trabajo de Bazel con la lista de artefactos de Espresso de uso común y sus dependencias:

androidx.test.espresso:espresso-core
androidx.test:rules
androidx.test:runner
javax.inject:javax.inject
org.hamcrest:java-hamcrest
junit:junit

Una forma de organizar estas dependencias es crear una biblioteca compartida //:test_deps en tu archivo project root/BUILD.bazel:

java_library(
    name = "test_deps",
    visibility = ["//visibility:public"],
    exports = [
        "@maven//:androidx_test_espresso_espresso_core",
        "@maven//:androidx_test_rules",
        "@maven//:androidx_test_runner",
        "@maven//:javax_inject_javax_inject"
        "@maven//:org_hamcrest_java_hamcrest",
        "@maven//:junit_junit",
    ],
)

Luego, agrega las dependencias necesarias en project root/WORKSPACE:

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

RULES_JVM_EXTERNAL_TAG = "2.8"
RULES_JVM_EXTERNAL_SHA = "79c9850690d7614ecdb72d68394f994fef7534b292c4867ce5e7dec0aa7bdfad"

http_archive(
    name = "rules_jvm_external",
    strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
    sha256 = RULES_JVM_EXTERNAL_SHA,
    url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
)

load("@rules_jvm_external//:defs.bzl", "maven_install")

maven_install(
    artifacts = [
        "junit:junit:4.12",
        "javax.inject:javax.inject:1",
        "org.hamcrest:java-hamcrest:2.0.0.0"
        "androidx.test.espresso:espresso-core:3.1.1",
        "androidx.test:rules:aar:1.1.1",
        "androidx.test:runner:aar:1.1.1",
    ],
    repositories = [
        "https://maven.google.com",
        "https://repo1.maven.org/maven2",
    ],
)

Por último, en tu destino android_binary de prueba, agrega la dependencia //:test_deps:

android_binary(
    name = "my_test_app",
    instruments = "//path/to:app",
    deps = [
        "//:test_deps",
        # ...
    ],
    # ...
)

Sugerencias

Lee los registros de prueba

Usa --test_output=errors para imprimir registros de las pruebas fallidas o --test_output=all para imprimir todos los resultados de la prueba. Si buscas un registro de prueba individual, ve a $PROJECT_ROOT/bazel-testlogs/path/to/InstrumentationTestTargetName.

Por ejemplo, los registros de prueba del proyecto canónico BasicSample están en bazel-testlogs/ui/espresso/BasicSample/BasicSampleInstrumentationTest. Ejecuta lo siguiente:

tree bazel-testlogs/ui/espresso/BasicSample/BasicSampleInstrumentationTest

Esto da como resultado el siguiente resultado:


$ tree bazel-testlogs/ui/espresso/BasicSample/BasicSampleInstrumentationTest
.
├── adb.409923.log
├── broker_logs
│   ├── aapt_binary.10.ok.txt
│   ├── aapt_binary.11.ok.txt
│   ├── adb.12.ok.txt
│   ├── adb.13.ok.txt
│   ├── adb.14.ok.txt
│   ├── adb.15.fail.txt
│   ├── adb.16.ok.txt
│   ├── adb.17.fail.txt
│   ├── adb.18.ok.txt
│   ├── adb.19.fail.txt
│   ├── adb.20.ok.txt
│   ├── adb.21.ok.txt
│   ├── adb.22.ok.txt
│   ├── adb.23.ok.txt
│   ├── adb.24.fail.txt
│   ├── adb.25.ok.txt
│   ├── adb.26.fail.txt
│   ├── adb.27.ok.txt
│   ├── adb.28.fail.txt
│   ├── adb.29.ok.txt
│   ├── adb.2.ok.txt
│   ├── adb.30.ok.txt
│   ├── adb.3.ok.txt
│   ├── adb.4.ok.txt
│   ├── adb.5.ok.txt
│   ├── adb.6.ok.txt
│   ├── adb.7.ok.txt
│   ├── adb.8.ok.txt
│   ├── adb.9.ok.txt
│   ├── android_23_x86.1.ok.txt
│   └── exec-1
│       ├── adb-2.txt
│       ├── emulator-2.txt
│       └── mksdcard-1.txt
├── device_logcat
│   └── logcat1635880625641751077.txt
├── emulator_itCqtc.log
├── outputs.zip
├── pipe.log.txt
├── telnet_pipe.log.txt
└── tmpuRh4cy
    ├── watchdog.err
    └── watchdog.out

4 directories, 41 files

Cómo leer registros del emulador

Los registros del emulador para los destinos android_device se almacenan en el directorio /tmp/ con el nombre emulator_xxxxx.log, en el que xxxxx es una secuencia de caracteres generada de forma aleatoria.

Usa este comando para encontrar el registro más reciente del emulador:

ls -1t /tmp/emulator_*.log | head -n 1

Pruebas en varios niveles de API

Si quieres probar varios niveles de API, puedes utilizar una comprensión de listas para crear objetivos de prueba para cada nivel de API. Por ejemplo:

API_LEVELS = [
    "19",
    "20",
    "21",
    "22",
]

[android_instrumentation_test(
    name = "my_test_%s" % API_LEVEL,
    test_app = ":my_test_app",
    target_device = "@android_test_support//tools/android/emulated_devices/generic_phone:android_%s_x86_qemu2" % API_LEVEL,
) for API_LEVEL in API_LEVELS]

Errores conocidos

  • Los procesos del servidor de adb bifurcados no se finalizan después de las pruebas
  • Si bien la compilación de APK funciona en todas las plataformas (Linux, macOS y Windows), las pruebas solo funcionan en Linux.
  • Incluso con --config=local_adb, los usuarios aún deben especificar android_instrumentation_test.target_device.
  • Si usas un dispositivo o emulador local, Bazel no desinstala los APKs después de la prueba. Limpia los paquetes ejecutando este comando:
adb shell pm list
packages com.example.android.testing | cut -d ':' -f 2 | tr -d '\r' | xargs
-L1 -t adb uninstall