การทดสอบการใช้เครื่องมือ 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 ต้องใช้ Framebuffer เสมือน 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

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

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

กราฟทรัพยากร Dependency เป้าหมายในการทดสอบการใช้เครื่องมือ 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 เป้าหมายนี้มีโค้ดทดสอบและทรัพยากร 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 คุณดูรุ่นล่าสุดได้ที่นี่

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

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

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

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

ตั้งค่าเอสเปรสโซ

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

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 ของการทดสอบ ให้เพิ่มการขึ้นต่อกันของ //: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 หลายระดับ คุณสามารถใช้ความเข้าใจของรายการเพื่อสร้างเป้าหมายการทดสอบสำหรับ 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 shell pm list
packages com.example.android.testing | cut -d ':' -f 2 | tr -d '\r' | xargs
-L1 -t adb uninstall