Espresso是谷歌力推的一個UI自動化測試框架,新建一個Andrdoid工程的時候默認就引入了Espresso的核心依賴,所以作爲Android開發者,非常有必要學習這個框架。
之前使用UI Automator的時候,我們經常在不同的指令之間添加一個時間延時保證手機端執行完成,在Espresso直接使用onView(),onView()會等待界面執行完在執行下一步。
Espresso和UI Automator一樣,也是在項目的app/src/androidTest文件夾下編寫測試代碼
下面先來個開胃菜
測試TextView是否已經顯示
新建一個測試類HelloWorldTest
@RunWith(AndroidJUnit4.class)
public class HelloWorldTest {
@Rule
public ActivityTestRule<MainActivity> activityRule =
new ActivityTestRule<>(MainActivity.class);
@Test
public void listGoesOverTheFold() {
onView(withText("Hello World!")).check(matches(isDisplayed()));
}
}
HelloWorldTest非常簡單,就是判斷"Hello World!"是不是已經顯示在屏幕上
- 通過
@Rule
註解來規定測試的Activity是MainActivity,並且測試完後關閉它 - 通過withText方法找到屏幕上的對應的控件
- onView方法可以等待尋找完成之後再工作。尋找完成之後,調用check方法來判斷該控件是不是已經顯示在屏幕上
- 點擊類旁邊的 run Test按鈕 測試完成
Espresso 的API組成
onView()
和onData()
是Espresso和視圖交互的入口都是用來定位一個View,onView()
是定位普通Vew,onData()
可以定位ListVew、GridView 中的一個條目- ViewMatchers: 實現了
Matcher<? super View>
接口,是onView()
的參數,將它傳到onView()
中幫助定位一個View。 - ViewActions:對一個View的動作,比如點擊
click()
,可以傳遞給ViewInteraction.perform()
方法 - ViewAssertions:用來斷言測試結果是否正確,可以傳遞給
ViewInteraction.check()
方法。
測試點擊界面上的一個按鈕
創建一個clickTest()測試方法,點擊界面上的某個按鈕
@Test
public void clickTest(){
onView(withId(R.id.btn_click))
.perform(click())
.check(matches(isDisplayed()));
}
一般情況下使用withId方法通過View的id來找到一個View,不過有時候可能不同界面中使用了相同的id,這時候可以多個條件一起使用來定位一個View,比如下面
//id爲R.id.my_view text爲"Hello!"的控件
onView(allOf(withId(R.id.my_view), withText("Hello!")));
//既有text "7" 又有text "item: 0"的控件
onView(allOf(withText("7"), hasSibling(withText("item: 0"))))
.perform(click());
點擊按鈕,將TextView的文字由Hello World!改爲Hello Espresso!,並檢查結果。
app中的代碼
final TextView textView = findViewById(R.id.tv_text);
findViewById(R.id.btn_click).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
textView.setText("Hello Espresso");
}
});
測試類中添加新的測試方法
@Test
public void matchText(){
//點擊按鈕,將Hello World!改爲Hello Espresso!
onView(withId(R.id.btn_click)).perform(click());
//檢查TextView上的文字是不是Hello Espresso!
onView(withId(R.id.tv_text)).check(matches(withText("Hello Espresso!")));
}
測試RecyclerView
測試RecyclerView,跳到RecyclerView的某一位置處執行點擊
首先需要引入一個依賴espresso-contrib這裏麪包含包含 DatePicker、RecyclerView 和 Drawer 操作、無障礙功能檢查以及 CountingIdlingResource
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.3.0-alpha04'
首先在應用的app中新建一個Activty來顯示一個列表,接着前面的例子,在MainActivity中定義一個按鈕點擊進入列表頁。
findViewById(R.id.btn_to_recyclerview).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(getApplicationContext(),RecyclerViewActivity.class));
}
});
測試根據position定位到某一條
新建一個RecyclerView的測試類RecyclerViewTest,測試根據position定位到某一條
@RunWith(AndroidJUnit4.class)
public class RecyclerViewTest {
@Rule
public ActivityTestRule<MainActivity> activityRule =
new ActivityTestRule<>(MainActivity.class);
@Test
public void testRecyclerView(){
//點擊進入列表頁
onView(ViewMatchers.withId(R.id.btn_to_recyclerview)).perform(click());
//定位到第30條並點擊
onView(ViewMatchers.withId(R.id.recyclerview))
.perform(RecyclerViewActions.actionOnItemAtPosition(30, click()));
//判斷第30條的文字"item++++++++++++++30"是否顯示了
onView(ViewMatchers.withText("item++++++++++++++30")).check(matches(isDisplayed()));
}
}
自定義匹配器來查找一個Item
還可以通過自定義匹配器來匹配一個Item是否存在,匹配器如下
public static Matcher<MyAdapter.ViewHolder> withAdaptedData(){
return new TypeSafeMatcher<MyAdapter.ViewHolder>(){
@Override
public void describeTo(Description description) {
description.appendText("測試item");
}
@Override
protected boolean matchesSafely(MyAdapter.ViewHolder item) {
return item.mTextView.getText().equals("item++++++++++++++30");
}
};
}
通過自定義匹配器滑動到RecyclerView的相應的位置,並判斷該條item是否顯示
@Test
public void matchItemData(){
//點擊進入列表頁
onView(ViewMatchers.withId(R.id.btn_to_recyclerview)).perform(click());
//根據匹配器滑動到相應的位置
onView(withId(R.id.recyclerview)).perform(RecyclerViewActions.scrollToHolder(withAdaptedData()));
//判斷期望的文本有沒有顯示
onView(withText("item++++++++++++++30")).check(matches(isDisplayed()));
}
Espresso-Intents
Espresso-Intents是Espresso的一個擴展,支持對被測應用發出的intent 進行驗證和打樁,適用於 Android Intent,可以判斷Intent是否跳轉成功,傳遞的參數是否正確。
使用它需要添加新的依賴
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0'
編寫Espresso-Intents之前,首先需要設置IntentsTestRule,ntentsTestRule 會在帶有 @Test 註解的每個測試運行前初始化 Espresso-Intents,並在每個測試運行後釋放 Espresso-Intents。
@Rule
public IntentsTestRule<MainActivity> intentsTestRule =
new IntentsTestRule<>(MainActivity.class);
Espresso中可以使用 intended()
和 intending()
這兩個方法來驗證一個Intent。
測試去一個界面選號碼然後打電話的動作
app中的步驟是這樣的,點擊“獲取號碼”按鈕,通過startActivityForResult
去聯繫人頁面選擇一個號碼返回保存到成員變量中,然後點擊“打電話”按鈕通過Intent打開系統打電話的界面。下面是測試代碼:
//測試去聯繫人頁面獲取一個電話號碼
@Test
public void testPickIntent(){
//創建一個Intent用來封裝返回的數據
Intent intent = new Intent();
intent.putExtra(ContactsActivity.KEY_PHONE_NUMBER,"123456789");
//驗證如果有相應的startActivityOnResult發生,就返回前面自己創建的結果。
intending(IntentMatchers.hasComponent(ComponentNameMatchers.hasShortClassName(".ContactsActivity")))
.respondWith(new Instrumentation.ActivityResult(Activity.RESULT_OK,intent));
//點擊去聯繫人頁面的按鈕
onView(withId(R.id.btn_to_contacts)).perform(click());
//點擊打電話的按鈕
onView(withId(R.id.btn_call)).perform(click());
//驗證打電話的Intent是否發送
intended(allOf(
hasAction(Intent.ACTION_CALL),
hasData("tel:123456789")
));
}
- 上面的測試中,intending() 方法用來測試startActivityOnResult,當startActivityOnResult動作發生的時候,就返回自己創建的一個Intent
- intended() 方法用來測試startActivity打開打電話頁面的時候Intent的參數是否正確。
測試打開系統相機獲取圖片的動作
動作:一個Button,一個ImageView,點擊按鈕拍照,拍照確定後將圖片顯示到ImageView上面
@Test
public void testTackPhoto() throws InterruptedException {
//自定義一個拍照返回drawable圖片的Intent
Intent picIntent = new Intent();
//把drawable放到bundle中傳遞
Bundle bundle = new Bundle();
Bitmap bitmap = BitmapFactory.decodeResource(intentsTestRule.getActivity().getResources(), R.mipmap.ic_launcher);
bundle.putParcelable("data", bitmap);
picIntent.putExtras(bundle);
Instrumentation.ActivityResult result =
new Instrumentation.ActivityResult(Activity.RESULT_OK,picIntent);
//判斷是否有包含ACTION_IMAGE_CAPTURE的Intent出現,出現就給它返回result
intending(hasAction(MediaStore.ACTION_IMAGE_CAPTURE)).respondWith(result);
//判斷ImageView上沒有顯示drawable
onView(withId(R.id.iv_take_photo)).check(matches(not(hasDrawable())));
//點擊拍照按鈕去拍照界面
onView(withId(R.id.btn_take_photo)).perform(click());
//判斷ImageView上顯示了一個drawable
onView(withId(R.id.iv_take_photo)).check(matches(hasDrawable()));
}
private BoundedMatcher<View, ImageView> hasDrawable(){
return new BoundedMatcher<View, ImageView>(ImageView.class) {
@Override
protected boolean matchesSafely(ImageView item) {
return item.getDrawable()!=null;
}
@Override
public void describeTo(Description description) {
description.appendText("是否有drawable");
}
};
}
- 自定義一個BoundedMatcher,用來判斷一個ImageView上面是否有圖片
- 從資源文件中找一張圖片,自定義一個Bitmap返回給帶有
ACTION_IMAGE_CAPTURE
這個action的Intent - 點擊拍照按鈕之前和之後都檢查一下ImageView上面是否有圖片。拍照之前沒有,拍照之後有則測試通過。
Espresso Web
Espresso Web用來測試Android中的WebView,混合應用,可以測試js和原生的交互
使用前首先引入依賴包
androidTestImplementation 'androidx.test.espresso:espresso-web:3.2.0'
下面的例子中,使用官網Demo中的本地Html頁面測試,裏面有javaScript相關的操作。完整demo看文末鏈接
簡單使用:
@Test
public void testWebViewHasDisplay(){
//點擊進入WebViewActivity
onView(withId(R.id.btn_go_to_web)).perform(click());
//判斷頁面的標題是否顯示了
onWebView().check(webMatches(Atoms.getTitle(),containsString("Hello Espresso Web")));
}
測試JavaScript,爲了支持 JavaScript 求值,被測 WebView 必須啓用 JavaScript。通過forceJavascriptEnabled()
方法來開啓
@Rule
public ActivityTestRule<WebViewActivity> activityWebRule =
new ActivityTestRule<WebViewActivity>(WebViewActivity.class,false,false){
@Override
protected void afterActivityLaunched() {
onWebView().forceJavascriptEnabled();
}
};
測試:在輸入框輸入文字,點擊某個按鈕,將輸入框中的文字,賦值給另一個標籤。檢測該標籤上的文字是不是輸入的文字。
@Test
public void testJsInteraction(){
//點擊進入WebViewActivity
onView(withId(R.id.btn_go_to_web)).perform(click());
onWebView()
//html界面上找到id爲text_input的元素
.withElement(findElement(Locator.ID, "text_input"))
//清除之前的文字
.perform(DriverAtoms.clearElement())
//輸入文字Lily
.perform(DriverAtoms.webKeys("Lily"))
//通過id找到切換文字的按鈕
.withElement(findElement(Locator.ID, "changeTextBtn"))
//點擊按鈕
.perform(webClick())
//通過id找到顯示文字的標籤
.withElement(findElement(Locator.ID, "message"))
//判斷標籤上的文字是不是Lily
.check(webMatches(getText(), containsString("Lily")));
}
測試:在輸入框中輸入文字,點擊提交按鈕提交表單,檢測表單裏的值跟輸入的文字是否一樣
@Test
public void testJsFormSubmit(){
//點擊進入WebViewActivity
onView(withId(R.id.btn_go_to_web)).perform(click());
onWebView()
// html界面上找到id爲text_input的元素
.withElement(findElement(Locator.ID, "text_input"))
// 清除之前的文字
.perform(clearElement())
// 輸入文字Lily
.perform(DriverAtoms.webKeys("Lily"))
// 通過id找到提交按鈕
.withElement(findElement(Locator.ID, "submitBtn"))
// 點擊按鈕執行提交動作
.perform(webClick())
// 通過id找到form表單的id
.withElement(findElement(Locator.ID, "response"))
// 驗證提交的內容是不是Lily
.check(webMatches(getText(), containsString("Lily")));
}
Espresso常用的功能就練習完了,還有一些不常用的功能可以點擊去官網查看