Android इंस्ट्रूमेंटेशन टेस्ट

अगर Bazel का इस्तेमाल पहली बार किया जा रहा है, तो Bazel की मदद से Android ऐप्लिकेशन बनाने का ट्यूटोरियल देखें.

Android इंस्ट्रूमेंटेशन टेस्ट को एक साथ चलाना

पहली इमेज. Android इंस्ट्रूमेंटेशन टेस्ट को एक साथ चलाना.

android_instrumentation_test की मदद से, डेवलपर अपने ऐप्लिकेशन को Android एम्युलेटर और डिवाइसों पर टेस्ट कर सकते हैं. यह Android फ़्रेमवर्क के असली एपीआई और Android Test Library का इस्तेमाल करता है.

Bazel, सैंडबॉक्स में Android एम्युलेटर बनाता और लॉन्च करता है. इससे, यह पक्का होता है कि टेस्ट हमेशा साफ़-सुथरे एनवायरमेंट में चलें. साथ ही, टेस्ट के नतीजे हमेशा एक जैसे हों. हर टेस्ट के लिए, एम्युलेटर का अलग इंस्टेंस मिलता है. इससे, टेस्ट एक साथ चलाए जा सकते हैं. साथ ही, एक टेस्ट से दूसरे टेस्ट में स्टेट पास करने की ज़रूरत नहीं होती.

Android इंस्ट्रूमेंटेशन टेस्ट के बारे में ज़्यादा जानकारी के लिए, Android developer documentation देखें.

कृपया GitHub के समस्या को ट्रैक करने वाले टूल में समस्याएं सबमिट करें.

यह कैसे काम करता है

जब android_instrumentation_test टारगेट के लिए, bazel test पहली बार चलाया जाता है, तो Bazel ये काम करता है:

  1. टेस्ट APK, टेस्ट किए जाने वाले APK, और उनकी ट्रांज़िटिव डिपेंडेंसी बनाता है
  2. साफ़-सुथरे एम्युलेटर स्टेट बनाता है, बूट करता है, और उन्हें कैश करता है
  3. एम्युलेटर शुरू करता है
  4. एपीके इंस्टॉल करता है
  5. Android Test Orchestrator का इस्तेमाल करके टेस्ट चलाता है
  6. एम्युलेटर बंद करता है
  7. नतीजों की रिपोर्ट करता है

इसके बाद के टेस्ट रन में, Bazel, एम्युलेटर को दूसरे चरण में बनाए गए साफ़-सुथरे, कैश किए गए स्टेट से बूट करता है. इसलिए, पिछले रन के बचे हुए स्टेट नहीं होते. एम्युलेटर स्टेट को कैश करने से, टेस्ट रन की स्पीड भी बढ़ जाती है.

ज़रूरी शर्तें

पक्का करें कि आपका एनवायरमेंट, इन ज़रूरी शर्तों को पूरा करता हो:

  • 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. बिना ग्राफ़िकल इंटरफ़ेस वाले टेस्ट (उदाहरण के लिए, सीआई सर्वर पर) चलाने के लिए, 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 इंस्ट्रूमेंटेशन टेस्ट में टारगेट डिपेंडेंसी ग्राफ़

दूसरी इमेज. 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 टारगेट के लिए, instruments एट्रिब्यूट की जानकारी देना ज़रूरी है. यह एट्रिब्यूट, किसी दूसरे android_binary की ओर इशारा करता है. यह दूसरा android_binary, टेस्ट किया जाने वाला ऐप्लिकेशन होता है.

  • target_device: एक android_device टारगेट. इस टारगेट में, Android एम्युलेटर की खास जानकारी दी जाती है. Bazel, इस एम्युलेटर का इस्तेमाल करके टेस्ट बनाता है, लॉन्च करता है, और चलाता है. ज़्यादा जानकारी के लिए, Android डिवाइस चुनने के बारे में जानकारी देने वाला सेक्शन देखें.

टेस्ट ऐप्लिकेशन की AndroidManifest.xml में, an <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 डिपेंडेंसी

इस नियम का इस्तेमाल करने के लिए, आपके प्रोजेक्ट को इन बाहरी रिपॉज़िटरी पर निर्भर रहना होगा:

इन डिपेंडेंसी को चालू करने के लिए, अपनी 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 डिपेंडेंसी

रिपॉज़िटरी से Maven आर्टफ़ैक्ट पर डिपेंडेंसी मैनेज करने के लिए, जैसे कि Google Maven या Maven Central, Maven रिज़ॉल्वर का इस्तेमाल करना चाहिए. जैसे कि rules_jvm_external.

इस पेज के बाकी हिस्से में, Maven रिपॉज़िटरी से डिपेंडेंसी को हल करने और फ़ेच करने के लिए, rules_jvm_external का इस्तेमाल करने का तरीका बताया गया है.

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 ज़रूरी है. सिस्टम इमेज डाउनलोड करने के लिए, 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 की मदद से, एम्युलेटर के साथ बिना ग्राफ़िकल इंटरफ़ेस वाले टेस्ट किए जा सकते हैं. इन्हें हेडलेस टेस्टिंग भी कहा जाता है. टेस्ट चलाते समय, ग्राफ़िकल इंटरफ़ेस बंद करने के लिए, Bazel को --enable_display=false टेस्ट आर्ग्युमेंट पास करें:

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

जीयूआई टेस्ट

अगर $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_id डिवाइस/एम्युलेटर की आईडी है जो adb devices में शामिल है.

सैंपल प्रोजेक्ट

अगर आपको कैननिकल प्रोजेक्ट के सैंपल चाहिए, तो Android टेस्टिंग के सैंपल देखें. इनमें Espresso और UIAutomator का इस्तेमाल करने वाले प्रोजेक्ट शामिल हैं.

Espresso सेटअप

अगर Espresso (androidx.test.espresso) की मदद से यूआई टेस्ट लिखे जाते हैं, तो Bazel के अपने वर्कस्पेस को, आम तौर पर इस्तेमाल किए जाने वाले Espresso आर्टफ़ैक्ट और उनकी डिपेंडेंसी की सूची के साथ सेट अप करने के लिए, यहां दिए गए स्निपेट का इस्तेमाल किया जा सकता है:

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_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 सर्वर की फ़ोर्क की गई प्रोसेस, टेस्ट के बाद बंद नहीं होती हैं
  • एपीके बनाने की सुविधा सभी प्लैटफ़ॉर्म (Linux, macOS, Windows) पर काम करती है. हालांकि, टेस्ट करने की सुविधा सिर्फ़ Linux पर काम करती है.
  • --config=local_adb का इस्तेमाल करने पर भी, उपयोगकर्ताओं को android_instrumentation_test.target_device की जानकारी देनी पड़ती है.
  • स्थानीय डिवाइस या एम्युलेटर का इस्तेमाल करने पर, Bazel, टेस्ट के बाद एपीके अनइंस्टॉल नहीं करता. पैकेज साफ़ करने के लिए, यह निर्देश चलाएं:
adb shell pm list
packages com.example.android.testing | cut -d ':' -f 2 | tr -d '\r' | xargs
-L1 -t adb uninstall