如何使用Mock修改Android設備上的features

背景

手工測試過程中有個測試場景需要刪除測試設備上某個Android feature,往上搜索瞭一圈沒找到有效的操作方法。獲取Android設備所有的feature可以通過adb命令pm list features或者Android代碼Context.getPackageManager().getSystemAvailableFeatures(),但都沒有對應的修改方法。

既然feature是從Context獲取的,那能不能構造一個隻包含我想要的feature的Context呢。順著這個思路,有瞭下面的方案。

方案設計

單元測試常用的Mock技術,就是來構造假/模擬對象的。但如果完全構造,又擔心和真實環境差別較大,測試結果不可靠。能不能從真實Android設備中獲取真實的Context,把不想要的feature去除,再給到被測試方法中呢?

答案是肯定的,通過選用流行的Mock組件Mockito ,官網上給出瞭下面2種Mock Java對象的方式:

  1. mock()/@Mock: create mock
  2. spy()/@Spy: partial mocking, real methods are invoked but still can be verified and stubbed

可以看到Spy這種Mock方式可完美地解決我的需求。

實現(簡化版需求)

業務需求說明

獲取所有Android Features並把feature name打印在日志中,使用adb命令的效果如下:

➜  study git:(master) ✗ adb shell pm list features                                    
feature:reqGlEsVersion=0x30002
feature:android.hardware.audio.output
feature:android.hardware.bluetooth
feature:android.hardware.bluetooth_le
feature:android.hardware.camera
feature:android.hardware.camera.any
feature:android.hardware.camera.autofocus
feature:android.hardware.camera.capability.manual_post_processing
feature:android.hardware.camera.capability.manual_sensor
feature:android.hardware.camera.capability.raw
feature:android.hardware.camera.flash
feature:android.hardware.camera.front
feature:android.hardware.camera.level.full
feature:android.hardware.faketouch
feature:android.hardware.location
feature:android.hardware.location.gps
feature:android.hardware.location.network
feature:android.hardware.microphone
feature:android.hardware.nfc.any
feature:android.hardware.opengles.aep
feature:android.hardware.ram.normal
feature:android.hardware.screen.landscape
feature:android.hardware.screen.portrait
feature:android.hardware.sensor.accelerometer
feature:android.hardware.sensor.compass
feature:android.hardware.sensor.light
feature:android.hardware.sensor.proximity
feature:android.hardware.sensor.stepcounter
feature:android.hardware.telephony
feature:android.hardware.telephony.cdma
feature:android.hardware.telephony.gsm
feature:android.hardware.touchscreen
feature:android.hardware.touchscreen.multitouch
feature:android.hardware.touchscreen.multitouch.distinct
feature:android.hardware.touchscreen.multitouch.jazzhand
feature:android.hardware.usb.accessory
feature:android.hardware.usb.host
feature:android.hardware.vulkan.compute
feature:android.hardware.vulkan.level
feature:android.hardware.vulkan.version=4194307
feature:android.hardware.wifi
feature:android.hardware.wifi.direct
feature:android.software.activities_on_secondary_displays
feature:android.software.app_widgets
feature:android.software.autofill
feature:android.software.backup
feature:android.software.companion_device_setup
feature:android.software.connectionservice
feature:android.software.cts
feature:android.software.device_admin
feature:android.software.file_based_encryption
feature:android.software.input_methods
feature:android.software.live_wallpaper
feature:android.software.managed_users
feature:android.software.midi
feature:android.software.picture_in_picture
feature:android.software.print
feature:android.software.securely_removes_users
feature:android.software.sip
feature:android.software.sip.voip
feature:android.software.verified_boot
feature:android.software.voice_recognizers
feature:android.software.webview

業務需求代碼實現

public class FeaturesUtil {
    private static final String TAG = "FeaturesUtil";

    public static void getFeatures(Context context) {
        PackageManager packageManager = context.getPackageManager();
        FeatureInfo[] featureInfos = packageManager.getSystemAvailableFeatures();

        for (FeatureInfo featureInfo : featureInfos) {
            Log.i(TAG, "feature: " + featureInfo.name);
        }
    }
}

測試代碼實現(去除bluetooth feature)

    @Spy
    Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();;

    @Spy
    PackageManager packageManager = context.getPackageManager();

		@Test
    public void getFeatures() {
        // 去除bluetooth feature
        FeatureInfo[] mockedFeatureInfos = removeFeature(packageManager.getSystemAvailableFeatures(),
                Arrays.asList("android.hardware.bluetooth", "android.hardware.bluetooth_le"));

        when(packageManager.getSystemAvailableFeatures()).thenReturn(mockedFeatureInfos);
        when(context.getPackageManager()).thenReturn(packageManager);

        FeaturesUtil.getFeatures(context);
    }

    /**
     * 根據feature name刪除一個或多個feature
     *
     * @param featureInfos FeatureInfo數組
     * @param featureNames 字符串列表,每個元素是要刪除的FeatureInfo的name
     * @return 刪除之後的FeatureInfo數組
     */
    private FeatureInfo[] removeFeature(FeatureInfo[] featureInfos, List<String> featureNames) {
        List<FeatureInfo> featureInfoList = new ArrayList();

        for (FeatureInfo featureInfo :
             featureInfos) {
            if (featureInfo.name != null && !(featureNames.contains(featureInfo.name))) {
                featureInfoList.add(featureInfo);
            }
        }

        return featureInfoList.toArray(new FeatureInfo[featureInfoList.size()]);
    }

運行測試代碼後Logcat輸出

可以看到bluetooth相關的feature已經沒有瞭

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.hardware.sensor.proximity

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.hardware.sensor.accelerometer

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.hardware.faketouch

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.hardware.usb.accessory

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.hardware.telephony.cdma

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.software.backup

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.hardware.touchscreen

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.hardware.touchscreen.multitouch

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.software.print

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.software.activities_on_secondary_displays

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.software.voice_recognizers

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.software.picture_in_picture

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.hardware.opengles.aep

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.hardware.camera.autofocus

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.hardware.telephony.gsm

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.software.sip.voip

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.hardware.usb.host

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.hardware.audio.output

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.software.verified_boot

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.hardware.camera.flash

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.hardware.camera.front

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.hardware.screen.portrait

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.hardware.microphone

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.software.autofill

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.software.securely_removes_users

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.hardware.sensor.compass

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.hardware.touchscreen.multitouch.jazzhand

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.software.app_widgets

2021-04-07 13:23:39.266 16238-16268/? I/FeaturesUtil: feature: android.software.input_methods

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.hardware.sensor.light

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.hardware.vulkan.version

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.software.companion_device_setup

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.software.device_admin

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.hardware.camera

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.hardware.screen.landscape

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.hardware.ram.normal

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.software.managed_users

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.software.webview

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.hardware.sensor.stepcounter

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.hardware.camera.capability.manual_post_processing

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.hardware.camera.any

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.hardware.camera.capability.raw

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.hardware.vulkan.compute

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.software.connectionservice

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.hardware.touchscreen.multitouch.distinct

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.hardware.location.network

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.software.cts

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.software.sip

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.hardware.camera.capability.manual_sensor

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.hardware.camera.level.full

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.hardware.wifi.direct

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.software.live_wallpaper

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.hardware.location.gps

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.software.midi

2021-04-07 13:23:39.267 16238-16268/? I/FeaturesUtil: feature: android.hardware.nfc.any

2021-04-07 13:23:39.268 16238-16268/? I/FeaturesUtil: feature: android.hardware.wifi

2021-04-07 13:23:39.268 16238-16268/? I/FeaturesUtil: feature: android.hardware.location

2021-04-07 13:23:39.268 16238-16268/? I/FeaturesUtil: feature: android.hardware.vulkan.level

2021-04-07 13:23:39.268 16238-16268/? I/FeaturesUtil: feature: android.hardware.telephony

2021-04-07 13:23:39.268 16238-16268/? I/FeaturesUtil: feature: android.software.file_based_encryption

2021-04-07 13:23:39.269 16238-16268/? I/TestRunner: finished: getFeatures(com.aniu.featuresmock.FeaturesUtilTest)

總結

  • 不直接使用Mock,而是使用Spy,最大限度保持測試環境真實可靠,從而保證測試效果
  • 測試代碼不要放在test目錄,要放在androidTest目錄,保證在真實設備上跑(不然代碼也會報錯)。目錄結構如下:

以上就是如何使用Mock修改Android設備上的features的詳細內容,更多關於Mock修改Android features的資料請關註WalkonNet其它相關文章!

推薦閱讀:

    None Found