Testes de instrumentação do Android

Informar um problema Ver fonte Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

Se você não conhece o Bazel, comece com o tutorial Como criar apps Android com o Bazel.

Executar testes de instrumentação do Android em paralelo

Figura 1. Execução de testes de instrumentação do Android em paralelo.

O android_instrumentation_test permite que os desenvolvedores testem os apps em emuladores e dispositivos Android. Ele usa APIs reais do framework Android e a biblioteca de teste do Android.

Para hermeticidade e reprodutibilidade, o Bazel cria e inicia emuladores do Android em uma sandbox, garantindo que os testes sempre sejam executados em um estado limpo. Cada teste recebe uma instância isolada do emulador, permitindo que os testes sejam executados em paralelo sem passar estados entre eles.

Para mais informações sobre testes de instrumentação do Android, consulte a documentação para desenvolvedores Android.

Registre problemas no rastreador de problemas do GitHub (em inglês).

Como funciona

Quando você executa bazel test em um destino android_instrumentation_test pela primeira vez, o Bazel realiza as seguintes etapas:

  1. Cria o APK de teste, o APK em teste e as dependências transitivas deles.
  2. Cria, inicializa e armazena em cache estados limpos do emulador.
  3. Inicia o emulador
  4. Instala os APKs
  5. Executa testes usando o Android Test Orchestrator.
  6. Desliga o emulador.
  7. Informa os resultados

Em execuções de teste subsequentes, o Bazel inicializa o emulador do estado limpo e armazenado em cache criado na etapa 2. Assim, não há estados restantes de execuções anteriores. O armazenamento em cache do estado do emulador também acelera as execuções de teste.

Pré-requisitos

Verifique se o ambiente atende aos seguintes pré-requisitos:

  • Linux. Testado no Ubuntu 16.04 e 18.04.

  • Bazel 0.12.0 ou mais recente. Verifique a versão executando bazel info release.

bazel info release

Isso resulta em uma saída semelhante a esta:

release 4.1.0

Para verificar se o KVM tem a configuração correta, execute:

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

Se a seguinte mensagem for exibida, a configuração está correta:

INFO: /dev/kvm exists
KVM acceleration can be used
  • Xvfb. Para executar testes sem interface gráfica (por exemplo, em servidores de CI), o Bazel exige o framebuffer virtual X.

Para instalar, execute:

apt-get install xvfb

Verifique se Xvfb está instalado corretamente e em /usr/bin/Xvfb executando:

which Xvfb

A saída é esta:

/usr/bin/Xvfb
  • Bibliotecas de 32 bits. Alguns dos binários usados pela infraestrutura de teste são de 32 bits. Portanto, em máquinas de 64 bits, verifique se os binários de 32 bits podem ser executados. Para o Ubuntu, instale estas bibliotecas de 32 bits:
sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2-1.0:i386

Primeiros passos

Este é um gráfico de dependência de destino típico de um android_instrumentation_test:

O gráfico de dependência de destino em um teste de instrumentação Android

Figura 2. Gráfico de dependência de destino de um android_instrumentation_test.

Arquivo BUILD

O gráfico é traduzido em um arquivo BUILD assim:

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",
    ]
    # ...
)

Os principais atributos da regra android_instrumentation_test são:

  • test_app: um destino android_binary. Esse destino contém código de teste e dependências como Espresso e UIAutomator. O android_binary de destino selecionado precisa especificar um atributo instruments que aponta para outro android_binary, que é o app em teste.

  • target_device: um destino android_device. Essa meta descreve as especificações do emulador do Android que o Bazel usa para criar, iniciar e executar os testes. Consulte a seção sobre como escolher um dispositivo Android para mais informações.

O AndroidManifest.xml do app de teste precisa incluir uma tag <instrumentation>. Essa tag precisa especificar os atributos do pacote do app de destino e do nome de classe totalmente qualificado do executor de testes de instrumentação, androidx.test.runner.AndroidJUnitRunner.

Confira um exemplo de AndroidTestManifest.xml para o app de teste:

<?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>

Dependências do WORKSPACE

Para usar essa regra, seu projeto precisa depender destes repositórios externos:

  • @androidsdk: o SDK do Android. Faça o download pelo Android Studio.

  • @android_test_support: hospeda o executor de testes, o iniciador de emulador e os destinos android_device. Confira a versão mais recente aqui.

Para ativar essas dependências, adicione as seguintes linhas ao arquivo 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()

Dependências do Maven

Para gerenciar dependências de artefatos Maven de repositórios, como Google Maven ou Maven Central, use um resolvedor Maven, como rules_jvm_external.

O restante desta página mostra como usar o rules_jvm_external para resolver e buscar dependências de repositórios Maven.

Escolher um destino android_device

android_instrumentation_test.target_device especifica em qual dispositivo Android os testes serão executados. Esses destinos android_device são definidos em @android_test_support.

Por exemplo, para consultar as fontes de um destino específico, execute:

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

O resultado é semelhante a:

# .../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",
)

Os nomes de destino do dispositivo usam este modelo:

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

Para iniciar um android_device, é necessário o system_image do nível de API selecionado. Para fazer o download da imagem do sistema, use o tools/bin/sdkmanager do Android SDK. Por exemplo, para baixar a imagem do sistema para generic_phone:android_23_x86, execute $sdk/tools/bin/sdkmanager "system-images;android-23;default;x86".

Para conferir a lista completa de destinos android_device compatíveis em @android_test_support, execute o seguinte comando:

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

No momento, o Bazel só é compatível com emuladores baseados em x86. Para melhorar o desempenho, use destinos QEMU2 android_device em vez de QEMU.

Como executar testes

Para executar testes, adicione estas linhas ao arquivo project root:/.bazelrc do projeto.

# 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

Em seguida, use uma das configurações para executar testes:

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

Use apenas uma configuração ou os testes vão falhar.

Teste sem comando

Com Xvfb, é possível testar com emuladores sem a interface gráfica, também conhecida como teste sem interface. Para desativar a interface gráfica ao executar testes, transmita o argumento de teste --enable_display=false ao Bazel:

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

Teste de GUI

Se a variável de ambiente $DISPLAY estiver definida, será possível ativar a interface gráfica do emulador enquanto o teste estiver em execução. Para isso, transmita estes argumentos de teste ao Bazel:

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

Como testar com um emulador ou dispositivo local

O Bazel também permite testar diretamente em um emulador iniciado localmente ou em um dispositivo conectado. Transmita as flags --test_strategy=exclusive e --test_arg=--device_broker_type=LOCAL_ADB_SERVER para ativar o modo de teste local. Se houver mais de um dispositivo conectado, transmita a flag --test_arg=--device_serial_number=$device_id, em que $device_id é o ID do dispositivo/emulador listado em adb devices.

Projetos de amostra

Se você estiver procurando exemplos de projetos canônicos, consulte os exemplos de teste do Android para projetos que usam Espresso e UIAutomator.

Configuração do Espresso

Se você escrever testes de UI com o Espresso (androidx.test.espresso), use os snippets a seguir para configurar seu espaço de trabalho do Bazel com a lista de artefatos do Espresso usados com frequência e as dependências deles:

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

Uma maneira de organizar essas dependências é criar uma biblioteca compartilhada //:test_deps no arquivo 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",
    ],
)

Em seguida, adicione as dependências necessárias em 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 fim, no destino de teste android_binary, adicione a dependência //:test_deps:

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

Dicas

Como ler registros de testes

Use --test_output=errors para imprimir registros de testes com falha ou --test_output=all para imprimir toda a saída do teste. Se você estiver procurando um registro de teste individual, acesse $PROJECT_ROOT/bazel-testlogs/path/to/InstrumentationTestTargetName.

Por exemplo, os registros de teste do projeto canônico BasicSample estão em bazel-testlogs/ui/espresso/BasicSample/BasicSampleInstrumentationTest. Execute:

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

Isso resulta na seguinte saída:


$ 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

Como ler registros do emulador

Os registros do emulador para destinos android_device são armazenados no diretório /tmp/ com o nome emulator_xxxxx.log, em que xxxxx é uma sequência de caracteres gerada aleatoriamente.

Use este comando para encontrar o registro mais recente do emulador:

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

Testar em vários níveis de API

Se você quiser testar em vários níveis de API, use uma compreensão de lista para criar destinos de teste para cada nível de API. Exemplo:

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]

Problemas conhecidos

  • Os processos do servidor adb bifurcados não são encerrados após os testes
  • Embora a criação de APKs funcione em todas as plataformas (Linux, macOS e Windows), o teste funciona apenas no Linux.
  • Mesmo com --config=local_adb, os usuários ainda precisam especificar android_instrumentation_test.target_device.
  • Se você estiver usando um dispositivo local ou um emulador, o Bazel não vai desinstalar os APKs após o teste. Limpe os pacotes executando este comando:
adb shell pm list
packages com.example.android.testing | cut -d ':' -f 2 | tr -d '\r' | xargs
-L1 -t adb uninstall