如何在 Android Robolectric 测试中强制更改配置?

发布于 2024-11-08 05:25:47 字数 302 浏览 0 评论 0原文

我正在使用 robolectric 来使我的 Android 单元测试足够快而有用。我想测试我编写的代码在屏幕方向发生变化以模拟常见的现实世界用例时是否有效。

具体来说,我正在测试的是对服务器的异步 http 调用,并在获取结果后解析一些 xml。我对所有工作都进行了单元测试,但无法弄清楚如何模拟屏幕旋转。任何导致 Activity 重新创建自身的状态更改都可以,不一定是屏幕旋转。

使用模拟器的解决方案不是一个选项,因为我每分钟运行几次测试,并且它们必须在 2 秒内运行。如果可能的话,我也希望能与 roboguice 一起使用。

谢谢。

I'm using robolectric to make my android unit tests fast enough to be useful. I want to test that code I've written works while the screen orientation is changing to simulate a common real world use case.

Specifically what I'm testing is an asynchronous http call to a server with some xml parsed after the result is fetched. I have the unit test for all that working great but can't figure out how to simulate the screen rotation. Any state change that causes the activity to recreate itself is fine, it doesn't necessarily have to be screen rotation.

A solution that uses the emulator is not an option as I run my tests several times per minute and they must run under 2 seconds. I would also like this to work with roboguice if possible.

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

东京女 2024-11-15 05:25:47

在 Robolectric 中调用 recreate(请注意,使用 Robolectric 时您不必担心较旧的 API 版本)非常接近模拟配置更改,但不一定会捕获您可能犯的所有错误。特别是它不会创建 Activity 的新实例(而且我很确定它不会“擦除”它),因此如果您忘记恢复 Activity 的成员字段,您的测试将无法捕获它。不过,它对于测试片段来说确实足够好(非保留片段被销毁并重新实例化)。

如果您在 Robolectric 测试中的 Activity 上调用 recreate,则会发生以下情况:

  1. onSaveInstanceState
  2. onPause
  3. onStop
  4. onDestroy
  5. onCreate
  6. onStart
  7. onRestoreInstanceState
  8. onResume

(我通过覆盖测试活动中的大多数生命周期方法并将日志语句放入其中发现了这一点)

您可以更接近一点使用如下代码进行实际配置更改:(

Bundle bundle = new Bundle();
activityController.saveInstanceState(bundle).pause().stop().destroy();
controller = Robolectric.buildActivity(YourActivity.class).create(bundle).start().restoreInstanceState(bundle).resume();
activity = controller.get();

此代码适用于 Robolectric 2.1 - 如果您使用的是 2.2 或更高版本,则可能需要在 之后调用 .visible() 。 resume())

使用上面的内容,您将看到以下事件发生:

  1. onSaveInstanceState
  2. onPause
  3. onStop
  4. onDestroy
  5. Activity 实例化的新实例(所有后续调用均在此新实例上)
  6. onCreate
  7. onStart
  8. onRestoreInstanceState
  9. onResume
  10. onPostResume

这仍然不是准确的匹配,但更接近于遇到实际配置更改时发生的情况。

我认为这可能是对由于内存不足而破坏活动时发生的情况的一个不错的模拟,因为与调用 recreate() 不同,我认为这不会保留对保留片段的引用。但我在这个领域的根基不稳固!

更新:

如果您的 Activity 是通过意图启动的,您可能需要添加对 withIntent 的调用,如下所示:

Robolectric.buildActivity(YourActivity.class).withIntent(intent).create(bundle) // and so on...

Calling recreate in Robolectric (note that you don't have to worry about older API versions when using Robolectric) is pretty close to simulating a configuration change, but won't necessarily catch all errors you could make. In particular it does not create a new instance of the Activity (and I'm pretty sure it does not 'scrub' it), so if you have forgotten to restore member fields of your Activity your tests won't catch that. It does work well enough for testing fragments though (non retained fragments are destroyed and re-instantiated).

If you call recreate on an Activity in a Robolectric test the following happens:

  1. onSaveInstanceState
  2. onPause
  3. onStop
  4. onDestroy
  5. onCreate
  6. onStart
  7. onRestoreInstanceState
  8. onResume

(I found this out by overriding most lifecycle methods in a test activity and putting logging statements in them)

You can get a little closer to a real configuration change with code like the following:

Bundle bundle = new Bundle();
activityController.saveInstanceState(bundle).pause().stop().destroy();
controller = Robolectric.buildActivity(YourActivity.class).create(bundle).start().restoreInstanceState(bundle).resume();
activity = controller.get();

(This code is for Robolectric 2.1 - if you are on 2.2 or up, you will possibly want a .visible() call after that .resume())

Using the above you will see the following events occur:

  1. onSaveInstanceState
  2. onPause
  3. onStop
  4. onDestroy
  5. new instance of Activity instantiated (all following calls are on this new instance)
  6. onCreate
  7. onStart
  8. onRestoreInstanceState
  9. onResume
  10. onPostResume

This still isn't an exact match but is much closer to what will happen when a real configuration change is encountered.

I think this might be a decent simulation of what happens when an activity is destroyed due to low memory, as unlike calling recreate() I don't think this will keep hold of references to retained fragments. I'm on shaky ground in this area though!

Update:

If your Activity was started via an intent, you might need to add in a call to withIntent, like so:

Robolectric.buildActivity(YourActivity.class).withIntent(intent).create(bundle) // and so on...
她如夕阳 2024-11-15 05:25:47

您正在针对哪个 Android API 级别进行编译?如果是 3.0 或更高版本,您可以尝试 Activity。重新创建()。文档指出:

使用新实例重新创建此 Activity。这会导致与由于配置更改而创建 Activity 时的流程本质上相同的流程 - 当前实例将经历其生命周期到 onDestroy(),然后在其之后创建一个新实例。

不过我自己还没有尝试过。

What Android API level are you compiling against? If it's 3.0 or above you could try Activity.recreate(). The documentation states:

Cause this Activity to be recreated with a new instance. This results in essentially the same flow as when the Activity is created due to a configuration change -- the current instance will go through its lifecycle to onDestroy() and a new instance then created after it.

Haven't tried it myself though.

与他有关 2024-11-15 05:25:47

我已经成功使用 ZoFreX 的答案,但是我想添加如何实际模拟旋转。我知道OP指定旋转不是绝对必须的,但标题暗示这应该包含在答案中,并且可以帮助最终迷失在这里的人。

基本上,在应用 ZoFrex 的解决方案之前设置活动的方向。或者用代码更简洁:

// toggle orientation
int currentOrientation = fragment.getActivity().getResources().getConfiguration().orientation;
boolean isPortraitOrUndefined = currentOrientation == Configuration.ORIENTATION_PORTRAIT || currentOrientation == Configuration.ORIENTATION_UNDEFINED;
int toOrientation = isPortraitOrUndefined ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT;
Robolectric.application.getResources().getConfiguration().orientation = toOrientation;

// ZoFreX's solution
Bundle bundle = new Bundle();
activityController.saveInstanceState(bundle).pause().stop().destroy();
controller = Robolectric.buildActivity(YourActivity.class).create(bundle).start().restoreInstanceState(bundle).resume();
activity = controller.get();

请查看 ZoFreX 的解决方案,因为它包含此处未包含的其他信息。

I've had success using ZoFreX's answer, however I'd like to add how to actually simulate the rotation. I know the OP specified that rotation is not an absolute must, but the title hints that this should be included in the answer, and could help people who end up straying here.

basically, set the orientation of the activity before applying ZoFrex's solution. Or more concisely in code:

// toggle orientation
int currentOrientation = fragment.getActivity().getResources().getConfiguration().orientation;
boolean isPortraitOrUndefined = currentOrientation == Configuration.ORIENTATION_PORTRAIT || currentOrientation == Configuration.ORIENTATION_UNDEFINED;
int toOrientation = isPortraitOrUndefined ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT;
Robolectric.application.getResources().getConfiguration().orientation = toOrientation;

// ZoFreX's solution
Bundle bundle = new Bundle();
activityController.saveInstanceState(bundle).pause().stop().destroy();
controller = Robolectric.buildActivity(YourActivity.class).create(bundle).start().restoreInstanceState(bundle).resume();
activity = controller.get();

Please checkout ZoFreX's solution because it contains additional info not included here.

乄_柒ぐ汐 2024-11-15 05:25:47

Robolectric 的 ActivityController 类有一个configurationChange() 方法,可能可以处理这个问题。天哪,它甚至还有 javadoc 注释! :D

The ActivityController class of Robolectric has a configurationChange() method that probably handles this. Hell, it even has a javadoc comment! :D

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文