我该如何为我的UI测试项目添加额外的Android权限?

33

我尝试在一个基于 InstrumentationTestCase2 的测试用例中将文件写入外部SD卡,这仅用于纯测试目的。当应用程序测试文件的 AndroidManifest.xml 文件中配置了 android.permission.WRITE_EXTERNAL_STORAGE 的设置时,这一切都很好,但是如果此设置仅存在于测试项目的 AndroidManifest.xml 文件中,则无法正常工作。

自然地,我不想在主清单中添加此权限,因为我只需要在功能测试期间使用此功能。我该如何实现?

2个回答

40

简而言之,你应该在应用程序的清单和测试项目的清单中添加相同的android:sharedUserId,并为测试项目声明必要的权限。

这个解决方法源于Android实际上为Linux用户帐户(uid)分配权限,而不是为应用程序本身分配权限(默认情况下,每个应用程序都有自己的uid,因此看起来权限是针对每个应用程序设置的)。

然而,使用相同证书签名的应用程序可以共享相同的uid。因此,它们具有共同的权限集。例如,我可以有一个请求WRITE_EXTERNAL_STORAGE权限的应用程序A和一个请求INTERNET权限的应用程序B。A和B都由相同的证书签名(假设是调试证书)。在A和B的AndroidManifest.xml文件中,android:sharedUserId="test.shared.id"<manifest>标记中声明。然后,尽管它们只声明所需权限的一部分,但A和B都可以访问网络并写入SD卡,因为权限是按uid分配的。当然,这仅适用于安装了A和B的情况。

以下是在测试项目中设置的示例。应用程序的AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.testproject"
    android:versionCode="1"
    android:versionName="1.0"
    android:sharedUserId="com.example.testproject.uid">

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="16" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name">
        <activity
            android:name="com.example.testproject.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />    
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

这是一个测试项目的AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.testproject.test"
    android:sharedUserId="com.example.testproject.uid"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <instrumentation
        android:name="android.test.InstrumentationTestRunner"
        android:targetPackage="com.example.testproject" />

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <uses-library android:name="android.test.runner" />
    </application>

</manifest>

这种解决方案的缺点是,当测试包被安装时,应用程序也能够写入外部存储。如果它意外地向存储器写入某些内容,可能会在发布时未被注意到,因为该软件包将使用不同的密钥进行签名。

关于共享UID的一些附加信息可以在http://developer.android.com/guide/topics/security/permissions.html#userid找到。


完美而全面的答案!现在,这是一个非常好的用例,可以使用已经“弃用”的共享用户ID。我可以接受您提到的缺点,但最重要的是将测试权限与生产权限分开,这一点已经实现了。再次感谢! - Thomas Keller
很好的解决方法,但我仍然不明白,当test.apk拥有所有所需的权限时,为什么它仍然会抛出错误? - Prateek Jain
4
你好!很好的解决方法,谢谢!不过似乎从Android 6(API级别23)开始,可能由于新的权限系统,权限不再合并。我个人没有找到任何解释这一点的文档,所以我的假设可能不正确。除了我之外,还有其他人遇到这个问题吗? - Bianca Daniciuc
你救了我的一天! - ospider
1
很不幸,sharedUserId自API 29起已被弃用。当我使用sharedUserId在旧版本上安装应用程序时,也会出现错误,这意味着我无法将新版本的应用程序部署到生产环境,因为用户将无法升级。 - Sam

16

还有一个简单的答案。

src/debug/AndroidManifest.xml中设置权限。如果该文件不存在,请创建它。

默认情况下,AndroidTest使用debug作为BuildType,因此,如果您在此定义测试权限,则清单合并过程将将这些权限添加到UI测试构建中。

这是带有新权限的清单。请注意,我没有包括package属性,因为它将从较低优先级的清单继承。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
</manifest>

此外,如果您想将这些权限应用于除debug之外的其他buildType,只需将新的AndroidManifest.xml移动到所需文件夹,并使用以下内容:


android {
   ...
   testBuildType 'release' // or other buildType you might have like 'testUI'
}

我已经尝试了这个解决方案,但是没有成功。我仍然收到错误信息:“java.io.IOException: open failed: EACCES (Permission denied)”。 - mra419
你把testBuildType改成了release了吗? - Maher Abuthraa
当我更改我的testBuildType时,我的测试代码无法编译通过。在build.gradle中定义的测试库,例如androidTestImplementation'androidx.test:core:1.2.0',无法从我的Java测试代码中引用。是否还需要其他步骤才能使其正常工作? - Sam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接