Android 계측 테스트

문제 신고 소스 보기 Nightly · 8.0 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

Bazel을 처음 사용하는 경우 Bazel을 사용하여 Android 빌드 튜토리얼부터 시작하세요.

Android 계측 테스트 병렬 실행

그림 1. Android 계측 테스트를 병렬로 실행합니다.

android_instrumentation_test을 사용하면 개발자가 Android 에뮬레이터와 기기에서 앱을 테스트할 수 있습니다. 실제 Android 프레임워크 API와 Android 테스트 라이브러리를 활용합니다.

견고성과 재현성을 위해 Bazel은 샌드박스에서 Android 에뮬레이터를 만들고 실행하여 테스트가 항상 깨끗한 상태에서 실행되도록 합니다. 각 테스트는 격리된 에뮬레이터 인스턴스를 가져오므로 테스트 간에 상태를 전달하지 않고도 테스트를 동시에 실행할 수 있습니다.

Android 계측 테스트에 관한 자세한 내용은 Android 개발자 문서를 참고하세요.

GitHub Issue Tracker에서 문제를 제출하세요.

작동 방식

android_instrumentation_test 타겟에서 bazel test를 처음 실행하면 Bazel은 다음 단계를 실행합니다.

  1. 테스트 APK, 테스트 대상 APK, 그들의 전이 종속 항목을 빌드합니다.
  2. 깨끗한 에뮬레이터 상태를 만들고 부팅하고 캐시합니다.
  3. 에뮬레이터를 시작합니다.
  4. APK를 설치합니다.
  5. Android Test Orchestrator를 활용하여 테스트를 실행합니다.
  6. 에뮬레이터 종료
  7. 결과를 보고합니다.

후속 테스트 실행에서 Bazel은 2단계에서 만든 클린 캐시된 상태에서 에뮬레이터를 부팅하므로 이전 실행에서 남은 상태가 없습니다. 에뮬레이터 상태를 캐시하면 테스트 실행 속도도 빨라집니다.

기본 요건

환경이 다음 기본 요건을 충족하는지 확인합니다.

  • Linux. Ubuntu 16.04 및 18.04에서 테스트되었습니다.

  • Bazel 0.12.0 이상 bazel info release을 실행하여 버전을 확인합니다.

bazel info release

그러면 다음과 비슷한 출력이 표시됩니다.

release 4.1.0
  • KVM Bazel을 사용하려면 에뮬레이터에 Linux에서 KVM을 사용한 하드웨어 가속이 있어야 합니다. Ubuntu의 경우 다음 설치 안내를 따르세요.

KVM이 올바르게 구성되었는지 확인하려면 다음을 실행합니다.

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

다음 메시지가 출력되면 구성이 올바른 것입니다.

INFO: /dev/kvm exists
KVM acceleration can be used
  • Xvfb. 헤드리스 테스트 (예: CI 서버에서)를 실행하려면 Bazel에 X 가상 프레임버퍼가 필요합니다.

설치하려면 다음을 실행합니다.

apt-get install xvfb

다음을 실행하여 Xvfb가 올바르게 설치되었고 /usr/bin/Xvfb에 설치되었는지 확인합니다.

which Xvfb

출력은 다음과 같습니다.

/usr/bin/Xvfb
  • 32비트 라이브러리 테스트 인프라에서 사용하는 일부 바이너리는 32비트이므로 64비트 머신에서는 32비트 바이너리를 실행할 수 있는지 확인합니다. Ubuntu의 경우 다음 32비트 라이브러리를 설치합니다.
sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2-1.0:i386

시작하기

다음은 android_instrumentation_test의 일반적인 타겟 종속 항목 그래프입니다.

Android 계측 테스트의 타겟 종속 항목 그래프

그림 2. android_instrumentation_test의 타겟 종속 항목 그래프

BUILD 파일

그래프는 다음과 같이 BUILD 파일로 변환됩니다.

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

규칙 android_instrumentation_test의 주요 속성은 다음과 같습니다.

  • test_app: android_binary 타겟입니다. 이 타겟에는 테스트 코드와 Espresso 및 UIAutomator와 같은 종속 항목이 포함되어 있습니다. 선택한 android_binary 타겟은 테스트 중인 앱인 다른 android_binary를 가리키는 instruments 속성을 지정해야 합니다.

  • target_device: android_device 타겟입니다. 이 타겟은 Bazel에서 테스트를 만들고, 실행하고, 실행하는 데 사용하는 Android 에뮬레이터의 사양을 설명합니다. 자세한 내용은 Android 기기 선택 섹션을 참고하세요.

테스트 앱의 AndroidManifest.xml에는 <instrumentation> 태그가 포함되어야 합니다. 이 태그는 타겟 앱의 패키지의 속성과 계측 테스트 실행기의 정규화된 클래스 이름androidx.test.runner.AndroidJUnitRunner을 지정해야 합니다.

다음은 테스트 앱의 AndroidTestManifest.xml 예입니다.

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

WORKSPACE 종속 항목

이 규칙을 사용하려면 프로젝트가 다음 외부 저장소에 종속되어야 합니다.

  • @androidsdk: Android SDK Android 스튜디오를 통해 다운로드합니다.

  • @android_test_support: 테스트 실행기, 에뮬레이터 런처, android_device 타겟을 호스팅합니다. 최신 버전은 여기에서 확인할 수 있습니다.

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()

Maven 종속 항목

Google Maven 또는 Maven Central과 같은 저장소의 Maven 아티팩트에 대한 종속 항목을 관리하려면 rules_jvm_external와 같은 Maven 리졸버를 사용해야 합니다.

이 페이지의 나머지 부분에서는 rules_jvm_external를 사용하여 Maven 저장소에서 종속 항목을 확인하고 가져오는 방법을 보여줍니다.

android_device 타겟 선택

android_instrumentation_test.target_device는 테스트를 실행할 Android 기기를 지정합니다. 이러한 android_device 타겟은 @android_test_support에 정의되어 있습니다.

예를 들어 다음을 실행하여 특정 타겟의 소스를 쿼리할 수 있습니다.

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

다음과 유사한 출력이 생성됩니다.

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

기기 타겟 이름은 다음 템플릿을 사용합니다.

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

android_device를 실행하려면 선택한 API 수준의 system_image가 필요합니다. 시스템 이미지를 다운로드하려면 Android SDK의 tools/bin/sdkmanager를 사용하세요. 예를 들어 generic_phone:android_23_x86의 시스템 이미지를 다운로드하려면 $sdk/tools/bin/sdkmanager "system-images;android-23;default;x86"를 실행합니다.

@android_test_support에서 지원되는 android_device 타겟의 전체 목록을 보려면 다음 명령어를 실행하세요.

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

Bazel은 현재 x86 기반 에뮬레이터만 지원합니다. 성능을 높이려면 QEMU 타겟 대신 QEMU2 android_device 타겟을 사용하세요.

테스트 실행

테스트를 실행하려면 프로젝트의 project root:/.bazelrc 파일에 다음 줄을 추가합니다.

# 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

그런 다음 구성 중 하나를 사용하여 테스트를 실행합니다.

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

구성 하나만 사용해야 합니다. 그렇지 않으면 테스트가 실패합니다.

헤드리스 테스트

Xvfb를 사용하면 그래픽 인터페이스가 없는 에뮬레이터로 테스트할 수 있습니다(헤드리스 테스트라고도 함). 테스트를 실행할 때 그래픽 인터페이스를 사용 중지하려면 테스트 인수 --enable_display=false를 Bazel에 전달합니다.

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

GUI 테스트

$DISPLAY 환경 변수가 설정된 경우 테스트가 실행되는 동안 에뮬레이터의 그래픽 인터페이스를 사용 설정할 수 있습니다. 이렇게 하려면 다음 테스트 인수를 Bazel에 전달합니다.

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

로컬 에뮬레이터 또는 기기로 테스트

Bazel은 로컬에서 실행된 에뮬레이터나 연결된 기기에서 직접 테스트를 지원합니다. --test_strategy=exclusive--test_arg=--device_broker_type=LOCAL_ADB_SERVER 플래그를 전달하여 로컬 테스트 모드를 사용 설정합니다. 연결된 기기가 두 대 이상인 경우 --test_arg=--device_serial_number=$device_id 플래그를 전달합니다. 여기서 $device_idadb devices에 나열된 기기/에뮬레이터의 ID입니다.

샘플 프로젝트

표준 프로젝트 샘플을 찾고 있다면 Espresso 및 UIAutomator를 사용하는 프로젝트의 Android 테스트 샘플을 참고하세요.

Espresso 설정

Espresso(androidx.test.espresso)로 UI 테스트를 작성하는 경우 다음 스니펫을 사용하여 일반적으로 사용되는 Espresso 아티팩트 및 종속 항목 목록으로 Bazel 워크스페이스를 설정할 수 있습니다.

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

이러한 종속 항목을 정리하는 한 가지 방법은 project root/BUILD.bazel 파일에 //:test_deps 공유 라이브러리를 만드는 것입니다.

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

그런 다음 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",
    ],
)

마지막으로 테스트 android_binary 타겟에서 //:test_deps 종속 항목을 추가합니다.

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

테스트 로그 읽기

--test_output=errors를 사용하여 실패한 테스트의 로그를 출력하거나 --test_output=all를 사용하여 모든 테스트 출력을 출력합니다. 개별 테스트 로그를 찾으려면 $PROJECT_ROOT/bazel-testlogs/path/to/InstrumentationTestTargetName로 이동하세요.

예를 들어 BasicSample 표준 프로젝트의 테스트 로그가 bazel-testlogs/ui/espresso/BasicSample/BasicSampleInstrumentationTest에 있으면 다음을 실행합니다.

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

그러면 다음과 같이 출력됩니다.


$ 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

에뮬레이터 로그 읽기

android_device 타겟의 에뮬레이터 로그는 /tmp/ 디렉터리에 emulator_xxxxx.log라는 이름으로 저장되며, 여기서 xxxxx은 무작위로 생성된 문자 시퀀스입니다.

다음 명령어를 사용하여 최신 에뮬레이터 로그를 찾습니다.

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

여러 API 수준 테스트

여러 API 수준에서 테스트하려면 목록 이해를 사용하여 각 API 수준의 테스트 타겟을 만들 수 있습니다. 예를 들면 다음과 같습니다.

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]

알려진 문제

  • 포크된 adb 서버 프로세스가 테스트 후 종료되지 않음
  • APK 빌드는 모든 플랫폼 (Linux, macOS, Windows)에서 작동하지만 테스트는 Linux에서만 작동합니다.
  • --config=local_adb를 사용해도 사용자는 android_instrumentation_test.target_device를 지정해야 합니다.
  • 로컬 기기나 에뮬레이터를 사용하는 경우 Bazel은 테스트 후 APK를 제거하지 않습니다. 다음 명령어를 실행하여 패키지를 정리합니다.
adb shell pm list
packages com.example.android.testing | cut -d ':' -f 2 | tr -d '\r' | xargs
-L1 -t adb uninstall