我正在为我的Android应用程序编写一个Instrumentation Test。
我遇到了一些奇怪的线程问题,我似乎无法找到解决方案。
我的原始测试:
@RunWith(AndroidJUnit4.class)
public class WorkOrderDetailsTest {
@Rule
public ActivityTestRule<WorkOrderDetails> activityRule = new ActivityTestRule<>(WorkOrderDetails.class);
@Test
public void loadWorkOrder_displaysCorrectly() throws Exception {
final WorkOrderDetails activity = activityRule.getActivity();
WorkOrder workOrder = new WorkOrder();
activity.updateDetails(workOrder);
//Verify customer info is displayed
onView(withId(R.id.customer_name))
.check(matches(withText("John Smith")));
}
}
这导致
android.view.ViewRootImpl $ CalledFromWrongThreadException:只有创建视图层次结构的原始线程才能触及其视图。
...
com.kwtree.kwtree.workorder.WorkOrderDetails.updateDetails(WorkOrderDetails.java:155)
updateDetails()
方法唯一能做的就是setText()
次调用。
经过研究后,似乎在我的测试中添加UiThreadTestRule
和android.support.test.annotation.UiThreadTest
注释可以解决问题。
@UiThreadTest:
@RunWith(AndroidJUnit4.class)
public class WorkOrderDetailsTest {
//Note: This is new
@Rule
public UiThreadTestRule uiThreadTestRule = new UiThreadTestRule();
@Rule
public ActivityTestRule<WorkOrderDetails> activityRule = new ActivityTestRule<>(WorkOrderDetails.class);
@Test
@UiThreadTest //Note: This is new
public void loadWorkOrder_displaysCorrectly() throws Exception {
final WorkOrderDetails activity = activityRule.getActivity();
WorkOrder workOrder = new WorkOrder();
activity.updateDetails(workOrder);
//Verify customer info is displayed
onView(withId(R.id.customer_name))
.check(matches(withText("John Smith")));
}
}
java.lang.IllegalStateException:无法在主应用程序线程上调用方法(on:main)
(注意:此堆栈跟踪中的所有方法都不是我的代码)
它似乎给了我混合的结果......如果它需要在创建视图但不能在主线程上运行的原始线程上运行,那么它应该在哪个线程上运行?
我真的很感激任何帮助或建议!
答案 0 :(得分:23)
这些测试测试在自己的应用程序中运行。这也意味着,它们在自己的线程中运行。
您必须将您的工具视为与实际应用一起安装的内容,因此您可能的互动是“有限的”。
您需要从应用程序的UIThread / main线程调用所有视图方法,因此从您的检测线程调用activity.updateDetails(workOrder);
不应用程序主线程。这就是引发异常的原因。
你可以运行你需要在主线程上测试的代码,就像你使用
从另一个线程在你的app中调用它一样activity.runOnUiThread(new Runnable() {
public void run() {
activity.updateDetails(workOrder);
}
}
运行此测试应该可以正常运行。
您收到的非法州例外似乎是因为您与规则的互动。 documentation州
请注意,当存在此注释时,可能无法使用检测方法。
如果您在@Before
开始/接受活动,它也应该有用。
答案 1 :(得分:16)
您可以在UiThreadTestRule.runOnUiThread(Runnable)
的帮助下在主UI线程上运行部分测试:
@Rule
public UiThreadTestRule uiThreadTestRule = new UiThreadTestRule();
@Test
public void loadWorkOrder_displaysCorrectly() throws Exception {
final WorkOrderDetails activity = activityRule.getActivity();
uiThreadTestRule.runOnUiThread(new Runnable() {
@Override
public void run() {
WorkOrder workOrder = new WorkOrder();
activity.updateDetails(workOrder);
}
});
//Verify customer info is displayed
onView(withId(R.id.customer_name))
.check(matches(withText("John Smith")));
}
在大多数情况下,使用UiThreadTest
注释测试方法更简单,但是,它可能会导致其他错误,例如java.lang.IllegalStateException: Method cannot be called on the main application thread (on: main)
。
FYR,以下是UiThreadTest
的Javadoc引用:
注意,由于当前的JUnit限制,使用
Before
和After
注释的方法也将在UI线程上执行。 如果这是一个问题,请考虑使用runOnUiThread(Runnable)。
请注意,上面提到的UiThreadTest
(包android.support.test.annotation
)与(UiThreadTest
(包android.test
))不同。
答案 2 :(得分:12)
现已接受的答案已弃用
实现此目标的最简单方法是使用UiThreadTest
import android.support.test.annotation.UiThreadTest;
@Test
@UiThreadTest
public void myTest() {
// Set up conditions for test
// Call the tested method
activity.doSomethingWithAView()
// Verify that the results are correct
}
答案 3 :(得分:1)
接受的答案描述了正在发生的事情。
另外,如果有人好奇为什么Espresso的触摸UI的方法,例如perform(ViewActions ...)
不需要这样做,这只是因为他们最后会为我们做这件事。
如果您关注perform(ViewActions ...)
,您会发现它最终会执行以下操作(在android.support.test.espresso.ViewInteraction
中):
private void runSynchronouslyOnUiThread(Runnable action) {
...
mainThreadExecutor.execute(uiTask);
...
}
mainThreadExecutor
本身已注明@MainThread
。
换句话说,Espresso也需要按照David所接受的答案采用相同的规则。
答案 4 :(得分:0)
借助androidx测试运行程序,添加了一个新类UiThreadStatement
,该类为此提供了runOnUiThread
方法。
UiThreadStatement.runOnUiThread {
// call activity here
}