java Android Instrumentation 测试 - UI 线程问题
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/35183981/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Android Instrumentation Testing - UI Thread Issues
提问by Khalos
I am trying to write an Instrumentation Test for my Android app.
我正在尝试为我的 Android 应用程序编写一个 Instrumentation 测试。
I'm running into some weird threading issues and I can't seem to find a solution.
我遇到了一些奇怪的线程问题,但似乎找不到解决方案。
My Original 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")));
}
}
This resulted in an
这导致了
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
...
com.kwtree.kwtree.workorder.WorkOrderDetails.updateDetails(WorkOrderDetails.java:155)
android.view.ViewRootImpl$CalledFromWrongThreadException:只有创建视图层次结构的原始线程才能触摸其视图。
...
com.kwtree.kwtree.workorder.WorkOrderDetails.updateDetails(WorkOrderDetails.java:155)
The only thing the updateDetails()
method does is some setText()
calls.
该updateDetails()
方法唯一能做的就是一些setText()
调用。
After researching a bit, it seemed like adding a UiThreadTestRule
and android.support.test.annotation.UiThreadTest
annotation to my test would fix the problem.
经过一番研究,似乎在我的测试中添加一个UiThreadTestRule
和android.support.test.annotation.UiThreadTest
注释可以解决问题。
@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: Method cannot be called on the main application thread (on: main)
java.lang.IllegalStateException: 无法在主应用程序线程上调用方法 (on: main)
(Note: All of the methods in this stack trace are not my code)
(注意:此堆栈跟踪中的所有方法都不是我的代码)
It seems to be giving me mixed results... If it needs to be run on the original thread that created the views but can't run on the main thread, what thread should it be run on?
它似乎给了我混合的结果......如果它需要在创建视图的原始线程上运行但不能在主线程上运行,它应该在哪个线程上运行?
I'd really appreciate any help or suggestions!
我真的很感激任何帮助或建议!
采纳答案by David Medenjak
Those instrumentation tests run inside their ownapp. This also means, they run in their own thread.
这些仪器测试在他们自己的应用程序中运行。这也意味着,它们在自己的线程中运行。
You must think of your instrumentation as something you install alongside your actual app, so your possible interactions are 'limited'.
您必须将仪器视为与实际应用程序一起安装的东西,因此您可能的交互是“有限的”。
You need to call all view methods from the UIThread / main thread of the application, so calling activity.updateDetails(workOrder);
from your instrumentation thread is notthe application main thread. This is why the exception is thrown.
您需要从应用程序的 UIThread/主线程调用所有视图方法,因此activity.updateDetails(workOrder);
从您的检测线程调用不是应用程序主线程。这就是抛出异常的原因。
You can just run the code you need to test on your main thread like you would do if you were calling it inside your app from a different thread by using
您可以在主线程上运行您需要测试的代码,就像您在应用程序中从不同的线程调用它一样,使用
activity.runOnUiThread(new Runnable() {
public void run() {
activity.updateDetails(workOrder);
}
}
With this running your test should work.
这样运行您的测试应该可以工作。
The illegal state exception you are receiving seems to be because of your interaction with the rule. The documentationstates
您收到的非法状态异常似乎是因为您与规则的交互。该文件指出
Note that instrumentation methods may not be used when this annotation is present.
请注意,当存在此注释时,可能无法使用检测方法。
If you start / get your activity in @Before
it should also work.
如果你开始/开始你的活动,@Before
它也应该有效。
回答by Jeremy Kao
You can run portion of your test on the main UI thread with the help of UiThreadTestRule.runOnUiThread(Runnable)
:
您可以借助以下工具在主 UI 线程上运行部分测试UiThreadTestRule.runOnUiThread(Runnable)
:
@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")));
}
In most cases it is simpler to annotate the test method with UiThreadTest
, however, it may incur other errors such as java.lang.IllegalStateException: Method cannot be called on the main application thread (on: main)
.
在大多数情况下,使用 注释测试方法更简单UiThreadTest
,但是,它可能会导致其他错误,例如java.lang.IllegalStateException: Method cannot be called on the main application thread (on: main)
。
FYR, here is a quote from UiThreadTest
's Javadoc:
FYR,这里引用自UiThreadTest
's Javadoc:
Note, due to current JUnit limitation, methods annotated with
Before
andAfter
will also be executed on the UI Thread. Consider using runOnUiThread(Runnable) if this is an issue.
请注意,由于当前的 JUnit 限制,使用
Before
和注释的方法After
也将在 UI 线程上执行。如果这是一个问题,请考虑使用 runOnUiThread(Runnable)。
Please note UiThreadTest
(package android.support.test.annotation
) mentioned above is different from (UiThreadTest
(package android.test
)).
请注意上面提到的UiThreadTest
(包android.support.test.annotation
)与(UiThreadTest
(包android.test
))不同。
回答by Greg Ennis
The accepted answer is now deprecated
已接受的答案现已弃用
The easiest way to achieve this is simply using UiThreadTest
实现这一目标的最简单方法是简单地使用 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
}
回答by harmanjd
With the androidx test runner a new class was added UiThreadStatement
that gives a runOnUiThread
method for this.
使用 androidx 测试运行器添加了一个新类,该类UiThreadStatement
提供了一种runOnUiThread
方法。
UiThreadStatement.runOnUiThread {
// call activity here
}
回答by joakim
The accepted answer describes what is going on perfectly.
接受的答案完美地描述了正在发生的事情。
As an addition, in case someone is curious why Espresso's methods that touch the UI e.g. perform(ViewActions ...)
don't need to do the same, it is simply because they end up doing it later for us.
另外,如果有人好奇为什么 Espresso 的方法接触 UI,例如perform(ViewActions ...)
不需要做同样的事情,那只是因为他们最终会为我们做这件事。
If you follow perform(ViewActions ...)
you will find it ends up doing the following (in android.support.test.espresso.ViewInteraction
):
如果您遵循,perform(ViewActions ...)
您会发现它最终会执行以下操作(在 中android.support.test.espresso.ViewInteraction
):
private void runSynchronouslyOnUiThread(Runnable action) {
...
mainThreadExecutor.execute(uiTask);
...
}
That mainThreadExecutor
is itself annotated with @MainThread
.
那mainThreadExecutor
本身是用@MainThread
.
In other words, Espresso also needs to play by the same rules described by David on the accepted answer.
换句话说,Espresso 也需要按照 David 在已接受答案中描述的相同规则进行操作。