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 grant 和 pm revoke 只对 运行时权限 有效。像 INTERNET 这种普通权限,你撤销了也没用,系统压根不让你改。
#!/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()));
}
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
);
// 这里你的代码会认为权限已授予
// 实际上系统弹窗根本没出现
}
}
11.4 知识体系总览
我把这一章的内容画成了一张图,方便你理解三条测试路径的关系:
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因为权限问题被用户差评,其实只要测试到位,这些问题完全可以避免。