【问题标题】:Activity lifecycle unit testing活动生命周期单元测试
【发布时间】:2012-02-22 18:27:57
【问题描述】:

在活动单元测试中,我如何模拟活动生命周期事件。

我可以在现有活动上调用仪器的 callActivityOn... 方法,但是如何触发活动重新创建,以便活动的 OnCreate 获取具有已保存状态的包

【问题讨论】:

    标签: android unit-testing


    【解决方案1】:

    我发现这段代码会导致创建新的 Activity:

    myActivity.finish();
    setActivity(null);
    myActivity = getActivity();
    

    但这不会导致 onSaveInstanceState 被调用。因此,例如测试是否在视图方向更改后正确创建了活动,这样的测试应该这样做:

    private mInstrumentation = getInstrumentation();
    ...
    final Bundle outState = new Bundle();
    mInstrumentation.callActivityOnSaveInstanceState(mActivity, outState);
    mActivity.finish();
    setActivity(null);
    mActivity = getActivity();
    runTestOnUiThread(new Thread() {
        @Override
        public void run() {
            mInstrumentation.callActivityOnRestoreInstanceState(mActivity, outState);
        }
    });
    

    【讨论】:

      【解决方案2】:

      不要关注state management test example:{死链接}

      myActivity.finish();
      myActivity = getActivity();
      

      ActivityInstrumentationTestCase2.getActivity() 在您第一次调用 Activity 时启动它,但随后它会在测试用例的每个后续调用中简单地返回 相同的 Activity。因此,您仍在查看已完成的 Activity。

      完成第一个 Activity 后,您需要从测试中开始一个新的。例如,您可以使用InstrumentationTestCase.launchActivity()

      作为另一个示例,我编写了一个测试,该测试在 ActivityA 中按下一个按钮,该按钮启动 ActivityB 以获得结果;然后测试立即终止 ActivityA(通过方向更改,但 finish() 也可以工作),然后测试获取系统在 ActivityB 完成时创建的新 ActivityA 的句柄并发送其结果。诀窍是让测试添加 Instrumentation.ActivityMonitor,然后让该监视器等待系统启动新的 ActivityA 并为测试提供句柄。

      编辑 2/23/2012 cdhabecker,添加可重现的代码:

      public class VerboseActivity extends Activity {
          public final static String TAG = "Verbose";
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              Log.i(TAG, "onCreate() " + (Activity)this);
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity5);
          }
          @Override
          protected void onDestroy() {
              Log.i(TAG, "onDestroy().");
              super.onDestroy();
          }
      }
      

      测试用例:(sleep() 调用给 Activity 大量时间响应)

      public class VerboseTest extends
              ActivityInstrumentationTestCase2<VerboseActivity> {
      
          Activity myActivity = null;
      
          public VerboseTest() {
              super("com.scanillion.demo", VerboseActivity.class);
          }
      
          public void test_01() {
              String TAG = "test_01";
              myActivity = getActivity();
              Log.i(TAG, "A getActivity()=" + myActivity);
              myActivity.finish();
              try {
                  Thread.sleep(5000L);
              } catch (InterruptedException e) {
              }
              myActivity = getActivity();
              Log.i(TAG, "B getActivity()=" + myActivity);
              try {
                  Thread.sleep(5000L);
              } catch (InterruptedException e) {
              }
          }
      }
      

      日志:

      02-23 21:25:37.689: I/Verbose(17747): onCreate() com.scanillion.demo.VerboseActivity@43ba3360
      02-23 21:25:38.159: I/ActivityManager(67): Displayed activity com.scanillion.demo/.VerboseActivity: 526 ms (total 526 ms)
      02-23 21:25:38.180: I/test_01(17747): A getActivity()=com.scanillion.demo.VerboseActivity@43ba3360
      02-23 21:25:38.540: I/Verbose(17747): onDestroy().
      02-23 21:25:43.236: I/test_01(17747): B getActivity()=com.scanillion.demo.VerboseActivity@43ba3360
      02-23 21:25:48.439: I/TestRunner(17747): finished: test_01(com.scanillion.demo.test.VerboseTest)
      02-23 21:25:48.439: I/TestRunner(17747): passed: test_01(com.scanillion.demo.test.VerboseTest)
      

      请注意,finish() 导致了onDestroy(),但随后的getActivity() 是无操作的。 getActivity() 不仅不会实例化一个新的 Activity,它甚至不会重新创建原来的 Activity。

      【讨论】:

      • 您能否提供可靠的参考来支持您的陈述“它只是在测试用例的每个后续调用中返回相同的活动”?顺便说一句,我认为这里的关键点是通过调用 Activity.finish() 这将完全破坏用户输入状态的活动,我的答案中的测试代码用于检查 Saving activity state 是否在 Activity.onCreate() 中正确实现,如果返回相同的实例(我假设基于您的陈述,尽管具有不同的状态)并不重要。
      • @yorkw 我添加了一个可重现的案例。
      • 我仍然不明白为什么Thread.sleep(5000L) 导致 onStop() > onDestroy() 在finish() 之后被调用。 sleep() 技巧对我很有效,因为我希望调用 onStop() 和 onDestroy()。
      【解决方案3】:

      我确认cdhabecker 是正确的,getActivity() 会返回开始时创建的活动,即使您“完成”它也是如此。但我想我已经找到了一个测试活动娱乐的解决方案。您可以尝试请求更改方向。这将重新创建您的活动,然后您检索新创建的活动。下面的代码sn-p:(我用robotium):

      protected void setUp() throws Exception {
        super.setUp();
        mActivity = getActivity();
        mSolo = new Solo(getInstrumentation(), getActivity());
        Log.v(TAG, "setUp; activity=" + mActivity);
      }
      
      public void testOrienationChange(){     
        mSolo.setActivityOrientation(Solo.LANDSCAPE);
        getInstrumentation().waitForIdleSync();
        MyActivity newActivity = getActivity(); //should be new, but it's not
        Activity newActivity2 = mSolo.getCurrentActivity(); //this will return newly created
        Log.v(TAG, "testOrienationChange; activity=" + newActivity);
        Log.v(TAG, "testOrienationChange; activity2=" + newActivity2);
      }   
      

      如果你阻止你的活动在方向改变后被破坏,当然它不会起作用。 Here 您可以找到我的完整答案,其中包含日志消息。希望有帮助。问候!

      【讨论】:

        【解决方案4】:

        如果您有 Android 4.x 设备,您可以进入设置 > 开发人员选项并选中“不保留活动”。现在,当您的 Activity 失去焦点时(例如:HOME 按钮),它将被杀死并调用 onSaveInstanceState(...)。

        当您恢复应用时,如果您将 Activity 保存在 onSaveInstanceState(...) 中,则您的 Activity 应该在 onCreate(...) 方法中包含捆绑数据。

        【讨论】:

        • 这可能适用于手动测试。我正在寻找一种在自动化测试中做到这一点的方法
        【解决方案5】:

        官方开发指南中有一个很好的例子,谈论状态管理测试here。基本上你只需要调用 Activity.finish() 来模拟活动已被杀死,查看下面的伪代码:

        public void testIfStateIsSaved() {
          // Open myActivity first time.
          MyActivity myActivity = getActivity();
          final EditText editText = (EditText) myActivity.findViewById(com.company.R.id.edit_text);
          // emulate some user action
          myActivity.runOnUiThread(new Runnable() {
            public void run() {
              editText.setText("save me");
            }
          });
        
          // Suppose you have implemented saved state properly.
        
          // kill activity and restart it again.
          myActivity.finish();
          myActivity = getActivity();
          final EditText editText2 = (EditText) myActivity.findViewById(com.company.R.id.edit_text);
          assertEquals("user input must be saved", "save me", editText2.getText());
        }
        

        希望这会有所帮助。

        【讨论】:

        • 我不认为这个答案有效——你会发现 getActivity() 根本没有开始一个新的活动——它和你以前的一样。请参阅 stackoverflow.com/a/9422946/424529 了解不同的方法。
        • 我一开始以为这就是答案,结果我错了——见@cdhabecker的评论
        【解决方案6】:

        详细说明 cdhabecker 的回答,我创建了以下适用于我的静态方法:

        public static Activity restartActivity(Activity activity, Instrumentation instrumentation, Intent intent){
            String className = activity.getClass().getName();
            Instrumentation.ActivityMonitor monitor = instrumentation.addMonitor(className, null, false);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.setClassName(instrumentation.getTargetContext(), className );
            instrumentation.startActivitySync(intent);
            Activity newActivity = instrumentation.waitForMonitor(monitor);
            instrumentation.removeMonitor(monitor);
            return newActivity;
        }
        

        使用活动后,我将其销毁并通过调用重置

        activity.finish();
        setActivity(null);
        

        在我的 ActivityInstrumentationTestCase2 类中。

        【讨论】:

          【解决方案7】:

          您可以通过ActivityLifeCycleMonitor获取新的恢复Activity

          例如,这个方法等待并将新创建的Activity设置为当前Activity。

          public void waitAndSetResumedActivity() {
              // well at least there are some activities in the pipeline - lets see if they resume.
          
              long[] waitTimes =
                      {10, 50, 100, 500, TimeUnit.SECONDS.toMillis(2), TimeUnit.SECONDS.toMillis(30)};
          
              final ActivityLifecycleMonitor activityLifecycleMonitor = ActivityLifecycleMonitorRegistry.getInstance();
              final AtomicBoolean activityResumed = new AtomicBoolean(false);
              for (int waitIdx = 0; waitIdx < waitTimes.length; waitIdx++) {
                  if (activityResumed.get()) return;
                  try {
                      Thread.sleep(waitTimes[waitIdx]);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  getInstrumentation().runOnMainSync(new Runnable() {
                      @Override
                      public void run() {
                          Collection<Activity> resumedActivities = activityLifecycleMonitor.getActivitiesInStage(Stage.RESUMED);
                          if (!resumedActivities.isEmpty()) {
                              activity = (MainActivity) resumedActivities.iterator().next();
                              setActivity(activity);
                              activityResumed.set(true);
                          }
                      }
                  });
          
              }
              throw new NoActivityResumedException("No activities in stage RESUMED. Did you forget to "
                      + "launch the activity. (test.getActivity() or similar)?");
          
          }
          

          所以调用此方法后,任何对getActivity() 的调用都会返回新的Activity。

          您可以像这样在轮换中测试 Activity 娱乐:

          Activity activity = getActivity(); // old activity    
          //rotate it
          activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
          //set new Activity
          waitAndSetResumedActivity();
          activity = getActivity();  // New Activity
          

          【讨论】:

            猜你喜欢
            • 2017-02-21
            • 2015-04-29
            • 2020-09-06
            • 2014-05-18
            • 1970-01-01
            • 2015-01-03
            • 2013-01-13
            • 2011-12-18
            • 1970-01-01
            相关资源
            最近更新 更多