我正在尝试将我的一些 Robotium 单元测试转换为使用 Espresso,但在通过测试更新 UI 时遇到了问题。该测试针对的是一个 fragment ,该 fragment 是一种显示来自对象的数据的表单。该 fragment 有一个方法“BaseFragment.object_set(object)”,然后它将更新 UI 组件(有很多 TextView.setText(object.getField())) .
当我在测试中运行以下命令时...
BaseFragment fragment = (BaseFragment) getFragment(activity);
fragment.object_set(object);
assertNotNull(fragment.object_get());
...我得到这个异常...
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
当我改为运行它时...
activity.runOnUiThread(new Runnable() {
public void run() {
BaseFragment fragment = (BaseFragment) getFragment(activity);
fragment.object_set(object);
}
});
sleep(500);
assertNotNull(fragment.object_get());
...测试成功。如果没有 sleep(),它实际上会因竞争条件而失败。
因此,我的理解是 Espresso 的 sleep 是一种糟糕的形式,我们应该实现并注册一个 Idling 资源。我试过这个...
sEspressoIdlingResource.setNotIdle();
activity.runOnUiThread(new Runnable() {
public void run() {
BaseFragment fragment = (BaseFragment) getFragment(activity);
fragment.object_set(object);
sEspressoIdlingResource.setIdle();
}
});
...在调试时我可以看到 Espresso 实际上正在等待资源释放...只是这仍然会导致竞争条件,测试失败。看起来 UI 直到 AFTER activity.runOnUiThread() 完成后才更新,所以我想我只是不明白哪个线程是哪个的原则......有人可以帮忙吗并解释一下?
下面是一些重要的测试代码(下面的测试会由于竞争条件而失败)...
@RunWith(AndroidJUnit4.class)
public class MyTest{
ActivityTestRule<AMain> mActivityTestRule;
@Rule
public ActivityTestRule<AMain> setUp() {
// other code here
mActivityTestRule = new ActivityTestRule<>(AMain.class);
return mActivityTestRule;
}
@Before
public void before() {
Espresso.registerIdlingResources(BaseTest.sEspressoIdlingResource);
}
@After
public void after() {
Espresso.unregisterIdlingResources(BaseTest.sEspressoIdlingResource);
}
@Test
public void test_1_showDetailsFragment() {
// more code here for activity and object
sEspressoIdlingResource.setNotIdle();
activity.runOnUiThread(new Runnable() {
public void run() {
BaseFragment fragment = (BaseFragment) getFragment(activity);
fragment.object_set(object);
sEspressoIdlingResource.setIdle();
}
});
assertNotNull(fragment.object_get());
// more asserts here
}
}
最佳答案
你现在可能已经自己回答了这个问题,但我相信我也有类似的问题,那就是 Espresso 没有等待 :
activity.runOnUiThread(new Runnable{...})
在执行其操作之前要完成的代码。 (就我而言):
onView(withId(R.id.where_city_search_input)).perform(typeText("Ch"));
据我所知,espresso 在执行其操作之前等待主线程处于空闲状态。所以如果你:
定义一个可运行的:
Runnable myRunnable = new Runnable{...};
告诉 Instrumentation 等待这个 Runnable:
InstrumentationRegistry.getInstrumentation().waitForIdle(myRunnable);
然后运行任何你想要的 Espresso 代码就可以了。最后它应该看起来像这样:
// Define Runnable:
Runnable myRunnable = new Runnable(){
@Override
public void run(){
// What you want to run //
}
};
// Run on UI Thread:
activity.runOnUiThread(myRunnable);
// Tell Instrumentation to wait:
InstrumentationRegistry.getInstrumentation().waitForIdle(myRunnable);
// BEGIN ESPRESSO CODE //
................
// END ESPRESSO CODE //
关于android - 使用 Espresso 的异步 UI 单元测试,IdlingResource 因竞争条件而失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34388098/