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"))))); 
}