Android M: 用浓缩咖啡测试权限?

Android M 引入的新权限模式需要在运行时检查特定权限,这意味着需要根据用户是否拒绝或允许访问而提供不同的流。

当我们使用 Espresso 在我们的应用程序上运行自动 UI 测试时,我们如何模拟或更新权限的状态以测试不同的场景?

37248 次浏览

事实上,到目前为止,我知道有两种方法可以做到这一点:

  1. 在测试开始前使用 adb 命令授予权限(文件) :

adb shell pm grant "com.your.package" android.permission.your_permission

  1. 可以单击权限对话框并使用 UIAutomator (文件)设置权限。如果您的测试是用 Espresso 为 android 编写的,那么您可以轻松地将 Espresso 和 UIAutomator 步骤组合在一个测试中。

当你的手机处于英语语境时,试试这种静态方法:

private static void allowPermissionsIfNeeded() {
if (Build.VERSION.SDK_INT >= 23) {
UiDevice device = UiDevice.getInstance(getInstrumentation());
UiObject allowPermissions = device.findObject(new UiSelector().text("Allow"));
if (allowPermissions.exists()) {
try {
allowPermissions.click();
} catch (UiObjectNotFoundException e) {
Timber.e(e, "There is no permissions dialog to interact with ");
}
}
}
}

我找到了 给你

接受的答案实际上并不测试权限对话框; 它只是绕过它。因此,如果权限对话框由于某种原因失败,您的测试将给出一个错误的绿色。我鼓励实际点击“给予权限”按钮来测试整个应用程序的行为。

看看这个解决方案:

public static void allowPermissionsIfNeeded(String permissionNeeded) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !hasNeededPermission(permissionNeeded)) {
sleep(PERMISSIONS_DIALOG_DELAY);
UiDevice device = UiDevice.getInstance(getInstrumentation());
UiObject allowPermissions = device.findObject(new UiSelector()
.clickable(true)
.checkable(false)
.index(GRANT_BUTTON_INDEX));
if (allowPermissions.exists()) {
allowPermissions.click();
}
}
} catch (UiObjectNotFoundException e) {
System.out.println("There is no permissions dialog to interact with");
}
}

在这里找到整个班级: https://gist.github.com/rocboronat/65b1187a9fca9eabfebb5121d818a3c4

顺便说一下,由于这个答案很受欢迎,我们将 PermissionGranter添加到 咖啡师中,这是我们在 Espresso 和 UiAutomator 之上的工具,目的是使仪器测试变得绿色: https://github.com/SchibstedSpain/Barista检查它,因为我们将按发布版本维护它。

在运行测试之前,您可以使用以下内容授予权限:

@Before
public void grantPhonePermission() {
// In M+, trying to call a number will trigger a runtime dialog. Make sure
// the permission is granted before running this test.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getInstrumentation().getUiAutomation().executeShellCommand(
"pm grant " + getTargetContext().getPackageName()
+ " android.permission.CALL_PHONE");
}
}

但是您不能撤销。如果您尝试 pm reset-permissionspm revoke...,进程将被终止。

通过在开始测试之前授予权限,可以很容易地实现这一点。例如,如果您应该在测试运行期间使用相机,您可以按照以下方式授予权限

@Before
public void grantPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getInstrumentation().getUiAutomation().executeShellCommand(
"pm grant " + getTargetContext().getPackageName()
+ " android.permission.CAMERA");
}
}

Android 测试支持库1.0的新版本中,有一个 授予许可规则,您可以在开始任何测试之前在测试中使用它来授予权限。

@Rule public GrantPermissionRule permissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);

Kotlin 溶液

@get:Rule var permissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION)

必须使用 @get:Rule,以避免 java.lang.Exception: The @Rule 'permissionRule' must be public.更多信息 给你

我知道一个答案已经被接受了,但是,与一再建议的 if语句不同,另一个更优雅的方法是在你想要的特定操作系统版本的实际测试中执行以下操作:

@Test
fun yourTestFunction() {
Assume.assumeTrue(Build.VERSION.SDK_INT >= 23)
// the remaining assertions...
}

如果调用 assumeTrue函数时表达式的计算结果为 false,那么测试将停止并被忽略,我假设这是您所希望的,以防测试在 SDK 23之前的设备上执行。

浓咖啡更新

这一行代码授予作为参数列出的每个权限 在拨款方法立即生效。换句话说,应用程序 将被视为如果权限已被授予-没有更多 对话

@Rule @JvmField
val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION)

然后爬上去

dependencies {
...
testImplementation "junit:junit:4.12"
androidTestImplementation "com.android.support.test:runner:1.0.0"
androidTestImplementation "com.android.support.test.espresso:espresso-core:3.0.0"
...
}

参考资料: https://www.kotlindevelopment.com/runtime-permissions-espresso-done-right/

我已经实现了一个利用包装类、重写和构建变量配置的解决方案。解决方案很难解释,在这里可以找到: https://github.com/ahasbini/AndroidTestMockPermissionUtils

它还没有打包在一个 sdk,但主要想法是覆盖功能的 ContextWrapper.checkSelfPermissionActivityCompat.requestPermissions被操纵,并返回模拟结果欺骗应用程序进入不同的场景进行测试,如: 许可被拒绝,因此应用程序请求它,并以授予许可结束。即使应用程序一直拥有该权限,这种情况也会发生,但其想法是它被重写实现的模拟结果所欺骗。

此外,该实现还有一个名为 PermissionRuleTestRule类,可以在测试类中轻松地模拟所有条件,以便无缝地测试权限。还可以进行断言,例如确保应用程序调用了 requestPermissions()

Android 测试支持库中有 GrantPermisonRule,您可以在开始任何测试之前在测试中使用它来授予权限。

@Rule public GrantPermissionRule permissionRule = GrantPermissionRule.grant(android.Manifest.permission.CAMERA, android.Manifest.permission.ACCESS_FINE_LOCATION);

感谢@niklas 提供的解决方案。如果有人想在 Java 中授予多个权限:

 @Rule
public GrantPermissionRule permissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.CAMERA);

如果您需要为单个测试或在运行时而不是规则设置权限,可以使用以下方法:

PermissionRequester().apply {
addPermissions(android.Manifest.permission.RECORD_AUDIO)
requestPermissions()
}

例如:。

@Test
fun openWithGrantedPermission_NavigatesHome() {
launchFragmentInContainer<PermissionsFragment>().onFragment {
setViewNavController(it.requireView(), mockNavController)
PermissionRequester().apply {
addPermissions(android.Manifest.permission.RECORD_AUDIO)
requestPermissions()
}
}


verify {
mockNavController.navigate(R.id.action_permissionsFragment_to_homeFragment)
}
}

对于允许的许可,必要时,我认为最简单的方法是使用 咖啡师PermissionGranter.allowPermissionsIfNeeded(Manifest.permission.GET_ACCOUNTS)直接在测试,需要这个许可。