Robolectric 找不到对 Framework 的新更改
Robolectric can't find new changes to Framework
我在 Android 9
中为系统服务添加了一个新方法
有系统库,为开发者提供API,使用修改后的框架。库工作正常并成功访问新方法。
我想使用 Robolectric 测试库。
声明
#########################
# Robolectric test target
#########################
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
mockito-robolectric-prebuilt \
truth-prebuilt
LOCAL_JAVA_LIBRARIES := \
junit \
platform-robolectric-3.6.1-prebuilt
LOCAL_JAVA_LIBRARIES += com.mars.thelibrary
LOCAL_INSTRUMENTATION_FOR := TheService
LOCAL_MODULE := TheServiceRoboTests
LOCAL_MODULE_TAGS := optional
include $(BUILD_STATIC_JAVA_LIBRARY)
###########################
# Robolectric runner target
###########################
include $(CLEAR_VARS)
LOCAL_MODULE := RunTheServiceRoboTests
LOCAL_SDK_VERSION := current
LOCAL_STATIC_JAVA_LIBRARIES := TheServiceRoboTests
LOCAL_TEST_PACKAGE := TheService
LOCAL_JAVA_LIBRARIES += com.mars.thelibrary
include prebuilts/misc/common/robolectric/3.6.1/run_robotests.mk
测试
// TestConfig.java
public class TestConfig {
public static final int SDK_VERSION = 26;
public static final String MANIFEST_PATH = "TheAwesomeLibrary/tests/robotests/AndroidManifest.xml";
}
// PackageUtilitiesTest.java
@RunWith(RobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class PackageUtilitiesTest {
private Installer installer; // Modified System Service
private PackageUtilities utilities; // Library Class
@Before
public void setUp() throws InstallerException {
installer = mock(Installer.class);
utilities = new PackageUtilities(installer);
}
@Test
public void disableApp_shouldSuspendCache() throws InstallerException {
// Prepare
doNothing()
.when(installer)
.suspendCache(anyString(), anyInt());
// Do
utilities.disableApp("com.mars.betrayer", true);
// Check
verify(installer, times(1)).suspendCache(anyString(), anyInt());
}
}
在准备阶段失败并出现错误
java.lang.NoSuchMethodError: com.android.server.pm.Insteller.suspendCache(Ljava/lang/String;I)V
但我很确定该方法存在,因为该库在设备上可以正常工作。
在所有其他 Robolectric 测试中,库使用未修改的框架部分,因此所有其他测试都按预期工作并通过。
如何正确编写 robolectric 测试?如何使它们与修改后的框架一起工作?
tl;博士
两个原因:
- Robolectric 来自预制件
- SDK 版本不是最新的
目前,sdk版本应该和平台版本一致。例如。对于 Android 9:
@Config(sdk = Build.VERSION_CODES.P)
(仍然与 android-12.0 相关。0_r4)
但是
这行不通,您会看到一个新错误:
java.lang.UnsupportedOperationException: Robolectric does not support API level 28.
为避免这种情况,您必须使用 external/robolectric-shadows
中的 robolectric 而不是 prebuilts/misc/common/robolectric
变化
include prebuilts/misc/common/robolectric/3.6.1/run_robotests.mk
到
include external/robolectric-shadows/run_robotests.mk
并像这样更改依赖项(注意 LOCAL_JAVA_LIBRARIES
声明):
#########################
# Robolectric test target
#########################
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAVA_LIBRARIES := \
mockito-robolectric-prebuilt \
junit \
Robolectric_all-target \
LOCAL_INSTRUMENTATION_FOR := TheService
LOCAL_MODULE := TheServiceRoboTests
LOCAL_MODULE_TAGS := optional
include $(BUILD_STATIC_JAVA_LIBRARY)
###########################
# Robolectric runner target
###########################
include $(CLEAR_VARS)
LOCAL_MODULE := RunTheServiceRoboTests
LOCAL_SDK_VERSION := current
LOCAL_STATIC_JAVA_LIBRARIES := TheServiceRoboTests
LOCAL_TEST_PACKAGE := TheService
LOCAL_JAVA_LIBRARIES := \
TheServiceRoboTests \
guava \
Robolectric_all-target \
robolectric_android-all-stub \
mockito-robolectric-prebuilt \
include external/robolectric-shadows/run_robotests.mk
将来会有另一种方法,这将有助于不再指定每个版本的确切版本:
@Config(sdk = Build.VERSION_CODES.CUR_DEVELOPMENT)
见1819020: Makes a special entry for tree-built Robolectric
它是如何工作的
设置SDK版本时
@Config(sdk = 26)
然后 robolectric 将尝试在 robolectric: SdkConfig.java
中支持的 SDK 映射中找到它
new HashMap<Integer, SdkVersion>() {
{
addSdk(Build.VERSION_CODES.JELLY_BEAN, "4.1.2_r1", "r1", "REL");
addSdk(Build.VERSION_CODES.JELLY_BEAN_MR1, "4.2.2_r1.2", "r1", "REL");
addSdk(Build.VERSION_CODES.JELLY_BEAN_MR2, "4.3_r2", "r1", "REL");
addSdk(Build.VERSION_CODES.KITKAT, "4.4_r1", "r2", "REL");
addSdk(Build.VERSION_CODES.LOLLIPOP, "5.0.2_r3", "r0", "REL");
addSdk(Build.VERSION_CODES.LOLLIPOP_MR1, "5.1.1_r9", "r2", "REL");
addSdk(Build.VERSION_CODES.M, "6.0.1_r3", "r1", "REL");
addSdk(Build.VERSION_CODES.N, "7.0.0_r1", "r1", "REL");
addSdk(Build.VERSION_CODES.N_MR1, "7.1.0_r7", "r1", "REL");
addSdk(Build.VERSION_CODES.O, "8.0.0_r4", "r1", "REL");
addSdk(Build.VERSION_CODES.O_MR1, "8.1.0", "4611349", "REL");
addSdk(Build.VERSION_CODES.P, "9", "4913185-2", "REL");
addSdk(Build.VERSION_CODES.Q, "10", "5803371", "REL");
// BEGIN-INTERNAL
// TODO: Update jar with final R release.
addSdk(Build.VERSION_CODES.R, "R-beta2", "6625208", "REL");
addSdk(Build.VERSION_CODES.S, "S", "r0", "S");
// END-INTERNAL
}
在 AOSP 中,Robolectric 将框架加载为 .jar
文件。当 Runner 开始测试时,它会查找合适的受支持的 jar。
它将 28 与 Build.VERSION_CODES.O
匹配,采用 Sdk(Build.VERSION_CODES.O, "8.0.0_r4", "r1", "REL")
,并使用它来构建 jar
的名称
public DependencyJar getAndroidSdkDependency() {
return createDependency("org.robolectric", "android-all", getSdkVersion().getAndroidVersion() + "-robolectric-" + getSdkVersion().getRobolectricVersion(), null);
}
例如,对于 Android 8 (26; Build.VERSION_CODES.O) 它将尝试使用已发布的 android 版本的 android-all-8.0.0_r4-robolectric-r1.jar
。
因此,当 run_robotests.mk
启动测试时,here it copies all existing jar files from prebuilts/misc/common/robolectric/android-all 并将其添加到 out/
目录中的位置以供将来使用。
这个 jar 已经包含所有 类 和方法,它不会包含任何新内容
要获取您对框架所做的所有最新更改,您必须传递当前的 sdk 版本。如果你的 AOSP 是 Android 12,那么你必须这样声明你的测试
@Config(sdk = Build.VERSION_CODES.S)
当它获得最新版本时,它会使用 Sdk(Build.VERSION_CODES.S, "S", "r0", "S");
,搜索 android-all-s-robolectric-r0.jar
,复制并使用它。
与其他版本不同,此 jar 将从当前源代码树构建,因此新的更改将在 Robolectric 测试中看到。
我在 Android 9
中为系统服务添加了一个新方法有系统库,为开发者提供API,使用修改后的框架。库工作正常并成功访问新方法。
我想使用 Robolectric 测试库。
声明
#########################
# Robolectric test target
#########################
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
mockito-robolectric-prebuilt \
truth-prebuilt
LOCAL_JAVA_LIBRARIES := \
junit \
platform-robolectric-3.6.1-prebuilt
LOCAL_JAVA_LIBRARIES += com.mars.thelibrary
LOCAL_INSTRUMENTATION_FOR := TheService
LOCAL_MODULE := TheServiceRoboTests
LOCAL_MODULE_TAGS := optional
include $(BUILD_STATIC_JAVA_LIBRARY)
###########################
# Robolectric runner target
###########################
include $(CLEAR_VARS)
LOCAL_MODULE := RunTheServiceRoboTests
LOCAL_SDK_VERSION := current
LOCAL_STATIC_JAVA_LIBRARIES := TheServiceRoboTests
LOCAL_TEST_PACKAGE := TheService
LOCAL_JAVA_LIBRARIES += com.mars.thelibrary
include prebuilts/misc/common/robolectric/3.6.1/run_robotests.mk
测试
// TestConfig.java
public class TestConfig {
public static final int SDK_VERSION = 26;
public static final String MANIFEST_PATH = "TheAwesomeLibrary/tests/robotests/AndroidManifest.xml";
}
// PackageUtilitiesTest.java
@RunWith(RobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class PackageUtilitiesTest {
private Installer installer; // Modified System Service
private PackageUtilities utilities; // Library Class
@Before
public void setUp() throws InstallerException {
installer = mock(Installer.class);
utilities = new PackageUtilities(installer);
}
@Test
public void disableApp_shouldSuspendCache() throws InstallerException {
// Prepare
doNothing()
.when(installer)
.suspendCache(anyString(), anyInt());
// Do
utilities.disableApp("com.mars.betrayer", true);
// Check
verify(installer, times(1)).suspendCache(anyString(), anyInt());
}
}
在准备阶段失败并出现错误
java.lang.NoSuchMethodError: com.android.server.pm.Insteller.suspendCache(Ljava/lang/String;I)V
但我很确定该方法存在,因为该库在设备上可以正常工作。
在所有其他 Robolectric 测试中,库使用未修改的框架部分,因此所有其他测试都按预期工作并通过。
如何正确编写 robolectric 测试?如何使它们与修改后的框架一起工作?
tl;博士
两个原因:
- Robolectric 来自预制件
- SDK 版本不是最新的
目前,sdk版本应该和平台版本一致。例如。对于 Android 9:
@Config(sdk = Build.VERSION_CODES.P)
(仍然与 android-12.0 相关。0_r4)
但是
这行不通,您会看到一个新错误:
java.lang.UnsupportedOperationException: Robolectric does not support API level 28.
为避免这种情况,您必须使用 external/robolectric-shadows
中的 robolectric 而不是 prebuilts/misc/common/robolectric
变化
include prebuilts/misc/common/robolectric/3.6.1/run_robotests.mk
到
include external/robolectric-shadows/run_robotests.mk
并像这样更改依赖项(注意 LOCAL_JAVA_LIBRARIES
声明):
#########################
# Robolectric test target
#########################
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAVA_LIBRARIES := \
mockito-robolectric-prebuilt \
junit \
Robolectric_all-target \
LOCAL_INSTRUMENTATION_FOR := TheService
LOCAL_MODULE := TheServiceRoboTests
LOCAL_MODULE_TAGS := optional
include $(BUILD_STATIC_JAVA_LIBRARY)
###########################
# Robolectric runner target
###########################
include $(CLEAR_VARS)
LOCAL_MODULE := RunTheServiceRoboTests
LOCAL_SDK_VERSION := current
LOCAL_STATIC_JAVA_LIBRARIES := TheServiceRoboTests
LOCAL_TEST_PACKAGE := TheService
LOCAL_JAVA_LIBRARIES := \
TheServiceRoboTests \
guava \
Robolectric_all-target \
robolectric_android-all-stub \
mockito-robolectric-prebuilt \
include external/robolectric-shadows/run_robotests.mk
将来会有另一种方法,这将有助于不再指定每个版本的确切版本:
@Config(sdk = Build.VERSION_CODES.CUR_DEVELOPMENT)
见1819020: Makes a special entry for tree-built Robolectric
它是如何工作的
设置SDK版本时
@Config(sdk = 26)
然后 robolectric 将尝试在 robolectric: SdkConfig.java
new HashMap<Integer, SdkVersion>() {
{
addSdk(Build.VERSION_CODES.JELLY_BEAN, "4.1.2_r1", "r1", "REL");
addSdk(Build.VERSION_CODES.JELLY_BEAN_MR1, "4.2.2_r1.2", "r1", "REL");
addSdk(Build.VERSION_CODES.JELLY_BEAN_MR2, "4.3_r2", "r1", "REL");
addSdk(Build.VERSION_CODES.KITKAT, "4.4_r1", "r2", "REL");
addSdk(Build.VERSION_CODES.LOLLIPOP, "5.0.2_r3", "r0", "REL");
addSdk(Build.VERSION_CODES.LOLLIPOP_MR1, "5.1.1_r9", "r2", "REL");
addSdk(Build.VERSION_CODES.M, "6.0.1_r3", "r1", "REL");
addSdk(Build.VERSION_CODES.N, "7.0.0_r1", "r1", "REL");
addSdk(Build.VERSION_CODES.N_MR1, "7.1.0_r7", "r1", "REL");
addSdk(Build.VERSION_CODES.O, "8.0.0_r4", "r1", "REL");
addSdk(Build.VERSION_CODES.O_MR1, "8.1.0", "4611349", "REL");
addSdk(Build.VERSION_CODES.P, "9", "4913185-2", "REL");
addSdk(Build.VERSION_CODES.Q, "10", "5803371", "REL");
// BEGIN-INTERNAL
// TODO: Update jar with final R release.
addSdk(Build.VERSION_CODES.R, "R-beta2", "6625208", "REL");
addSdk(Build.VERSION_CODES.S, "S", "r0", "S");
// END-INTERNAL
}
在 AOSP 中,Robolectric 将框架加载为 .jar
文件。当 Runner 开始测试时,它会查找合适的受支持的 jar。
它将 28 与 Build.VERSION_CODES.O
匹配,采用 Sdk(Build.VERSION_CODES.O, "8.0.0_r4", "r1", "REL")
,并使用它来构建 jar
public DependencyJar getAndroidSdkDependency() {
return createDependency("org.robolectric", "android-all", getSdkVersion().getAndroidVersion() + "-robolectric-" + getSdkVersion().getRobolectricVersion(), null);
}
例如,对于 Android 8 (26; Build.VERSION_CODES.O) 它将尝试使用已发布的 android 版本的 android-all-8.0.0_r4-robolectric-r1.jar
。
因此,当 run_robotests.mk
启动测试时,here it copies all existing jar files from prebuilts/misc/common/robolectric/android-all 并将其添加到 out/
目录中的位置以供将来使用。
这个 jar 已经包含所有 类 和方法,它不会包含任何新内容
要获取您对框架所做的所有最新更改,您必须传递当前的 sdk 版本。如果你的 AOSP 是 Android 12,那么你必须这样声明你的测试
@Config(sdk = Build.VERSION_CODES.S)
当它获得最新版本时,它会使用 Sdk(Build.VERSION_CODES.S, "S", "r0", "S");
,搜索 android-all-s-robolectric-r0.jar
,复制并使用它。
与其他版本不同,此 jar 将从当前源代码树构建,因此新的更改将在 Robolectric 测试中看到。