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;博士

两个原因:

  1. Robolectric 来自预制件
  2. 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 测试中看到。