單個應用的UI測試
測試單個應用中的用戶交互可以幫助我們確保用戶在使用使用應用 時不會遇到一些意想不到的結果或者遇到糟糕的用戶體驗。如果你需要驗證你的應用的 UI 功能正確,你應該養成創建用戶界面(UI)測試的習慣。
Android 測試支持庫 提供的 Espresso 測試框架,提供了用於編寫UI測試以模仿目標App中的用戶交互的API。Espresso 測試可以運行在Android 2.3.3(API 10)以上的設備上。使用 Espresso 的一個關鍵的好處就是它提供了測試操作與正在測試的App的UI的自動同步。Espresso 可以觀察到什麼時候主線程是空閒的,所以他可以再合適的時間運行你的測試命令,提升測試的可靠性。這種能力可以減少你在測試測試代碼中添加任何時間變通方法(例如 Thread.sleep())的麻煩。
Espresso 測試框架是基於 instrumentation 的API,並且在 AndroidJUnitRunner 測試運行器上運行。
設置 Espresso
在使用 Espresso 構建你的UI測試之前,請確保配置了你的測試代碼的路徑和項目的依賴,就像 測試基礎 裏面描述的一樣。
在你的Android app module 中的 build.gradle 文件,你必須設置一個 Espresso 庫的依賴關係引用:
dependencies {
// Other dependencies ...
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
}
關掉你的測試設備上的動畫效果 - 測試設備上的系統動畫打開可能會導致意外的結果或者可能使你的測試失敗。打開開發者選項從設置中關調動化並且也關掉下面的幾個選項:
窗口動畫縮放
過渡動畫縮放
動畫程序時長縮放
如果你想設置您的工程去使用 Espresso 的特性而不是核心API提供的功能,請看 resource
創建一個 Espresso 測試類
要想創建一個 Espresso 測試,請創建一個遵循下面編程模型的 Java 類:
通過調用 onView 方法或者用於 AdapterView 控制的 onData() 方法,來找到在 Activity 中你想測試的 UI 組件(例如,應用中的一個登錄按鈕)。
通過調用 ViewInteraction.perform() 或者 DataInteraction.perform() 方法傳入用戶操作來模擬一個執行在 UI 組件上的用戶交互(例如,註冊按鈕的點擊事件).要在同一個組件上對多個動作進行排序,使用你的方法參數中的逗號分割來鏈接他們。
根據需要重複上述步驟,以模擬目標應用多個界面的用戶操作流。
在執行了這些用戶交互後,使用 ViewAssertions 方法來檢查 UI 是否是期盼的狀態或者行爲。
以下各節將詳細介紹這些步驟.
下面的代碼片段展示了你的測試類應該如何調用這個基本的流程:
onView(withId(R.id.my_view)) // withId(R.id.my_view) is a ViewMatcher
.perform(click()) // click() is a ViewAction
.check(matches(isDisplayed())); // matches(isDisplayed()) is a ViewAssertion
使用 Espresso 和 ActvityTestRule
下面的部分解釋了應該如何使用 JUnit4 風格來創建一個新的 Espresso 測試,並使用 ActivityTestRule 來減少你需要編寫的樣板代碼的數量。通過使用 ActivityTestRule,測試框架在每個使用 @Test 註解的方法和 任何使用 @Before 註解方法之前啓動被測的 Activity. 在測試完成後,測試框架會結束測試的 Activity 並且所有 使用 @After 註解的方法都會執行。
package com.example.android.testing.espresso.BasicSample;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
...
@RunWith(AndroidJUnit4.class)
@LargeTest
public class ChangeTextBehaviorTest {
private String mStringToBetyped;
@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(
MainActivity.class);
@Before
public void initValidString() {
// Specify a valid string.
mStringToBetyped = "Espresso";
}
@Test
public void changeText_sameActivity() {
// Type text and then press the button.
onView(withId(R.id.editTextUserInput))
.perform(typeText(mStringToBetyped), closeSoftKeyboard());
onView(withId(R.id.changeTextBt)).perform(click());
// Check that the text was changed.
onView(withId(R.id.textToBeChanged))
.check(matches(withText(mStringToBetyped)));
}
}
訪問UI組件
在 Espresso 可以與你被測應用交互之前,你必須首先確認 UI組件或 view. Espresso 支持 Hamcrest marchers 的使用用來確認你的應用中的 view 或者 adapter.
爲了找到view,調用 onView() 方法並傳遞進去一個 view matcher, 他知道該你了你的目標view. Specifying a View Matcher 描述了更多的細節. onView() 方法返回了一個 ViewInteraction 對象,允許你的測試與 view 交互。然而,如果你想在 RecyclerView 中查找一個 view, onView() 方法可能不起作用. 這種情況下,請按照 Locating a view in an AdapterView 中的說明進行操作.
注意: onView() 方法不會檢查你指定的 view 是否有效,Espresso 只使用提供的 matcher 檢查當前的視圖結構。如果沒有找到相匹配的View,這個方法將會拋出一個 NoMatchingViewException 異常.
下面的代碼片段展示了你應該如何編寫一個測試用來訪問一個 EditText 字段,輸入一段文本,關閉虛擬鍵盤,然後執行按鈕的點擊.
public void testChangeText_sameActivity() {
// Type text and then press the button.
onView(withId(R.id.editTextUserInput))
.perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard());
onView(withId(R.id.changeTextButton)).perform(click());
// Check that the text was changed.
...
}
指定一個 View Matcher
你可以通過這些方法來指定一個 view matcher
- 調用 ViewMatchers 類中的方法.例如,通過他顯示的文字來查找一個 view,你可以調用這樣的方法:
onView(withText("Sign-in"));
同樣,你一可以調用 withId() 方法,提供 view 的資源ID(R.id),像下面的例子展示的那樣:
onView(withId(R.id.button_signin));
Android 資源 ID 不保證是唯一的。因此如果你的測試視圖匹配多個 view 共同使用的資源 ID ,Espresso 將會拋出AmbiguousViewMatcherException異常.
- 使用 Hamcrest Matchers 類,你可以使用 allOf() 來合併多個 matcher, 像 containsString() 和 instanceOf() .這種方法可以使你更嚴格的過濾匹配結果,如以下示例所示:
onView(allOf(withId(R.id.button_signin), withText("Sign-in")));
你可以使用 not 關鍵字來過濾不符合匹配的 view,如以下示例所示:
onView(allOf(withId(R.id.button_signin), not(withText("Sign-out"))));
要在你的測試中使用這些方法,請導入 org.hamcrest.Matchers 包.要了解更多關於 Hamcrest matching,請看 Hamcrest site.
要提高你的 Espresso 測試的性能,請指定查找目標 view 所需要的最小的匹配信息。例如,過一個 view 可以通過他的描述的文本作爲唯一標識,那你就不需要指定他,他也可以從 TextView 實例分配。
定位 AdapterView 中的 view
在一個 AdapterView 組件中,視圖在運行時動態填充子視圖。如果你想測試的目標 view 在 AdapterView (例如 ListView,GridView ,或者 Spinner)裏面 onView() 方法可能不會起作用因爲可能只會有一部分 view 在當前視圖結構加載。
取而代之的是,調用 onData() 方法獲得一個 DataInteraction 對象以訪問目標視圖元素。 Espresso 處理將目標視圖加載到當前的視圖結構中。 Espresso 也負責滾動目標元素,並將焦點放到元素上。
注意:onData() 方法不會檢查你所制定的項目是否與視圖對應。 Espresso 只會查找當前的視圖結構。如果沒有找到相匹配的,那麼這個方法會拋出 NoMatchingViewException 異常。
下面的代碼片段展示了你應該如何使用 onData 方法結合 Hamcrest maatching 去查找一個給定字符串列表中的確定項目。在這個例子中,LongListActivity 類包含通過 SimpleAdapter 暴露的字符串集合。
onData(allOf(is(instanceOf(Map.class)),
hasEntry(equalTo(LongListActivity.ROW_TEXT), is("test input")));
執行動作
調用 ViewInteraction.perform 或者 DataInteraction.pergorm() 方法來模擬UI組件上的用戶交互。你必須傳進去一個或多個 ViewAction 對象作爲參數。 Espresso 按照給定的順序依次觸發每個操作,並且在主線程執行他們。
ViewActions 類提供了一系列用於指定常用動作的幫助方法。你可以使用這些方法作爲辯解的快捷方式,而不用創建並配置一個獨立的 ViewAction 對象。你可以指定如下操作:
ViewActions.cllick():點擊視圖
ViewActions.typeText(): 點擊視圖並且輸入一個特定的字符串
ViewActions.scrollTo(): 滑動視圖。目標視圖必須是 ScrollView 的子類,並且他的 android:visibility 屬性必須是 VISIBLE 。對於那些繼承自 Adapter 的視圖(例如 ListView ),onData() 方法將負責滾動。
ViewActions.pressKey(): 使用特定的鍵碼執行按鍵操作。
ViewActions.clearText(): 清楚目標視圖上的文本。
如果目標視圖在 ScrollView 裏面,先執行 ViewActions.scrollTo() 操作來顯示目標是圖然後再執行其他操作。如果視圖已經顯示了,那麼 ViewActions.scrollTo() 操作沒有影響。
使用 Espresso Intents 隔離測試你的 acticity
Espresso Intents 可以驗證一個應用程序發出去的 intent 的 stub.通過使用 Espresso Intents,你可以通過攔截的發出去的 intents,對結果進行存根,並將其發送回被測組件來隔離測試應用組件,actiity 或者 service 。
要想開始使用 Espresso Intents 進行測試,請將下面這行添加到到你的 app 級別下的 build.gradle 文件。
dependencies {
// Other dependencies ...
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2'
}
要想測試一個 intent,你需要創建一個 IntentTestRule 類的實例。IntentTestRule 類在每次測試之前初始化 Espresso Intents ,終止主 activity,並且在每次測試完成之後釋放 Espresso Intents 。
下面代碼片段所展示的測試類爲明確的 intent 提供了一個簡單的測試。他測試了你在 構建你的第一個應用程序 教程裏面創建的 activities 和 intents 。
@Large
@RunWith(AndroidJUnit4.class)
public class SimpleIntentTest {
private static final String MESSAGE = "This is a test";
private static final String PACKAGE_NAME = "com.example.myfirstapp";
/* Instantiate an IntentsTestRule object. */
@Rule
public IntentsTestRule≶MainActivity> mIntentsRule =
new IntentsTestRule≶>(MainActivity.class);
@Test
public void verifyMessageSentToMessageActivity() {
// Types a message into a EditText element.
onView(withId(R.id.edit_message))
.perform(typeText(MESSAGE), closeSoftKeyboard());
// Clicks a button to send the message to another
// activity through an explicit intent.
onView(withId(R.id.send_message)).perform(click());
// Verifies that the DisplayMessageActivity received an intent
// with the correct package name and message.
intended(allOf(
hasComponent(hasShortClassName(".DisplayMessageActivity")),
toPackage(PACKAGE_NAME),
hasExtra(MainActivity.EXTRA_MESSAGE, MESSAGE)));
}
}
要想了解更多關於 Espresso Intents,請看 Android 測試支持庫中的 Espresso Intents 文檔。 你也可以下載 IntentsBasicSample 和 IntentsAdvancedSample 代碼示例。
使用 Espresso Web 測試 WebViews
Espresso Web 允許你測試 activity 中的 WebView 組件。它使用 WebDriver API 類檢查和控制 WebView 的行爲。
要想開始使用 Espresso Web 進行測試,請將下面這行添加到到你的 app 級別下的 build.gradle 文件。
dependencies {
// Other dependencies ...
androidTestCompile 'com.android.support.test.espresso:espresso-web:2.2.2'
}
使用 Espresso Web 創建一個測試時,需要在實例化 ActivityTestRule 對象以測試activity時在 WebView 上啓用 JavaScript、在測試中,你可以選擇 WebView中顯示的 HTML 元素並且模仿用戶交互,例如向 text box 中輸入文本並且點擊按鈕。在這些動作執行完畢後,你就可以驗證網頁上顯示的結果是否如你所期望的結果相匹配。
在下面的代碼片段中,該類測試了被測試activity中id爲”webview”的 WebView 組件。
verifyValidInputYieldsSuccesfulSubmission() 測試選擇了一個網頁上的 < input> 元素,輸入了一些文本,並且檢查另一個元素上顯示的文本。
@LargeTest
@RunWith(AndroidJUnit4.class)
public class WebViewActivityTest {
private static final String MACCHIATO = "Macchiato";
private static final String DOPPIO = "Doppio";
@Rule
public ActivityTestRule mActivityRule =
new ActivityTestRule(WebViewActivity.class,
false /* Initial touch mode */, false /* launch activity */) {
@Override
protected void afterActivityLaunched() {
// Enable JavaScript.
onWebView().forceJavascriptEnabled();
}
}
@Test
public void typeTextInInput_clickButton_SubmitsForm() {
// Lazily launch the Activity with a custom start Intent per test
mActivityRule.launchActivity(withWebFormIntent());
// Selects the WebView in your layout.
// If you have multiple WebViews 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
.perform(clearElement())
// Enter text into the input element
.perform(DriverAtoms.webKeys(MACCHIATO))
// Find the submit button
.withElement(findElement(Locator.ID, "submitBtn"))
// Simulate a click via JavaScript
.perform(webClick())
// Find the response element by ID
.withElement(findElement(Locator.ID, "response"))
// Verify that the response page contains the entered text
.check(webMatches(getText(), containsString(MACCHIATO)));
}
}
要想了解更多關於 Espresso Web,請看 Android 測試支持庫中的 Espresso Web 文檔。 你也可以下載 Espresso Web code sample 代碼示例。
驗證結果
調用 ViewInteration.check() 或者 DataInteration.check() 方法來斷言用戶界面上的視圖是否匹配一些預期的狀態。你必須傳遞一個 ViewAssertion 對象作爲參數。如果斷言失敗了,那麼 Espresso 將會拋出 AssertionFailedError 異常
ViewAssertions 類提供了一些列用於確定普通斷言的幫助方法,你可以使用的這些銨鹽包括:
doesNotExist: 斷言當前視圖結構中沒有與指定條件所匹配的視圖。
matches: 斷言指定的視圖存在於當前的視圖結構中並且它的狀態與給定的 Hamcrest matcher 相匹配。
selectedDescendentsMatch: 斷言給定的子視圖存在父視圖,並且他們的狀態與給定的 Hamcrest matcher 相匹配。
下面的代碼片段展示了你應該如何檢查 UI 上顯示的文本與之前在 EditText 上輸入的文本相同。
public void testChangeText_sameActivity() {
// Type text and then press the button.
...
// Check that the text was changed.
onView(withId(R.id.textToBeChanged))
.check(matches(withText(STRING_TO_BE_TYPED)));
}
在設備或者模擬器上運行 Espresso Tests
你可以在 Android Studio 上或者從命令行上運行測試。確定指定了 AndroidJUnitRunner 作爲你項目的默認 instrumentation 測試。