1.爲了確保測試穩定性,使用前須要在開發者選項中關閉一下三個設置:
-
窗口動畫縮放;
-
過分動畫縮放;
-
Animator 時長縮放;
2.如何使用:
// dependencies 下面
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
androidTestImplementation 'androidx.test:runner:1.1.0'
androidTestImplementation 'androidx.test:rules:1.1.0'
// android 的 defaultConfig 下面
// 添加 Instrumentation Runner
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@RunWith(AndroidJUnit4.class)
@LargeTest
public class ExampleInstrumentedTest {
// 經過這個來指定在哪一個 Activity 下面查找
@Rule
public ActivityTestRule<MainActivity> activityTestRule = new ActivityTestRule<>(MainActivity.class);
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = ApplicationProvider.getApplicationContext();
assertEquals("com.jaaaelu.gzw.autotest", appContext.getPackageName());
}
@Test
public void matchesText() {
// 去匹配是否存在對應的文字
// 精確匹配不然失敗
onView(withText("Hello")).check(matches(isDisplayed()));
}
}
3.主要組件介紹:
-
Espresso,與 View 交互的入口,主要是 onView 和 onData;
-
ViewMatchers,一個 Matcher<? super View> 的實例建立類,提供了各類 Matcher 的建立;
-
ViewAction,負責在給定的 View 上執行交互;
-
ViewAssertions,提供了經常使用的 ViewAssertion 實例;
-
ViewInteraction,爲開發人員提供主要接口,以便對 View 進行操做或斷言,不過這個咱們不直接操做 Espresso.onView 會返回這個對象;
實例:
@Test
public void clickFab() {
onView(withId(R.id.fab)).perform(click()).check(matches(isDisplayed()));
}
4.經常使用功能介紹:
-
尋找某個 View:
-
對 View 執行操做:
-
能夠執行一個或多個操做,好比常見的點擊事件,onView(withId(R.id.fab)).perform(click());
-
多個操做點擊了輸入 Test 進去,onView(withId(R.id.et_input)).perform(typeText("Test"), click());
-
若是在 ScrollView 中還能夠經過 scrollTo() 操做先滑動到對應位置,不過若是已經顯示在頁面上,就不起做用了;
-
能夠同一個對一個無效的 id 進行操做,從而查看視圖結構,例如 onView(withId(-1)).perform(click());
-
View 斷言:
-
檢查列表中的數據加載:
5.常見斷言與操做:
-
View 沒有顯示:onView(...).check(matcher(not(isDisplayed())));
-
View 不在視圖結構中:onView(...).check(doesNotExist());
-
數據是否在適配器中,能夠經過自定義 Matcher 來完成;
-
能夠經過自定義異常處理器來處理異常狀況,有點類似線程的異常處理器;
// 官方
private static class CustomFailureHandler implements FailureHandler {
private final FailureHandler delegate;
public CustomFailureHandler(Context targetContext) {
delegate = new DefaultFailureHandler(targetContext);
}
@Override
public void handle(Throwable error, Matcher<View> viewMatcher) {
try {
delegate.handle(error, viewMatcher);
} catch (NoMatchingViewException e) {
throw new MySpecialException(e);
}
}
}
// 點開 Fab 按鈕,彈出 Dialog
onView(withId(R.id.fab)).perform(click());
// 而後點擊 Dialog 的 yes 按鈕,使 Dialog 消失
onView(withText("yes")).inRoot(withDecorView(not(is(activityTestRule.getActivity().getWindow().getDecorView())))).perform(click());
6.Espresso 能夠在多進程中的使用,
在 src/androidTest/AndroidManifest.xml 下面加上便可
<!-- 多進程必備 -->
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.jaaaelu.gzw.autotest"
android:targetProcesses="*">
<meta-data
android:name="remoteMethod"
android:value="androidx.test.espresso.remote.EspressoRemote#remoteInit" />
</instrumentation>
-
默認狀況下測試的是應用啓動的默認線程;
-
每一個進程都有一個 AndroidJUintRunner 實例,每一個 AndroidJUintRunner 都把 Espresso 註冊爲測試框架,不一樣進程的 AndroidJUintRunner 須要經過握手來創建鏈接,圖片是跨進程通訊:
7.測試代碼可訪問性,經過使用 AccessibilityChecks 類來使用。
8.AndroidJUnitRunner 類是一個JUnit測試運行器,容許您在 Android 設備上運行 JUnit 3 或 JUnit 4 樣式的測試類,包括那些使用 Espresso 和 UI Automator 測試框架的測試類。測試運行器處理將測試包和被測應用程序加載到設備,運行測試和報告測試結果。此類替換了 InstrumentationTestRunner 類,該類僅支持 JUnit 3 測試。
頁面依賴或者數據依賴怎麼解決?
可否記錄流程,好比截圖
onData
9.onData 用來匹配數據(onView 也能夠匹配 Text,而 onData 更可能是和那種 AdapterView 的控件一塊兒使用,好比 ListView、Spinner 等),onView 用來匹配試圖,例如:
// 咱們經過 onData 匹配列表的內容,並給出對應 Id
onData(withItemContent("item: 60"))
.onChildView(withId(R.id.item_size))
.perform(click());
對應匹配到的內容如圖,先經過 item: 60 找到內容對應的行,而後再經過提供的控件 Id 去找到對應控件(不過這種用法通常適用於 ListView 這樣的控件):
若是咱們使用的是 RecycleView 的話,就須要配合 RecycleViewAction 這個類來進行操做,它提供了一些經常使用的功能:
-
scrollTo() - Scrolls to the matched View(滑動到匹配試圖);
-
scrollToHolder() - Scrolls to the matched View Holder(滑動到匹配的 ViewHolder);
-
scrollToPosition() - Scrolls to a specific position(滑動到指定位置);
-
actionOnHolderItem() - Performs a View Action on a matched View Holder(在匹配的 ViewHolder 上執行操做);
-
actionOnItem() - Performs a View Action on a matched View(在指定 View 上執行操做);
-
actionOnItemAtPosition() - Performs a ViewAction on a view at a specific position(在指定位置上的 View 執行操做);
// 官方 Demo,這個意思就是直接滑動到中間 Holder 位置,isInTheMiddle() 是自定義匹配器,而後判斷是否滑動成功,對應控件是否已經顯示在頁面上了
// First, scroll to the view holder using the isInTheMiddle matcher.
onView(ViewMatchers.withId(R.id.recyclerView))
.perform(RecyclerViewActions.scrollToHolder(isInTheMiddle()));
// Check that the item has the special text.
String middleElementText =
getApplicationContext().getResources().getString(R.string.middle);
onView(withText(middleElementText)).check(matches(isDisplayed()));
// 自定義的匹配器
private static Matcher<CustomAdapter.ViewHolder> isInTheMiddle() {
return new TypeSafeMatcher<CustomAdapter.ViewHolder>() {
@Override
protected boolean matchesSafely(CustomAdapter.ViewHolder customHolder) {
// 調用的是 holder 中的方法來判斷
return customHolder.getIsInTheMiddle();
}
@Override
public void describeTo(Description description) {
description.appendText("item in the middle");
}
};
}
// 點擊第 40 個 item,而後判斷是否已經顯示在頁面上
// First scroll to the position that needs to be matched and click on it.
onView(ViewMatchers.withId(R.id.recyclerView))
.perform(RecyclerViewActions.actionOnItemAtPosition(40, click()));
// Match the text in an item below the fold and check that it's displayed.
String itemElementText = getApplicationContext().getResources().getString(
R.string.item_element_text) + String.valueOf(40);
onView(withText(itemElementText)).check(matches(isDisplayed()));
官方的第二個 Demo 確定也是測試成功的,由於 RecyclerViewActions.actionOnItemAtPosition 這個方法會先滑動到對應位置
10.Espresso-Intents,是 Espresso 的擴展,它能夠用來驗證和存儲被測試的程序發出的 Intent,不過使用前須要先添加對應依賴:
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0'
// 須要指定 Intent 發出頁面
// 若是要測試意圖這裏須要使用 IntentsTestRule 而不是 ActivityTestRule,不過 IntentsTestRule 是它的之類,作了一些額外的工做
// 例如初始化 Intents,裏面有個變量來手機 Intent
// // Should be accessed only from main thread
// private static final List<VerifiableIntent> recordedIntents = new ArrayList<VerifiableIntent>();
@Rule
public IntentsTestRule<DialerActivity> mActivityRule = new IntentsTestRule<>(
DialerActivity.class);
// 對應權限給足
@Rule public GrantPermissionRule grantPermissionRule = GrantPermissionRule.grant("android.permission.CALL_PHONE");
// 判斷對應 Intent.ACTION_CALL 的 Intent 是否發送
@Test
public void typeNumber_ValidInput_InitiatesCall() {
// Types a phone number into the dialer edit text field and presses the call button.
onView(withId(R.id.edit_text_caller_number))
.perform(typeText(VALID_PHONE_NUMBER), closeSoftKeyboard());
onView(withId(R.id.button_call_number)).perform(click());
// Verify that an intent to the dialer was sent with the correct action, phone
// number and package. Think of Intents intended API as the equivalent to Mockito's verify.
// intended 用於校驗給定匹配器中被測應用是否發送該意圖
intended(allOf(
hasAction(Intent.ACTION_CALL),
hasData(INTENT_DATA_PHONE_NUMBER)));
}
// R.id.button_call_number 觸發的方法
private Intent createCallIntentFromNumber() {
final Intent intentToCall = new Intent(Intent.ACTION_CALL);
String number = mCallerNumber.getText().toString();
intentToCall.setData(Uri.parse("tel:" + number));
return intentToCall;
}
// 若是是 startActivityForResult 的啓動方式,能夠經過這種方式來構建代碼並測試,不過這種只是模擬啓動和數據返回,不會真的啓動頁面,而後模擬調用 // onActivityResult
@Test
public void testStartActivityForResult() {
// 構造結果Intent
Intent resultIntent = new Intent();
resultIntent.putExtra("result", "OK");
Instrumentation.ActivityResult activityResult =
new Instrumentation.ActivityResult(Activity.RESULT_OK, resultIntent);
// 若是有相應的 intent 發送,並返回虛構的結果
intending(allOf(
hasComponent(hasShortClassName(".OtherActivity")),
toPackage(TEST_PAGE_NAME)))
.respondWith(activityResult);
// 點擊獲取結果按鈕,這個按鈕就是對應調用 startActivityForResult 方法的按鈕
onView(withId(R.id.for_result_button)).perform(click());
// 查看是否顯示結果
onView(withId(R.id.result_text_view))
.check(matches(withText("OK")));
}
// 驗證 Intent 中參數是否與測試內容一致
intended(allOf(
hasAction(equalTo(Intent.ACTION_VIEW)),
hasCategories(hasItem(equalTo(Intent.CATEGORY_BROWSABLE))),
hasData(hasHost(equalTo("www.google.com"))),
hasExtras(allOf(
hasEntry(equalTo("key1"), equalTo("value1")),
hasEntry(equalTo("key2"), equalTo("value2")))),
toPackage("com.android.browser")));
因此這麼看下來主要就是兩個功能,一個 Intent 的驗證(使用 intended,驗證還能夠驗證 Intent 中的參數是否正確,驗證沒必要以與發送意圖相同的順序發生。從調用Intents.init的時間開始記錄意圖),一個 Intent 的存根(使用 intending,方法說明:啓用存根意圖響應。此方法相似於Mockito.when,當啓動 intent 的活動須要返回數據時(特別是在目標活動是外部的狀況下),此方法特別有用。在這種狀況下,測試做者能夠調用 intending(matcher).thenRespond(myResponse)並驗證啓動活動是否正確處理結果。注意:目標活動不會啓動)。
11.使用 Espresso-Web 測試混合應用程序,也就是 native + WebView,若是隻是想要單獨測試 WebView 可使用 WebDriver 框架來編寫常規的的 Web 測試。
WebDriver 框架使用 Atoms 方式查找與操做 Web 元素,Atoms 相似 ViewAction。
經常使用 API 介紹:
// 先經過 Id 找到元素,而後出發點擊事件,並檢查跳轉後的頁面是否包含 navigation_2.html
onWebView()
.withElement(findElement(Locator.ID, "link_2")) // similar to onView(withId(...))
.perform(webClick()) // Similar to perform(click())
// Similar to check(matches(...))
.check(webMatches(getCurrentUrl(), containsString("navigation_2.html")));
public static final String MACCHIATO = "Macchiato";
// 先經過 id 查找 text_input,而後清空以前的輸入,而後輸入 Macchiato,再查找 submitBtn,點擊按鈕,跳到轉新的網頁上, 並檢查這個網頁上是否之
// 前輸入的文字
@Test
public void typeTextInInput_clickButton_SubmitsForm() {
// Create an intent that displays a web form.
Intent webFormIntent = new Intent();
// ...
// Lazily launch the Activity with a custom start Intent per test.
// 每次都要寫
ActivityScenario.launchActivity(webFormIntent);
// Selects the WebView in your layout. If you have multiple WebView objects,
// you can also use a matcher to select a given WebView,
// onWebView(withId(R.id.web_view)).
onWebView()
// Find the input element by ID.
.withElement(findElement(Locator.ID, "text_input"))
// Clear previous input and enter new text into the input element.
.perform(clearElement())
.perform(DriverAtoms.webKeys(MACCHIATO))
// Find the "Submit" button and simulate a click using JavaScript.
.withElement(findElement(Locator.ID, "submitBtn"))
.perform(webClick())
// Find the response element by ID, and verify that it contains the
// entered text.
.withElement(findElement(Locator.ID, "response"))
.check(webMatches(getText(), containsString(MACCHIATO)));
}
在使用 Espresso-Web 是,必定要確保 JS 打開,
@Rule
public ActivityTestRule<WebViewActivity> mActivityRule = new ActivityTestRule<WebViewActivity>(
WebViewActivity.class, false, false) {
@Override
protected void afterActivityLaunched() {
// Technically we do not need to do this - WebViewActivity has javascript turned on.
// Other WebViews in your app may have javascript turned off, however since the only way
// to automate WebViews is through javascript, it must be enabled.
onWebView().forceJavascriptEnabled();
}
};
12.Espresso 提供的一套同步的功能,因此它並不知道任何異步操做,好比後臺線程上運行的操做。若是爲了讓 Espresso 長期運行,那麼就須要爲空閒資源註冊,若是不使用空閒資源這種方式,可使用例如 Thread.sleep 或 CountDownLatch 的方式。
在執行如下操做時,應該考慮使用空閒資源:
-
從網絡或本地數據源加載數據;
-
數據庫操做;
-
使用 Service;
-
執行復雜的耗時邏輯;
這些操做完成後可能會涉及到 UI 更新,這時就該註冊空閒資源。
可直接使用的空閒資源實現:
-
CountingIdlingResource,計數器,計數器爲零時,關聯的資源被視爲空閒;
-
UriIdlingResource,與上面的類型,不過在資源被視爲空閒以前,計數器須要在特定時間段內爲 0。額外的等待時間會考慮連續的網絡請求;
-
IdlingThreadPoolExecutor,內部實現了線程池,可跟蹤建立的線程池中正在運行的任務總數;
-
IdlingScheduledThreadPoolExecutor,內部實現了 ScheduledThreadPoolExecutor,與上面的同樣,不過它還能夠跟蹤定時執行的任務;
建立本身的 IdlingResource,
public void isIdle() {
// DON'T call callback.onTransitionToIdle() here!
}
public void backgroundWorkDone() {
// 在後臺工做完成後通知
// Background work finished.
callback.onTransitionToIdle() // Good. Tells Espresso that the app is idle.
// Don't do any post-processing work beyond this point. Espresso now
// considers your app to be idle and moves on to the next test action.
}
通常咱們都在 @Before 中註冊空閒資源,而後別忘了在完成了反註冊,代碼以下:
不過這些須要修改代碼,須要在代碼中配合,至少配合通知到空閒資源後臺工做已經完成,而後調用通知方法。
若是不但願過多影響代碼能夠有如下方式,如構建 Gradle's product flavors,僅在調試版本中使用,或使用像 Dagger 的依賴注入框架;