การทดสอบการใช้เครื่องมือ Android

หากคุณเพิ่งเคยใช้ Bazel ให้เริ่มต้นด้วยบทแนะนำการสร้าง Android ด้วย Bazel

การเรียกใช้การทดสอบการวัดคุมของ Android แบบขนาน

รูปที่ 1 การเรียกใช้การทดสอบการวัดคุมของ Android แบบขนาน

android_instrumentation_test ช่วยให้นักพัฒนาแอปทดสอบแอปในโปรแกรมจำลองและอุปกรณ์ Android ได้ โดยใช้ API ของเฟรมเวิร์ก Android จริงและไลบรารีการทดสอบ Android

Bazel สร้างและเปิดใช้โปรแกรมจำลอง Android ในแซนด์บ็อกซ์เพื่อความปลอดภัยและความสามารถในการทำซ้ำ ซึ่งช่วยให้การทดสอบทำงานจากสถานะที่สะอาดเสมอ การทดสอบแต่ละรายการจะมีอินสแตนซ์ของโปรแกรมจำลองที่แยกกัน ซึ่งช่วยให้การทดสอบทำงานแบบขนานได้โดยไม่ต้องส่งต่อสถานะระหว่างการทดสอบ

ดูข้อมูลเพิ่มเติมเกี่ยวกับการทดสอบการวัดคุมของ Android ได้ที่เอกสารประกอบสำหรับนักพัฒนาซอฟต์แวร์ Android

โปรดแจ้งปัญหาในเครื่องมือติดตามปัญหาบน GitHub

วิธีการทำงาน

เมื่อคุณเรียกใช้ bazel test ในเป้าหมาย android_instrumentation_test เป็นครั้งแรก Bazel จะทำตามขั้นตอนต่อไปนี้

  1. สร้าง APK ทดสอบ, APK ภายใต้การทดสอบ และทรัพยากร Dependency ที่ส่งผ่าน
  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 มีการกำหนดค่าที่ถูกต้อง ให้เรียกใช้คำสั่งต่อไปนี้

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

หากระบบพิมพ์ข้อความต่อไปนี้ แสดงว่าคุณมีการกำหนดค่าที่ถูกต้อง

INFO: /dev/kvm exists
KVM acceleration can be used
  • Xvfb หากต้องการเรียกใช้การทดสอบแบบไม่มีส่วนหัว (เช่น ในเซิร์ฟเวอร์ CI) Bazel กำหนดให้ต้องมี X virtual framebuffer

หากต้องการติดตั้ง ให้เรียกใช้คำสั่งต่อไปนี้

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

เริ่มต้นใช้งาน

นี่คือกราฟทรัพยากร Dependency ของเป้าหมาย android_instrumentation_test โดยทั่วไป

กราฟทรัพยากร Dependency ของเป้าหมายในการทดสอบการวัดคุมของ Android

รูปที่ 2 กราฟทรัพยากร Dependency ของเป้าหมายของ 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 เป้าหมายนี้มีโค้ดทดสอบและทรัพยากร Dependency เช่น Espresso และ UIAutomator เป้าหมาย android_binary ที่เลือกต้องระบุแอตทริบิวต์ instruments ที่ชี้ไปยัง android_binary อื่น ซึ่งเป็นแอปที่อยู่ระหว่างการทดสอบ

  • target_device: เป้าหมาย android_device เป้าหมายนี้อธิบายข้อกำหนดของโปรแกรมจำลองของ Android ที่ Bazel ใช้เพื่อสร้าง เปิดตัว และเรียกใช้การทดสอบ ดูข้อมูลเพิ่มเติมได้ที่ส่วนการเลือกอุปกรณ์ 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>

ทรัพยากร Dependency ของ WORKSPACE

หากต้องการใช้กฎนี้ โปรเจ็กต์ของคุณต้องพึ่งพาที่เก็บภายนอกต่อไปนี้

  • @androidsdk: Android SDK ดาวน์โหลดผ่าน Android Studio

  • @android_test_support: โฮสต์โปรแกรมเรียกใช้การทดสอบ, Launcher โปรแกรมจำลอง และเป้าหมาย android_device คุณดูรุ่นล่าสุด ได้ที่นี่

เปิดใช้ทรัพยากร Dependency เหล่านี้โดยเพิ่มบรรทัดต่อไปนี้ลงในไฟล์ 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()

ทรัพยากร Dependency ของ Maven

หากต้องการจัดการทรัพยากร Dependency ในอาร์ติแฟกต์ Maven จากที่เก็บ เช่น Google Maven หรือ Maven Central คุณควรใช้รีโซลเวอร์ของ Maven เช่น rules_jvm_external

ส่วนที่เหลือของหน้านี้จะแสดงวิธีใช้ rules_jvm_external เพื่อแก้ไขและดึงทรัพยากร Dependency จากที่เก็บของ 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

คุณต้องมี system_image สำหรับระดับ API ที่เลือกจึงจะเปิดใช้งาน android_device ได้ หากต้องการดาวน์โหลดอิมเมจระบบ ให้ใช้ tools/bin/sdkmanager ของ Android SDK เช่น หากต้องการดาวน์โหลดอิมเมจระบบสำหรับ generic_phone:android_23_x86 ให้เรียกใช้ $sdk/tools/bin/sdkmanager "system-images;android-23;default;x86"

หากต้องการดูรายการเป้าหมาย android_device ทั้งหมดที่รองรับใน @android_test_support ให้เรียกใช้คำสั่งต่อไปนี้

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

ปัจจุบัน Bazel รองรับเฉพาะโปรแกรมจำลองที่ใช้ x86 หากต้องการประสิทธิภาพที่ดีขึ้น ให้ใช้ QEMU2 android_device เป้าหมายแทนเป้าหมาย QEMU

การเรียกใช้การทดสอบ

หากต้องการเรียกใช้การทดสอบ ให้เพิ่มบรรทัดต่อไปนี้ลงในไฟล์ 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 ยังรองรับการทดสอบโดยตรงในโปรแกรมจำลองที่เปิดใช้ในเครื่องหรืออุปกรณ์ที่เชื่อมต่อ ด้วย ส่ง Flag --test_strategy=exclusive และ --test_arg=--device_broker_type=LOCAL_ADB_SERVER เพื่อเปิดใช้โหมดทดสอบในเครื่อง หากมีอุปกรณ์ที่เชื่อมต่อมากกว่า 1 เครื่อง ให้ส่ง Flag --test_arg=--device_serial_number=$device_id โดยที่ $device_id คือรหัสของอุปกรณ์/โปรแกรมจำลองที่แสดงใน adb devices

โปรเจ็กต์ตัวอย่าง

หากต้องการดูตัวอย่างโปรเจ็กต์ Canonical โปรดดูตัวอย่างการทดสอบ Android สำหรับโปรเจ็กต์ที่ใช้ Espresso และ UIAutomator

การตั้งค่า Espresso

หากคุณเขียนการทดสอบ UI ด้วย Espresso (androidx.test.espresso) คุณสามารถใช้ข้อมูลโค้ดต่อไปนี้เพื่อตั้งค่าพื้นที่ทำงาน Bazel ด้วยรายการอาร์ติแฟกต์ Espresso ที่ใช้กันโดยทั่วไปและทรัพยากร Dependency ของอาร์ติแฟกต์เหล่านั้น

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

วิธีหนึ่งในการจัดระเบียบทรัพยากร Dependency เหล่านี้คือการสร้างไลบรารีที่แชร์ //:test_deps ในไฟล์ 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",
    ],
)

จากนั้นเพิ่มทรัพยากร Dependency ที่จำเป็นใน 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 ให้เพิ่มทรัพยากร Dependency //: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

ตัวอย่างเช่น บันทึกการทดสอบสำหรับโปรเจ็กต์ Canonical 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 หลายระดับ คุณสามารถใช้ List Comprehension เพื่อสร้างเป้าหมายการทดสอบสำหรับ 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