0

我试图测试我的SettingsActivity类,但我不断收到AmbiguousViewMatcherException浓咖啡:多个ListViews

这里是我的测试用例:

@Test 
public void whenHospitalSettingEmpty_shouldDisplaySummary() throws Exception { 
    Resources res = getInstrumentation().getTargetContext().getResources(); 

    mPreferenceEditor.putString(
      res.getString(R.string.activity_settings_hospital_key), 
      ""); 

    mActivityTestRule.launchActivity(null); 

    String expected = res.getString(R.string.activity_settings_hospital_summary); 

    onData(allOf(
      is(instanceOf(Preference.class)), 
      withKey(res.getString(R.string.activity_settings_hospital_key)), 
      withSummary(R.string.activity_settings_hospital_summary), 
      withTitle(R.string.activity_settings_hospital_title))) 
      .onChildView(withText(expected)) 
      .check(matches(isDisplayed())); 

} 

这里的日志输出:

android.support.test.espresso.AmbiguousViewMatcherException: 'is assignable from class: class android.widget.AdapterView' matches multiple views in the hierarchy. 
Problem views are marked with '****MATCHES****' below. 

View Hierarchy: 
... 
+------->LinearLayout{id=16909142, res-name=headers, visibility=VISIBLE, width=600, height=887, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=2} 
| 
+-------->ListView{id=16908298, res-name=list, visibility=VISIBLE, width=536, height=823, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=32.0, y=32.0, child-count=0} ****MATCHES****** 
| 
+-------->FrameLayout{id=16909143, res-name=list_footer, visibility=VISIBLE, width=536, height=0, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=32.0, y=855.0, child-count=0} 
| 
+------>RelativeLayout{id=16909146, res-name=button_bar, visibility=GONE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=2} 
| 
+------->AppCompatButton{id=16909147, res-name=back_button, visibility=VISIBLE, width=0, height=0, has-focus=false, has-focusable=true, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=false, is-focusable=true, is-layout-requested=true, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, text=Tilbage, input-type=0, ime-target=false, has-links=false} 
| 
+------->LinearLayout{id=-1, visibility=VISIBLE, width=0, height=0, has-focus=false, has-focusable=true, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=2} 
| 
+-------->AppCompatButton{id=16909148, res-name=skip_button, visibility=GONE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=false, is-focusable=true, is-layout-requested=true, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, text=Spring over, input-type=0, ime-target=false, has-links=false} 
| 
+-------->AppCompatButton{id=16909149, res-name=next_button, visibility=VISIBLE, width=0, height=0, has-focus=false, has-focusable=true, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=false, is-focusable=true, is-layout-requested=true, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, text=Næste, input-type=0, ime-target=false, has-links=false} 
| 
+----->LinearLayout{id=-1, visibility=VISIBLE, width=600, height=887, has-focus=true, has-focusable=true, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=3} 
| 
+------>ListView{id=16908298, res-name=list, visibility=VISIBLE, width=600, height=887, has-focus=true, has-focusable=true, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=true, is-focusable=true, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=12} ****MATCHES****** 
| 
+------->AppCompatTextView{id=16908310, res-name=title, visibility=VISIBLE, width=584, height=35, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=8.0, y=0.0, text=Synkronisering, input-type=0, ime-target=false, has-links=false} 
... 

出于某种原因,onData() 2个ListView s比,但我只是无法弄清楚如何防止这种情况,因为我不明白第一个ListView来自哪里。

这里是我的preference.xml

<?xml version="1.0" encoding="utf-8"?> 
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> 
    <PreferenceCategory 
     android:title="@string/activity_settings_sync_category_title" 
     android:key="@string/activity_settings_sync_category_key"> 

     <SwitchPreference 
      android:key="@string/activity_settings_sync_key" 
      android:summary="@string/activity_settings_sync_summary" 
      android:title="@string/activity_settings_sync_title" 
      android:defaultValue="true"/> 

    </PreferenceCategory> 

    <PreferenceCategory 
     android:title="@string/activity_settings_site_category_title" 
     android:key="@string/activity_settings_site_category_key" 
     android:summary="@string/activity_settings_site_category_summary"> 


     <EditTextPreference 
      android:key="@string/activity_settings_hospital_key" 
      android:title="@string/activity_settings_hospital_title" 
      android:summary="@string/activity_settings_hospital_summary" 
      android:dialogTitle="@string/activity_settings_hospital_dialog_title" 
      android:dialogMessage="@string/activity_settings_hospital_dialog_message" 
      android:inputType="textCapWords" /> 

     <EditTextPreference 
      android:key="@string/activity_settings_department_key" 
      android:title="@string/activity_settings_department_title" 
      android:summary="@string/activity_settings_department_summary" 
      android:dialogTitle="@string/activity_settings_department_dialog_title" 
      android:dialogMessage="@string/activity_settings_department_dialog_message" 
      android:inputType="textCapWords" /> 

    </PreferenceCategory> 

    <PreferenceCategory 
     android:title="@string/activity_settings_notification_ringtone_category_title" 
     android:key="@string/activity_settings_notification_ringtone_category_key"> 


     <RingtonePreference 
      android:key="@string/activity_settings_notification_ringtone_key" 
      android:title="@string/activity_settings_notification_ringtone_title" 
      android:summary="@string/activity_settings_notification_ringtone_summary" 
      android:dialogTitle="@string/activity_settings_notification_ringtone_dialog_title" 
      android:dialogMessage="@string/activity_settings_notification_ringtone_dialog_message"/> 

    </PreferenceCategory> 

    <PreferenceCategory 
     android:title="@string/activity_settings_dicom_category_title" 
     android:key="@string/activity_settings_dicom_category_key"> 

     <SwitchPreference 
       android:key="@string/activity_settings_dicom_worklist_key" 
       android:title="@string/activity_settings_dicom_worklist_title" 
       android:summary="@string/activity_settings_dicom_worklist_summary" /> 

      <!--<EditTextPreference--> 
       <!--android:key="dcmwl_ae_title_preference"--> 
       <!--android:title="@string/pref_dicom_settings_worklist_ae_title"--> 
       <!--android:dialogMessage="@string/pref_dicom_settings_worklist_ip_dialog_text"--> 
       <!--android:dialogTitle="@string/pref_dicom_settings_worklist_ip_dialog_title"--> 
       <!--android:dependency="dcmwl_enable"/>--> 
      <!--<EditTextPreference--> 
       <!--android:defaultValue="10.0.0.2"--> 
       <!--android:key="dcmwl_ip_preference"--> 
       <!--android:title="@string/pref_dicom_settings_worklist_ip"--> 
       <!--android:dependency="dcmwl_enable" />--> 
      <!--<EditTextPreference--> 
       <!--android:key="dcmwl_port_preference"--> 
       <!--android:title="@string/pref_dicom_settings_worklist_port"--> 
       <!--android:dependency="dcmwl_enable"/>--> 

     <SwitchPreference 
      android:key="@string/activity_settings_dicom_pacs_key" 
      android:title="@string/activity_settings_dicom_pacs_title" 
      android:summary="@string/activity_settings_dicom_pacs_summary"/> 

      <!--<EditTextPreference--> 
       <!--android:key="pacs_ae_title_preference"--> 
       <!--android:title="@string/pref_dicom_settings_pacs_ae_title"--> 
       <!--android:dependency="pacs_enable"/>--> 
      <!--<EditTextPreference--> 
       <!--android:defaultValue="10.0.0.2"--> 
       <!--android:key="pacs_ip_preference"--> 
       <!--android:title="@string/pref_dicom_settings_pacs_ip"--> 
       <!--android:dependency="pacs_enable"/>--> 
      <!--<EditTextPreference--> 
       <!--android:key="pacs_port_preference"--> 
       <!--android:title="@string/pref_dicom_settings_pacs_port"--> 
       <!--android:dependency="pacs_enable"/>--> 

    </PreferenceCategory> 

    <PreferenceCategory 
     android:title="@string/activity_settings_debug_category_title" 
     android:key="@string/activity_settings_debug_category_key"> 

     <SwitchPreference 
      android:key="@string/activity_settings_debug_key" 
      android:summary="@string/activity_settings_debug_summary" 
      android:title="@string/activity_settings_debug_title" 
      android:defaultValue="false"/> 

    </PreferenceCategory> 

</PreferenceScreen> 

更新: 试图修改从第一个答案是这样建议的解决方案:

private static Matcher<View> withResName(final String resName) { 

     return new TypeSafeMatcher<View>() { 
      @Override 
      public void describeTo(Description description) { 
       description.appendText("with res-name: " + resName); 
       Timber.d("Resourcename: ", resName); 
      } 

      @Override 
      public boolean matchesSafely(View view) { 
       Timber.d("View ID: ", view.getId()); 
       String matchableResName = view.getResources().getResourceEntryName(view.getId()); 
       return !TextUtils.isEmpty(matchableResName) && matchableResName.equals(resName); 
      } 
     }; 
    } 

测试现在看起来是这样的:

@Test 
    public void whenHospitalSettingEmpty_shouldDisplaySummary() throws Exception { 
     Resources res = getInstrumentation().getTargetContext().getResources(); 

     mPreferenceEditor.putString(
       res.getString(R.string.activity_settings_hospital_key), 
       "").commit(); 

     mActivityTestRule.launchActivity(null); 

     String expected = res.getString(R.string.activity_settings_hospital_summary); 

//  onView(withText(expected)).check(matches(isDisplayed())); 

     onData(allOf(
       is(instanceOf(Preference.class)), 
       withKey(res.getString(R.string.activity_settings_hospital_key)), 
       withSummary(R.string.activity_settings_hospital_summary), 
       withTitle(R.string.activity_settings_hospital_title))) 
       .onChildView(withText(expected)) 
       .inAdapterView(withParent(not(withResName("headers")))) 
       .check(matches(isDisplayed())); 


    } 

但它仍然不起作用。日志输出看起来是这样的:

... 
I/TestRunner: android.content.res.Resources$NotFoundException: 
Unable to find resource ID #0xffffffff 
    at android.content.res.Resources.getResourceEntryName(Resources.java:2127) 
    at com.conhea.smartgfr.preferences.SettingsActivityTest$2.matchesSafely(SettingsActivityTest.java:153) 
    at com.conhea.smartgfr.preferences.SettingsActivityTest$2.matchesSafely(SettingsActivityTest.java:143) 
    at org.hamcrest.TypeSafeMatcher.matches(TypeSafeMatcher.java:65) 
    at org.hamcrest.core.IsNot.matches(IsNot.java:25) 
    at android.support.test.espresso.matcher.ViewMatchers$26.matchesSafely(ViewMatchers.java:892) 
    at android.support.test.espresso.matcher.ViewMatchers$26.matchesSafely(ViewMatchers.java:883) 
    at org.hamcrest.TypeSafeMatcher.matches(TypeSafeMatcher.java:65) 
... 

回答

0

我想PreferenceCategory将在内部创建ListView。因此,您最终会在您的层次中有多个AdapterView,因此发生AmbiguousViewMatcherException

只要你做具备与ListView S的id,你不能执行与ViewMatcher.withId()匹配。因此,您必须根据这些ListView之间的重要事项创建自定义匹配器。

不幸的是,那些ListView s具有相同的属性,因此您必须在父级别上执行匹配。那些ListView的父母是LinearLayout。正如你可能会注意到的,其中一个(实际上是你不感兴趣的)有一个属性res-name=headers

这会给你一个提示执行上ListView匹配,有一个家长,其中资源名称是“头”

让我们试着用浓咖啡来做到这一点。一等品,让我们写一个匹配,一个View与特定的资源名称匹配:

 


    class ResourceNameMatcher extends TypeSafeMatcher { 

     private final String resName; 

     public ResourceNameMatcher(String resName) { 
     this.resName = resName; 
     } 

     @Override protected boolean matchesSafely(View item) { 
     Resources resources = item.getResources(); 
     String matchableResName = resources.getResourceName(item.getId()); 
     return !TextUtils.isEmpty(matchableResName) && matchableResName.equals(resName); 
     } 

     @Override public void describeTo(Description description) { 
     description.appendText("with res-name: " + resName); 
     } 
    } 

 

现在,让我们进行一个View匹配,具有特定资源名称:

 


     onData(allOf(
        Matchers.is(instanceOf(Preference.class), 
        ... // other matchers here))) 
      .onChildView(withText(expected)) 
      .inAdapterView(withParent(not(new ResourceNameMatcher("headers")))) 
      .check(matches(isDisplayed())); 

 

注,这只是一个草图代码,只要我没有测试过,它可能不起作用。不过,这个概念应该起作用。

+0

这是行不通的。 item.getId()没有返回一个ID。出于某种原因,android.R.id.headers id是隐藏的。这非常奇怪,因为在布局检查器中,我可以看到,linearlayout有一个名为headers的ID,在调试输出中我可以看到整数值。 – Bohsen

+0

getIdentifier()是解决方案的一部分,而不是'item.getId()'执行'resources.getIdentifier(“headers”,“id”,“android”)' – azizbekian

+0

。谢谢您的帮助。 – Bohsen

0

这是我的解决方案,用于测试我的SettingsActivity。

测试:

@Test 
public void whenHospitalSettingEmpty_shouldDisplaySummary() throws Exception { 
    Resources res = getInstrumentation().getTargetContext().getResources(); 

    // Set the hospital preferencevalue to empty 
    mPreferenceEditor.putString(
      res.getString(R.string.activity_settings_hospital_key), 
      "").commit(); 

    // Launch activity 
    mActivityTestRule.launchActivity(null); 

    // When hospital value is empty we want to display the summary from our string ressources 
    // instead of our preferencevalue 
    onPreferenceRow(allOf(
       withKey(res.getString(R.string.activity_settings_hospital_key)), 
       withSummary(R.string.activity_settings_hospital_summary))) 
      .check(matches(isDisplayed())); 
} 

我的辅助方法:

... 

private static Matcher<View> withResName(final String resName) { 

    return new TypeSafeMatcher<View>() { 
     @Override 
     public void describeTo(Description description) { 
      description.appendText("with res-name: " + resName); 
     } 

     @Override 
     public boolean matchesSafely(View view) { 
      int identifier = view.getResources().getIdentifier(resName, "id", "android"); 
      return !TextUtils.isEmpty(resName) && (view.getId() == identifier); 
     } 
    }; 
} 

private static DataInteraction onPreferenceRow(Matcher<? extends Object> datamatcher) { 

    DataInteraction interaction = onData(datamatcher); 

    return interaction 
     .inAdapterView(allOf(
      withId(android.R.id.list), 
      not(withParent(withResName("headers"))))); 
}