總目錄:Espresso從開始到…
onData
onView()
方法採用hamcrest適配器,用於匹配當前視圖結構中有且僅有一個的控件。在大多數情況下onView()
可以滿足我們在測試過程中對於控件定位的需求。但是,在處理AdapterView
的時候,由於AdapterView
的數據源可能很長,很多時候無法一次性將所有數據源顯示在屏幕上,對於沒有顯示在屏幕上的那部分數據,我們通過onView()
是沒有辦法找到的。
常見的ListView、GridView、Spinner都屬於AdapterView。
在使用方法上onData()
和onView()
並沒有過大的差異,主要的區別是:
onView()
匹配當前視圖結構中有且僅有一個的控件onData()
匹配AdapterView()
數據源中有且僅有一個的數據
來看一下Espresso的官方文檔中例子,界面很簡單Activity
只有一個ListView
,而且根據官方的描述可以知道,當前ListView
的每一行都是對應一個Map
首先用onView()
進行控件查找操作,由於onView()
只能對視圖內的控件進行查找,這裏選擇第一條進行測試:
onView(allOf(withText("7"), hasSibling(withText("item 0"))))
.perform(click());
這裏想要進行點擊操作的是第一個item中的“7”,所以使用匹配器withText("7")
進行篩選,但是由於當前界面內有多個控件中text爲“7”,所以額外添加匹配器hasSibling(withText("item 0"))
選擇相鄰控件中包含text“7”的控件,由於這裏使用了多個匹配器便增加了allOf()
來將多個匹配器整合起來,當做一條完整的匹配條件使用。
現在用onData()
進行控件查找操作,爲了彰顯onData()
與上面的區別,這裏選擇第五十條進行測試:
onData(allOf(
is(instanceOf(Map.class)),
hasEntry(equalTo("STR"), is("item: 50) ))
.perform(click());
allOf()
的用法相同,用來將多個匹配條件整合成一條。
instanceOf(Map.class)
就是字面意思,匹配的數據繼承自Map.class
。
hasEntry(equalTo("STR"), is("item: 50) ))
可以簡單的理解爲查找Map<"STR","item:50">
,其中is()
和equalTo()
是將string封裝爲Matcher的方式,會在之後的文章中進行接收,這裏就不過多的贅述了。
還有一點,因爲onData()
是對數據進行匹配的,所以當在同一個界面中有多個AdapterView時,就會報錯,因爲電腦不清楚你需要對哪一組數據進行操作,這裏可以使用inAdapterView()
來確定當前需要的數據源。
onData(Matcher<object>)
.inAdapterView(Matcher<View>)
.perform(click());
注意:
onData()不適用於RecyleView和ScrollView,這兩者與AdapterView在表現形式上類似,但是工作原理是不同的,不能混淆。
- ScrollView
如果操作的視圖在 ScrollView (水平或垂直方向)中,需要考慮在對該視圖執行操作(如click()
)之前通過scrollTo()
方法使其處於顯示狀態。這樣就保證了視圖在執行其他操作之前是在當前視圖範圍內。
onView(…).perform(scrollTo(), click());
- RecyleView
如果想要與RecyleView交互進行自動化測試,需要引入“espresso-contrib”,裏邊包含一系列的Actions可以用於滾動和點擊。
/**
*點擊position位置的item
*/
onView(withId(R.id.recycleview))
.perform(RecyclerViewActions.actionOnItemAtPosition(position,click()));
/**
*滑動到position位置的item
*/
onView(withId(R.id.recycleview))
.perform(scrollToPosition(position));
Toast
Toast
並沒有在我們的常規視圖中,Android支持多窗口,如果我們使用常規的方式是無法檢測到Toast
的,所以這裏需要使用inRoot()
來進行對Toast
的匹配:
onView(withText("South China Sea"))
.inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView()))))
.perform(click());
IdlingResource
IdlingResource本身只是一個簡單的接口
public interface IdlingResource {
//標識異步資源佔用的名字,在日誌中會顯示
public String getName();
//返回目前資源是否可用(閒置),
public boolean isIdleNow();
//Espresso會註冊此回調
public void registerIdleTransitionCallback(ResourceCallback callback);
//回調接口
public interface ResourceCallback {
public void onTransitionToIdle();
}
}
使用方法
- 使用時首先實現接口
IdlingResource
對public boolean isIdleNow();
的返回值進行控制
public class SimpleIdlingResource implements IdlingResource {
@Nullable private volatile ResourceCallback mCallback;
/**
* 管理阻塞狀態
*/
private AtomicBoolean mIsIdleNow = new AtomicBoolean(true);
@Override
public String getName() {
return this.getClass().getName();
}
@Override
public boolean isIdleNow() {
return mIsIdleNow.get();
}
@Override
public void registerIdleTransitionCallback(ResourceCallback callback) {
mCallback = callback;
}
/**
* 在耗時操作的前後分別調用此方法,來改變 isIdleNow() 的狀態,阻塞測試
*/
public void setIdleState(boolean isIdleNow) {
mIsIdleNow.set(isIdleNow);
if (isIdleNow && mCallback != null) {
mCallback.onTransitionToIdle();
}
}
}
- Activity中在耗時操作開始和結束時分別修改Idle狀態。
private void loadData() {
//耗時操作開始變更爲忙碌狀態(false)阻塞測試線程
mIdlingResource.setIdleState(false);
loadData(new Callback{
@Override
void onCall(Data data){
content.setText("finish data");
//耗時操作完成變更爲空閒狀態(true)開啓測試線程
mIdlingResource.setIdleState(true);
}
})
- 在Test的
@Before
和@After
中分別進行註冊和註銷操作:
Espresso.registerIdlingResources(mIdlingResource);
Espresso.unregisterIdlingResources(mIdlingResource);
注意:
Activity中與Test中爲相同mIdlingResource,需要在Test@Before
中獲取Activity中的mIdlingResource,IdlingResource對象需要在Activity(業務邏輯代碼)中創建
IdlingResource的使用,會涉及到App中邏輯代碼的變化,爲了測試專門在業務代碼中增加額外的邏輯,需要測試人員對於代碼或者開發人員對於測試有一定的瞭解,亦或者兩者不分,才能真正良好的IdlingResource。
ActionBar
ActionBar有兩種不同的形式,普通的ActionBar直接通過onView(withId(R.id.xxx))
訪問即可。
另一種就是菜單中的條目,常規的菜單條目可以通過以下代碼執行:
openActionBarOverflowOrOptionsMenu(getInstrumentation().getTargetContext());
在帶有硬件懸浮菜單按鈕的設備上,可以通過通過下面的方式模擬硬件點擊:
(沒有遇到過這種設備,所以也沒有使用過)
openContextualActionModeOverflowMenu();
Espresso-Intent
當使用 Espresso-Intents 時,應當用IntentsTestRule
替換ActivityTestRule
。 IntentsTestRule
使得在 UI 功能測試中使用 Espresso-Intents API 變得簡單。該類是 ActivityTestRule
的擴展,它會在每一個被@Test
註解的測試執行前初始化 Espresso-Intents,然後在測試執行完後釋放Espresso-Intents。
Espresso-Intents分別提供了Intent validation
和Intent stubbing
的方法intended() 和intending().可以理解爲intended()是在Intent發出後進行檢查 ,而intending()是在Intend發出之前設置檢查項。這裏舉出官方Demo的栗子:
@Test
public void validateIntentSentToPackage() {
// 用戶發出了“打電話”的 Intent
user.clickOnView(system.getView(R.id.callButton));
//檢查 Intent 已經被髮出
intended(toPackage("com.android.phone"));
}
@Test
public void activityResult_IsHandledProperly() {
/**
* 新建 Intent 並封裝爲 ActivityResult
*/
Intent resultData = new Intent();
String phoneNumber = "123-345-6789";
resultData.putExtra("phone", phoneNumber);
ActivityResult result = new ActivityResult(Activity.RESULT_OK, resultData);
//設置對Intent的檢查
intending(toPackage("com.android.contacts")).respondWith(result));
//執行發送Intent的操作
onView(withId(R.id.pickButton)).perform(click());
onView(withId(R.id.phoneNumber).check(matches(withText(phoneNumber)));
}