11、权限测试:adb命令模拟权限授予/撤销、UI自动化测试、权限状态mock

权限测试,说白了就是验证你的App在“有权限”和“没权限”两种状态下,能不能正常干活。我见过太多开发,写完权限申请逻辑就扔给测试,结果一上线,用户拒绝权限后直接闪退——这种坑,咱们今天必须填上。

这一章,我带你走三条路:adb命令模拟UI自动化测试权限状态mock。三条路走通了,权限测试这块你就稳了。

11.1 adb命令:最直接的权限开关

adb命令是测试权限最粗暴、也最有效的方式。你不需要点手机屏幕,一行命令就能授予或撤销权限。我个人习惯在写代码前,先用adb把权限开关测一遍,确认逻辑没问题再写UI。

11.1.1 授予与撤销权限

核心命令就两个:

# 授予权限
adb shell pm grant com.example.app android.permission.CAMERA

# 撤销权限
adb shell pm revoke com.example.app android.permission.CAMERA

嗯,这里要注意:pm grantpm revoke 只对 运行时权限 有效。像 INTERNET 这种普通权限,你撤销了也没用,系统压根不让你改。

小技巧: 我习惯写一个shell脚本,批量测试所有权限的授予/撤销组合。比如:
#!/bin/bash
PACKAGE="com.example.app"
PERMISSIONS=(
  "android.permission.CAMERA"
  "android.permission.ACCESS_FINE_LOCATION"
  "android.permission.RECORD_AUDIO"
)

for perm in "${PERMISSIONS[@]}"; do
  echo "Testing: $perm"
  adb shell pm grant $PACKAGE $perm
  # 这里可以加你的测试逻辑
  adb shell pm revoke $PACKAGE $perm
  # 再测一遍
done

11.1.2 查看当前权限状态

有时候你忘了当前权限是授予还是撤销,可以用这个命令查看:

adb shell dumpsys package com.example.app | grep "runtime permissions"

输出结果里,granted=true 表示已授予,granted=false 表示已撤销。我曾经在调试一个定位权限问题时,发现权限明明授予了,但App就是拿不到位置——后来一查,是系统定位开关没开。所以,权限授予 ≠ 功能可用,这个坑你要记住。

11.2 UI自动化测试:模拟用户操作

adb命令虽然快,但它跳过了UI交互。你想想看,用户是在界面上点击“允许”或“拒绝”的,不是敲命令。所以,UI自动化测试必须上。

11.2.1 用Espresso模拟权限弹窗

Android官方推荐用 Espresso 配合 GrantPermissionRule 来测试权限。但这里有个坑:GrantPermissionRule 会自动授予权限,你没法用它测试“拒绝权限”的场景。

那怎么办?我建议用 UiAutomator 来模拟点击弹窗按钮。代码大概长这样:

@Test
public void testDenyCameraPermission() {
    // 启动需要相机权限的Activity
    onView(withId(R.id.btn_camera)).perform(click());

    // 等待权限弹窗出现
    UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
    UiObject denyButton = device.findObject(new UiSelector()
        .text("拒绝")
        .className("android.widget.Button"));

    if (denyButton.exists()) {
        denyButton.click();
    }

    // 验证App在拒绝权限后的行为
    onView(withText("需要相机权限才能拍照")).check(matches(isDisplayed()));
}
注意: 不同厂商的ROM,权限弹窗的UI可能不一样。华为、小米、OPPO的“拒绝”按钮文字可能不同。我建议在测试脚本里加一个兜底逻辑:如果找不到“拒绝”,就找“禁止”或“不允许”。

11.2.2 多权限组合测试

现实中的App往往需要多个权限。比如一个直播App,需要相机、麦克风、存储三个权限。你不能只测单个权限,要测组合场景。

我整理了一个测试矩阵,你可以照着测:

场景 相机 麦克风 存储 预期行为
全部授予 正常直播
仅拒绝相机 提示“需要相机权限”,可语音直播
仅拒绝麦克风 提示“需要麦克风权限”,可静音直播
全部拒绝 提示“需要权限”,退出直播

这个矩阵看起来简单,但很多团队只测了第一行。我曾经接手过一个项目,用户拒绝相机权限后,App直接崩溃——就是因为没测第二行。

11.3 权限状态mock:绕过系统弹窗

adb和UI自动化都有个问题:慢。每次测试都要安装App、启动、等弹窗。如果你在写单元测试,或者想快速验证某个权限状态下的逻辑,那就需要 mock

11.3.1 用Mockito模拟权限检查

假设你的代码里有一个 PermissionChecker 类,用来检查权限是否授予:

public class PermissionChecker {
    public boolean hasCameraPermission(Context context) {
        return ContextCompat.checkSelfPermission(
            context, Manifest.permission.CAMERA
        ) == PackageManager.PERMISSION_GRANTED;
    }
}

在单元测试里,你可以直接mock这个类:

@Test
public void testCameraPermissionDenied() {
    // 创建mock对象
    PermissionChecker mockChecker = mock(PermissionChecker.class);
    when(mockChecker.hasCameraPermission(any())).thenReturn(false);

    // 注入到ViewModel或Presenter中
    CameraViewModel viewModel = new CameraViewModel(mockChecker);
    viewModel.startCamera();

    // 验证行为
    assertEquals("需要相机权限", viewModel.getErrorMessage().getValue());
}

这样做的好处是:毫秒级完成测试,不需要真机或模拟器。我一般把这种测试放在CI的 unitTest 阶段,每次提交代码都跑一遍。

11.3.2 用Robolectric模拟系统权限

如果你不想mock自己的类,想直接mock系统权限检查,可以用 Robolectric。它允许你在JVM上运行Android代码,并且可以控制权限状态:

@RunWith(RobolectricTestRunner.class)
@Config(sdk = {Build.VERSION_CODES.TIRAMISU})
public class PermissionTest {

    @Test
    public void testNotificationPermissionOnAndroid13() {
        // 模拟未授予通知权限
        ShadowApplication shadowApp = Shadows.shadowOf(
            RuntimeEnvironment.getApplication()
        );
        shadowApp.grantPermissions(
            Manifest.permission.POST_NOTIFICATIONS
        );

        // 这里你的代码会认为权限已授予
        // 实际上系统弹窗根本没出现
    }
}
核心思路: mock的本质是“跳过系统,直接控制结果”。你不需要关心权限弹窗长什么样,只需要告诉代码“现在有权限”或“现在没权限”,然后验证代码的反应。

11.4 知识体系总览

我把这一章的内容画成了一张图,方便你理解三条测试路径的关系:

权限测试三条路径 adb命令模拟 pm grant / pm revoke dumpsys package 查看状态 批量脚本自动化 优点:快速直接 UI自动化测试 Espresso + UiAutomator 模拟点击弹窗按钮 多权限组合测试矩阵 优点:贴近真实用户 权限状态mock Mockito mock权限检查类 Robolectric模拟系统权限 跳过UI,直接控制结果 优点:毫秒级,适合CI 全面覆盖:权限授予/撤销/拒绝/组合场景 三条路径互补:adb快但无UI,UI自动化真实但慢,mock适合单元测试 建议:CI中跑mock + adb,发版前跑UI自动化

11.5 避坑指南

最后,我总结几个实战中容易踩的坑:

  • adb命令在Android 11+有变化:从Android 11开始,pm grant 对某些权限(如 MANAGE_EXTERNAL_STORAGE)不再生效。你需要用 adb shell appops set 来模拟。具体命令我建议你查官方文档,不同版本差异挺大。
  • UI自动化测试的弹窗超时:权限弹窗出现的时间不确定,尤其是低端机。我建议把等待时间设长一点,比如5秒。我曾经在测试机上等过10秒才弹出弹窗——那台机器太卡了。
  • mock不能完全替代真机测试:mock只能测逻辑,测不了系统行为。比如Android 13新增的 POST_NOTIFICATIONS 权限,在mock环境里一切正常,但真机上可能因为厂商定制而表现不同。所以,mock + 真机 才是完整方案。

嗯,这一章的内容就到这里。权限测试说难不难,说简单也不简单——关键在于你愿不愿意花时间把三条路径都走通。我见过太多App因为权限问题被用户差评,其实只要测试到位,这些问题完全可以避免。

公众号:蓝海资料掘金营,微信deep3321