簡體   English   中英

活動生命周期單元測試

[英]Activity lifecycle unit testing

在活動單元測試中,我如何模擬活動生命周期事件。

我可以在現有活動上調用instrumentation的callActivityOn ...方法,但是如何觸發活動重新創建,以便活動的OnCreate獲取具有已保存狀態的bundle

我發現此代碼會導致創建新的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);
    }
});

不要按照狀態管理測試示例 :{dead link}

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

ActivityInstrumentationTestCase2.getActivity()在您第一次調用它時啟動Activity,但它只是在測試用例的每個后續調用中返回相同的Activity 因此,您仍然在查看已完成的活動。

完成第一個Activity后,您需要從測試中開始一個新的Activity。 例如,您可以使用InstrumentationTestCase.launchActivity()

作為另一個例子,我編寫了一個測試,在ActivityA中按下一個按鈕,啟動ActivityB for-result; 測試然后立即殺死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()調用為活動提供了大量響應時間)

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。

我確認cdhabecker是正確的,getActivity()返回在開始時創建的活動,即使你“完成”它。 但我想我已經找到了一種測試活動娛樂的解決方案。 您可以嘗試請求更改方向。 這將重新創建您的活動,然后您檢索新創建的。 下面的代碼片段:(我用過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);
}   

當然,如果您在方向更改后阻止您的活動被銷毀,它將無法工作。 在這里,您可以找到包含日志消息的完整答案。 希望有所幫助。 問候!

在闡述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類中。

如果你有Android 4.x設備,你可以進入設置>開發者選項並檢查'不要保持活動'。 現在,只要你的Activity失去焦點(例如:HOME按鈕),它就會被殺死並且將調用onSaveInstanceState(...)。

當您恢復應用程序時,如果您將其保存在onSaveInstanceState(...)中,則您的活動應該在onCreate(...)方法中包含數據包數據。

官方開發指南中有一個非常好的例子,在這里討論狀態管理測試。 基本上你只需要調用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());
}

希望這可以幫助。

您可以通過ActivityLifeCycleMonitor獲取新的恢復ActivityLifeCycleMonitor

例如,此方法等待並將新創建的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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM