11

我想运行具有不同语言环境的参数化仪器测试,以使用所有支持的语言运行相同的测试。

观察到的行为是该活动将具有第一次测试运行的本地化标题,也适用于随后的每次运行。因此,无论我的手机使用哪种语言,标题都会在第一次参数化测试运行时正确本地化,并且对于接下来的每个测试运行仍然相同。

虽然覆盖语言环境本身适用于任何资源,如果由.AndroidManifest.xml

活动似乎在 中设置了它们的标题attach,并且任何调用 attach 似乎都将标题缓存在应用程序首次启动的语言环境中。

final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
   ---> CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
    attachBaseContext(context);

由于资源总是得到正确本地化,解决方法是调用setTitle(R.string.title)或只是getActionBar().setTitle(R.string.setTitle),但我不想仅仅为了测试目的而更改活动。

问题:如何在第一次测试运行后更改启动活动的标题?如上所述,这似乎被缓存并且没有正确更新,并且杀死应用程序以重新启动它会使检测测试失败。

测试设置

整个测试项目可以在 GitHub 上找到Localization.java包含当前失败的单元测试以及此处描述的问题),并且将参数化单元测试与UIAutomator.

目标是在不太了解应用程序本身(UIAutomator)的情况下拍摄一批屏幕截图,并且也不必为测试修改应用程序。

更改语言环境:

我在每次测试之前成功地更改了语言环境,并且通过执行以下操作正确显示了我的文本,而且我有多个断言确保资源实际上是正确的语言环境。

public LocalizationTest(Locale locale) {
    mLocale = locale;
    Configuration config = new Configuration();
    Locale.setDefault(mLocale);
    config.setLocale(mLocale);

    Resources resources = InstrumentationRegistry.getTargetContext().getResources();
    resources.updateConfiguration(config, resources.getDisplayMetrics());

    resources.flushLayoutCache();
}

什么不起作用:

我显然尝试在目标上下文、应用程序上下文和活动上以相同的方式设置语言环境(无论如何这可能为时已晚)。

我看到它attach是从 调用的Instrumentation,但只是创建一个新应用程序并尝试启动该活动也不会本地化标题。

Intent intent = context.getPackageManager().getLaunchIntentForPackage(BuildConfig.APPLICATION_ID);
context = InstrumentationRegistry.getInstrumentation().newApplication(App.class,
                InstrumentationRegistry.getTargetContext());
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
4

2 回答 2

2

标题字符串缓存在包管理器ApplicationPackageManager中的静态sStringCache.

虽然有一种static void configurationChanged()清除缓存的方法,但它似乎不会在手动更改时被调用。因此,第一次调用后错误本地化的活动标题的描述问题。

使用反射来加载类并自己调用方法可以解决这个问题。这有点脏,因为它正在访问私有方法,但它可以工作。

// as before
Configuration config = new Configuration();
Locale.setDefault(mLocale);
config.setLocale(mLocale);

Resources resources = context.getResources();
resources.updateConfiguration(config, resources.getDisplayMetrics());

// CLEAR the cache!
Method method = getClass().getClassLoader()
        .loadClass("android.app.ApplicationPackageManager")
        .getDeclaredMethod("configurationChanged");
method.setAccessible(true);
method.invoke(null);

或者,您可以在另一个非公共 API 上使用公共方法,该 API 反过来也会调用上述方法。仍然很脏但没有调用私有方法。

看起来你可以resources.updateConfiguration(...);通过使用这个方法来省略,因为它也会处理这个问题。

// Clear the cache. 
Object thread = getClass().getClassLoader()
        .loadClass("android.app.ActivityThread")
        .getMethod("currentActivityThread")
        .invoke(null);
Method method = getClass().getClassLoader()
        .loadClass("android.app.ActivityThread")
        .getMethod("applyConfigurationToResources", Configuration.class);
method.invoke(thread, config);
于 2016-01-31T16:52:15.113 回答
1

我们发现活动标题设置onAttach()为活动管理器提供的标题。因此,我认为您需要更改系统区域设置。

为此,测试可以使用反射ActivityManagerNative更新配置:

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public Localization(Locale locale) throws ClassNotFoundException, NoSuchMethodException,
        InvocationTargetException, IllegalAccessException {
    Context context = InstrumentationRegistry.getTargetContext();
    log(context.toString());
    log(context.getApplicationContext().toString());

    mLocale = locale;

    Class<?> amClass = Class.forName("android.app.ActivityManagerNative");
    Method getDefaultMethod = amClass.getDeclaredMethod("getDefault");
    Object iActivityManager = getDefaultMethod.invoke(null /* static method */);
    Method updateConfigurationMethod =
            amClass.getMethod("updateConfiguration", Configuration.class);
    Configuration configuration = new Configuration(context
            .getResources().getConfiguration());
    configuration.locale = locale;
    updateConfigurationMethod.invoke(iActivityManager, configuration);
}

为此,请授予您的应用权限(此权限使用调试密钥签名,仅将其添加到 AndroidManifest 中是不够的)

adb shell pm grant at.bleeding182.testing.instrumentationtest android.permission.CHANGE_CONFIGURATION

我已经测试了这个解决方案,并且可以确认区域设置现在已正确更改,并且测试通过了 -makeScreenshot非常不稳定,但那是另一天。

您将需要更改软件工厂的工作流程:

  1. 更新应用 apk 并测试 apk
  2. 将 android.permission.CHANGE_CONFIGURATION 赋予测试 apk
  3. 运行测试
于 2016-01-31T15:33:11.687 回答