我正在使用谷歌推荐的Android测试框架:ActivityInstrumentationTestCase2。在随机测试运行过程中我遇到了以下错误,并且持续性致命。这意味着有时所有测试都通过了(开心!),但很多时候它会随机失败,或出现以下三个错误之一。这非常令人沮丧,让我对测试结果没有信心。
为了详细描述这些问题,我提供了我的测试类简化伪代码和以下三个问题。这两个测试用例是相互独立的。
现在,我一遍又一遍地运行了这两个测试用例(它们将按照字母顺序运行),以下是结果:
到目前为止,我已经处理了这些问题几天时间,研究了互联网上的许多建议,并进行了尝试和错误。没有一个可以立即解决特定问题,但是 - 通过结合我找到的内容,我解决了上述问题(1)和(2),但仍存在问题(3)未解决。以下是详细信息,我如何解决此问题。
问题(1)ActivityMonitor.waitForActivityWithTimeout()返回NULL
1.1. 我现在知道我必须在getActivit()之前声明getInstrumentation().addMonitor()。请参见我如何更改setUp()方法,这样解决了问题。任何理解为什么需要此要求的人,请告诉我们,我们将不胜感激。
1.2. 在模拟器上,此调用偶尔可能返回NULL,导致测试失败。我了解到这是因为等待时间太短。因此,增加等待时间有助于防止ActivityMonitor过早返回。
问题(2)下一个testB_SadPath()会在setUp() > getActivity()无限期挂起
2.1. 正如我上面所描述的那样,当前一个测试(testA_HappyPath)失败时,就会发生这种情况。我认为我的tearDown()清除了一切,并准备好运行下一个测试。发生了什么是,testA正在等待SecondActivity显示在屏幕上,但由于ActivityMonitor.waitForActivityWithTimeout()返回NULL,testA失败了。 tearDown()被执行得很好。问题在于SecondActivity确实出现在屏幕上,但它从未在finally块中关闭,因为它的方法实例“secondActivity”仍为空。保持SecondActivity处于活动状态并停留在屏幕上导致下一个getActivity()挂起。我通过更改最终块来确保如果SecondActivity存在,则关闭它来解决此问题。
这些更改在以下代码中总结(请参见setUp()和finally块)。
为了详细描述这些问题,我提供了我的测试类简化伪代码和以下三个问题。这两个测试用例是相互独立的。
public class FirstActivityTest
extends ActivityInstrumentationTestCase2<FirstActivity> {
private FirstActivity mActivity;
private ActivityMonitor mActivityMonitor;
public FirstActivityTest () {
super(FirstActivity.class);
}
public void setUp() throws Exception {
super.setUp();
setActivityInitialTouchMode(false);
mActivity = getActivity();
assertNotNull("Cannot start test since target Activity is NULL!", mActivity);
mActivityMonitor = getInstrumentation().addMonitor(SecondActivity.class.getName(), null, false);
}
public void tearDown() throws Exception {
super.tearDown();
if(mActivity != null) {
mActivity.finish();
mActivity = null;
}
if(mActivityMonitor != null) {
getInstrumentation().removeMonitor(mActivityMonitor);
mActivityMonitor = null;
}
}
/**
* Open FirstActivity, enter a text and click submit button.
* Verifies SecondActivity is open.
*/
public void testA_HappyPath() {
Activity secondActivity = null;
try {
//(Omitted) Get edit text and enter a valid value
//(Omitted) Find submitButton view
//Click submit button
TouchUtils.clickView(this, submitButton);
//Wait for result and validate:
secondActivity = mActivityMonitor.waitForActivityWithTimeout(10000);
assertNotNull("Result SecondActivity should NOT be null!", secondActivity );
} finally {
//Clean up:
if(secondActivity != null) {
secondActivity .finish();
secondActivity = null;
}
}
}
/**
* Open FirstActivity, do NOT enter a text and click submit button.
* Verifies error message is returned.
*/
public void testB_SadPath() {
//(Omitted) Find submitButton view
//Click submit button
TouchUtils.clickView(this, submitButton);
//(Omitted) Validate error message is displayed
}
}
现在,我一遍又一遍地运行了这两个测试用例(它们将按照字母顺序运行),以下是结果:
- 两个测试用例都通过了,或者
- testA_HappyPath()失败,因为ActivityMonitor.waitForActivityWithTimeout()返回NULL SecondActivity。但是当我查看我的设备时,SecondActivity正确显示。不知何故,测试未能注意到它。为什么?
- 当testA_HappyPath()失败时,然后下一个testB_SadPath()会在setUp() > getActivity()无限期地挂起。我认为我在tearDown()中关闭了所有东西。为什么?
- testB_SadPath()经常在TouchUtils.clickView()上失败,并出现以下错误:“注入到另一个应用程序需要INJECT_EVENTS权限”(无论testA_HappyPath是否通过或失败)。为什么?
到目前为止,我已经处理了这些问题几天时间,研究了互联网上的许多建议,并进行了尝试和错误。没有一个可以立即解决特定问题,但是 - 通过结合我找到的内容,我解决了上述问题(1)和(2),但仍存在问题(3)未解决。以下是详细信息,我如何解决此问题。
问题(1)ActivityMonitor.waitForActivityWithTimeout()返回NULL
1.1. 我现在知道我必须在getActivit()之前声明getInstrumentation().addMonitor()。请参见我如何更改setUp()方法,这样解决了问题。任何理解为什么需要此要求的人,请告诉我们,我们将不胜感激。
1.2. 在模拟器上,此调用偶尔可能返回NULL,导致测试失败。我了解到这是因为等待时间太短。因此,增加等待时间有助于防止ActivityMonitor过早返回。
问题(2)下一个testB_SadPath()会在setUp() > getActivity()无限期挂起
2.1. 正如我上面所描述的那样,当前一个测试(testA_HappyPath)失败时,就会发生这种情况。我认为我的tearDown()清除了一切,并准备好运行下一个测试。发生了什么是,testA正在等待SecondActivity显示在屏幕上,但由于ActivityMonitor.waitForActivityWithTimeout()返回NULL,testA失败了。 tearDown()被执行得很好。问题在于SecondActivity确实出现在屏幕上,但它从未在finally块中关闭,因为它的方法实例“secondActivity”仍为空。保持SecondActivity处于活动状态并停留在屏幕上导致下一个getActivity()挂起。我通过更改最终块来确保如果SecondActivity存在,则关闭它来解决此问题。
这些更改在以下代码中总结(请参见setUp()和finally块)。
public class FirstActivityTest
extends ActivityInstrumentationTestCase2<FirstActivity> {
private FirstActivity mActivity;
private ActivityMonitor mActivityMonitor;
public FirstActivityTest () {
super(FirstActivity.class);
}
public void setUp() throws Exception {
super.setUp();
setActivityInitialTouchMode(false);
}
public void tearDown() throws Exception {
super.tearDown();
if(mActivity != null) {
mActivity.finish();
mActivity = null;
}
if(mActivityMonitor != null) {
getInstrumentation().removeMonitor(mActivityMonitor);
mActivityMonitor = null;
}
}
/**
* Open FirstActivity, enter a text and click submit button.
* Verifies SecondActivity is open.
*/
public void testA_HappyPath() {
mActivityMonitor = getInstrumentation().addMonitor(SecondActivity.class.getName(), null, false);
mActivity = getActivity();
assertNotNull("Cannot start test since target Activity is NULL!", mActivity);
Activity secondActivity = null;
try {
//(Omitted) Get edit text and enter a valid value
//(Omitted) Find submitButton view
//Click submit button
TouchUtils.clickView(this, submitButton);
//Wait for result and validate:
secondActivity = mActivityMonitor.waitForActivityWithTimeout(20000);
assertNotNull("Result SecondActivity should NOT be null!", secondActivity );
} finally {
//Clean up:
if(secondActivity == null) {
//If empty, wait longer because need to shut down the foreground activity, if any:
secondActivity = mActivityMonitor.waitForActivityWithTimeout(20000);
}
if(secondActivity != null) {
secondActivity .finish();
secondActivity = null;
}
}
}
/**
* Open FirstActivity, do NOT enter a text and click submit button.
* Verifies error message is returned.
*/
public void testB_SadPath() {
mActivity = getActivity();
assertNotNull("Cannot start test since target Activity is NULL!", mActivity);
//(Omitted) Find submitButton view
//Click submit button
TouchUtils.clickView(this, submitButton);
//(Omitted) Validate error message is displayed
}
}
问题(3):testB_SadPath() 经常在 TouchUtils.clickView() 中失败,出现以下错误:"Injecting to another application requires INJECT_EVENTS permission"。
我仍然无法解决这个最后的问题 :-(