Тестирование инструментальных средств Android - проблемы с интерфейсом пользовательского интерфейса
Я пытаюсь написать Test Instrumentation Test для своего приложения для Android.
У меня возникают некоторые странные проблемы с потоками, и я не могу найти решение.
Мой первоначальный тест:
@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: метод нельзя вызвать в главном потоке приложения (на: main)
(Примечание. Все методы в этой трассировке стека не являются моим кодом)
Кажется, это дает мне смешанные результаты... Если его нужно запустить в исходном потоке, который создал представления, но не может работать в основном потоке, в каком потоке он должен быть запущен?
Я бы очень признателен за любую помощь или предложения!
Ответы
Ответ 1
Те контрольные тесты запускаются внутри собственного приложения. Это также означает, что они запускаются в своем потоке.
Вы должны думать о своей аппаратуре как о том, что вы устанавливаете рядом с вашим реальным приложением, поэтому ваши возможные взаимодействия "ограничены".
Вам нужно вызвать все методы просмотра из UIThread/основного потока приложения, поэтому вызов activity.updateDetails(workOrder);
из вашей инструментальной нити не является основным потоком приложения. Вот почему выбрано исключение.
Вы можете просто запустить код, который вам нужно протестировать в своем основном потоке, как если бы вы его вызывали в своем приложении из другого потока, используя
activity.runOnUiThread(new Runnable() {
public void run() {
activity.updateDetails(workOrder);
}
}
С этим запуском ваш тест должен работать.
Незаконное государственное исключение, которое вы получаете, похоже, связано с вашим взаимодействием с правилом. документация утверждает
Обратите внимание, что при использовании этой аннотации методы инструментария не могут использоваться.
Если вы начинаете/получаете свою деятельность в @Before
, она также должна работать.
Ответ 2
Вы можете выполнить часть своего теста в основном потоке пользовательского интерфейса с помощью 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")));
}
В большинстве случаев проще аннотировать тестовый метод с помощью UiThreadTest
, однако он может нести другие ошибки, такие как java.lang.IllegalStateException: Method cannot be called on the main application thread (on: main)
.
FYR, вот цитата из UiThreadTest
Javadoc:
Обратите внимание, что из-за текущего ограничения JUnit методы, аннотированные с помощью Before
и After
, также будут выполняться в потоке пользовательского интерфейса. Рассмотрите возможность использования runOnUiThread (Runnable), если это проблема.
Обратите внимание, что упомянутый выше UiThreadTest
(package android.support.test.annotation
) отличается от (UiThreadTest
(package android.test
)).
Ответ 3
Принятый ответ устарел. Правильный способ сделать это сейчас просто:
@Test
@UiThreadTest
public void myTest() {
// ...
}
Ответ 4
В принятом ответе описывается, что происходит отлично.
В качестве дополнения, если кому-то интересно, почему методы Espresso, которые касаются пользовательского интерфейса, например. perform(ViewActions ...)
не нужно делать то же самое, просто потому, что они в конечном итоге делают это позже для нас.
Если вы следуете за perform(ViewActions ...)
, вы обнаружите, что он заканчивает выполнение следующего (в android.support.test.espresso.ViewInteraction
):
private void runSynchronouslyOnUiThread(Runnable action) {
...
mainThreadExecutor.execute(uiTask);
...
}
Это mainThreadExecutor
само помечается с помощью @MainThread
.
Другими словами, Espresso также должен играть по тем же правилам, которые описаны Дэвидом в принятом ответе.