[英]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.