使用Espresso測試Android使用者介面
在這篇文章中,您將學習如何使用Espresso測試框架編寫UI測試並自動化測試工作流程,而不是使用繁瑣且極易出錯的手動過程。
Espresso是用於在Android中編寫UI測試的測試框架。根據官方文件,您可以:
使用Espresso編寫簡潔,美觀,可靠的Android UI測試。
1. 為什麼要使用濃縮咖啡?
手動測試的一個問題是執行起來既費時又乏味。例如,要在Android應用中測試登入螢幕(手動),您必須執行以下操作:
- 啟動應用程式。
- 導航到登入螢幕。
- 確認是否
usernameEditText
和passwordEditText
可見。 - 在各自的欄位中鍵入使用者名稱和密碼。
- 確認登入按鈕是否也可見,然後單擊該登入按鈕。
- 檢查登入成功或失敗時是否顯示正確的檢視。
而不是花費所有這些時間手動測試我們的應用程式,最好花更多時間編寫程式碼,使我們的應用程式脫穎而出!而且,即使手動測試繁瑣且速度很慢,它仍然容易出錯,您可能會錯過一些極端情況。
自動化測試的一些優點包括:
- 自動化測試每次執行時都會執行完全相同的測試用例。
- 開發人員可以在將問題傳送給QA團隊之前快速發現問題。
- 與手動測試不同,它可以節省大量時間。通過節省時間,軟體工程師和QA團隊可以將更多時間花在挑戰和獎勵任務上。
- 實現更高的測試覆蓋率,從而實現更好的應用質量。
在本教程中,我們將通過將Espresso整合到Android Studio專案中來了解Espresso。我們將為登入螢幕和a編寫UI測試 RecyclerView
,我們將學習測試意圖。
質量不是一種行為,而是一種習慣。- 巴勃羅畢加索
2. 先決條件
為了能夠學習本教程,您需要:
- 對核心Android API和 Kotlin 的基本瞭解
- Android Studio 3.1.3或更高版本
- Kotlin外掛 1.2.51或更高
您可以 在我們的GitHub倉庫中找到本教程的示例專案(在Kotlin中), 以便您輕鬆跟進。
3. 建立Android Studio專案
啟動Android Studio 3並建立一個名為空活動的新專案 MainActivity
。一定要檢查 包含Kotlin支援 。

建立Android專案對話方塊
4. 設定濃縮咖啡和 AndroidJUnitRunner
建立新專案後,請確保在 build.gradle中 新增Android測試支援庫中的以下依賴 項 (儘管Android Studio已經為我們提供了這些依賴項 )。在本教程中,我們使用最新的Espresso庫版本3.0.2(撰寫本文時)。
android { //... defaultConfig { //... testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } //... } dependencies { //... androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test:rules:1.0.2' }
我們還包括了儀表執行器 AndroidJUnitRunner
:
一個 [Instrumentation](https://developer.android.com/reference/android/app/Instrumentation.html)
運行鍼對Android包(應用程式)JUnit3和JUnit4測試。
請注意, Instrumentation
它只是實現應用程式檢測程式碼的基類。
關閉動畫
Espresso的同步,如果您在測試裝置上允許動畫,則不知道如何等待動畫完成,可能導致某些測試失敗。要關閉測試裝置上的動畫,請轉到 “設定” >“ 開發人員選項” ,然後關閉“繪圖”部分下的所有以下選項:
- 視窗動畫比例
- 過渡動畫比例
- 動畫師持續時間刻度
5. 在Espresso中寫下你的第一個測試
首先,我們開始測試登入螢幕。以下是登入流程的啟動方式:使用者啟動應用程式,顯示的第一個螢幕包含一個 登入 按鈕。當 登入 按鈕被點選,它打開了 LoginActivity
視窗。此螢幕僅包含兩個 EditText
s(使用者名稱和密碼欄位)和一個“ 提交” 按鈕。
這是我們的 MainActivity
佈局:

MainActivity佈局
這是我們的 LoginActivity
佈局:

image
我們現在為我們的 MainActivity
班級寫一個測試。轉到您的 MainActivity
班級,將游標移動到 MainActivity
名稱,然後按 Shift-Control-T 。在彈出選單中 選擇 Create New Test ....

建立新的測試對話方塊
按 OK 按鈕,出現另一個對話方塊。選擇 androidTest 目錄, 再次單擊“ 確定” 按鈕。請注意,因為我們正在編寫一個檢測測試(Android SDK特定測試),所以測試用例位於 androidTest / java 資料夾中。
現在,Android Studio已成功為我們建立了一個測試類。在類名上方,包含此註釋: @RunWith(AndroidJUnit4::class)
。
import android.support.test.runner.AndroidJUnit4 import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class MainActivityTest { }
此註釋表示此類中的所有測試都是特定於Android的測試。
測試活動
因為我們想測試一個Activity,所以我們必須做一些設定。我們需要通知Espresso哪個Activity在執行之前開啟或啟動,並在執行任何測試方法後銷燬。
import android.support.test.rule.ActivityTestRule import android.support.test.runner.AndroidJUnit4 import org.junit.Rule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class MainActivityTest { @Rule @JvmField var activityRule = ActivityTestRule<MainActivity>( MainActivity::class.java ) }
請注意,@Rule註釋意味著這是一個JUnit4測試規則。JUnit4測試規則在每個測試方法之前和之後執行(註釋 @Test)。在我們自己的場景中,我們希望MainActivity在每個測試方法之前啟動 並在之後銷燬它。
我們還包括了@JvmFieldKotlin註釋。這只是指示編譯器不為屬性生成getter和setter,而是將其作為簡單的Java欄位公開。
以下是編寫Espresso測試的三個主要步驟:
查詢要測試的小部件(例如 TextView或Button)。
在該視窗小部件上執行一個或多個操作。
驗證或檢查該視窗小部件現在是否處於某種狀態。
以下型別的註釋可以應用於測試類中使用的方法。
@BeforeClass:這表示應用此註釋的靜態方法必須在類中的所有測試之前執行一次。例如,這可用於建立與資料庫的連線。
@Before:表示必須在類中的每個測試方法之前執行附加此註釋的方法。
@Test:表示附加此註釋的方法應作為測試用例執行。
@After:表示此註釋所附加的方法應在每個測試方法之後執行。
@AfterClass:表示此註釋附加到的方法應該在執行類中的所有測試方法之後執行。在這裡,我們通常會關閉已開啟的資源@BeforeClass。
找一個View使用onView()
在我們的MainActivity佈局檔案中,我們只有一個小部件 - Login按鈕。讓我們測試使用者將找到該按鈕並單擊它的場景。
import android.support.test.espresso.Espresso.onView import android.support.test.espresso.matcher.ViewMatchers.withId // ... @RunWith(AndroidJUnit4::class) class MainActivityTest { // ... @Test @Throws(Exception::class) fun clickLoginButton_opensLoginUi() { onView(withId(R.id.btn_login)) } }
要在Espresso中查詢小部件,我們使用 onView()
靜態方法(而不是 findViewById()
)。我們提供的引數型別 onView()
是a Matcher
。請注意, Matcher
API不是來自Android SDK,而是來自 Hamcrest專案 。Hamcrest的匹配庫位於我們通過Gradle拉出的Espresso庫中。
該 onView(withId(R.id.btn_login))
會返回一個 ViewInteraction
是一個 View
ID為 R.id.btn_login
。在上面的示例中,我們用於 withId()
查詢具有給定id的視窗小部件。我們可以使用的其他檢視匹配器是:
-
withText()
:返回TextView
基於其text屬性值匹配的匹配器。 -
withHint()
:返回TextView
基於其提示屬性值匹配的匹配器。 -
withTagKey()
:返回View
基於標記鍵匹配的匹配器。 -
withTagValue()
:返回View
基於標記屬性值匹配s 的匹配器。
首先,讓我們測試按鈕是否實際顯示在螢幕上。
onView(withId(R.id.btn_login)).check(matches(isDisplayed()))
在這裡,我們只是確認具有給定id( R.id.btn_login
)的按鈕是否對使用者可見,因此我們使用該 check()
方法來確認底層 View
是否具有某種狀態 - 在我們的情況下,如果它是可見的。
該 matches()
靜態方法返回通用 ViewAssertion
斷言的檢視在檢視層次結構中存在,並且由給定的檢視匹配器匹配。通過呼叫返回給定的檢視匹配器 isDisplayed()
。如方法名稱所示, isDisplayed()
是一個匹配器, View
它將當前顯示在螢幕上的s與使用者匹配。例如,如果我們想檢查按鈕是否已啟用,我們只需傳遞 isEnabled()
給 matches()
。
我們可以傳遞給 matches()
方法的其他流行檢視匹配器是:
-
hasFocus()
:返回匹配View
當前具有焦點的s 的匹配器 。 -
isChecked()
:返回一個匹配器,當且僅當檢視是CompoundButton
(或子型別)且處於選中狀態時才接受。這種方法的反面是isNotChecked()
。 -
isSelected()
:返回匹配View
所選s 的匹配器。
要執行測試,可以單擊方法旁邊的綠色三角形或類名。單擊類名旁邊的綠色三角形將執行該類中的所有測試方法,而方法旁邊的那個將僅針對該方法執行測試。

MainActivityTest類
萬歲!我們的測試通過!

Android Studio測試結果工具欄
在檢視上執行操作
在 ViewInteraction
通過呼叫返回的物件上 onView()
,我們可以模擬使用者可以在視窗小部件上執行的操作。例如,我們可以通過簡單地呼叫類中的 click()
靜態方法 來模擬單擊操作 ViewActions
。這將為 ViewAction
我們返回一個 物件。
文件說這 ViewAction
是:
負責在給定的View元素上執行互動。
@Test fun clickLoginButton_opensLoginUi() { // ... onView(withId(R.id.btn_login)).perform(click()) }
我們通過第一次呼叫執行點選事件 perform()
。此方法對當前檢視匹配器選擇的檢視執行給定操作。請注意,我們可以傳遞單個操作或操作列表(按順序執行)。在這裡,我們給了它 click()
。其他可能的行動是:
-
typeText()
模仿鍵入文字EditText
。 -
clearText()
模擬清算文字EditText
。 -
doubleClick()
模擬雙擊aView
。 -
longClick()
模仿長按aView
。 -
scrollTo()
模擬滾動ScrollView
到View
可見的特定內容。 -
swipeLeft()
模擬在a的垂直中心從右向左滑動View
。
在 ViewActions
課堂上 可以找到更多的模擬。
使用檢視斷言進行驗證
讓我們完成測試,驗證 LoginActivity
每次單擊“ 登入” 按鈕時都會顯示螢幕。雖然我們已經看過如何使用 check()
a ViewInteraction
,讓我們再次使用它,將它傳遞給另一個 ViewAssertion
。
@Test fun clickLoginButton_opensLoginUi() { // ... onView(withId(R.id.tv_login)).check(matches(isDisplayed())) }
在 LoginActivity
佈局檔案中,除了 EditText
s和a之外 Button
,我們還有一個 TextView
帶ID R.id.tv_login
。因此,我們只需進行檢查以確認 TextView
使用者是否可見。
現在你可以再次執行測試!

Android Studio測試結果工具欄
如果您正確執行了所有步驟,則測試應成功通過。
這是執行測試過程中發生的事情:
-
推出了
MainActivity
使用該activityRule
領域。 -
驗證 登入 按鈕(
R.id.btn_login
)是否isDisplayed()
對使用者可見()。 -
模擬
click()
該按鈕上的單擊操作()。 -
驗證,如果
LoginActivity
通過檢查是否顯示給使用者TextView
id為R.id.tv_login
在LoginActivity
可見。
您始終可以參考 Espresso備忘單 以檢視不同的檢視匹配器,檢視操作和檢視可用的斷言。
6. 測試 LoginActivity
螢幕
這是我們的 LoginActivity.kt :
import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.widget.Button import android.widget.EditText import android.widget.TextView class LoginActivity : AppCompatActivity() { private lateinit var usernameEditText: EditText private lateinit var loginTitleTextView: TextView private lateinit var passwordEditText: EditText private lateinit var submitButton: Button override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) usernameEditText = findViewById(R.id.et_username) passwordEditText = findViewById(R.id.et_password) submitButton = findViewById(R.id.btn_submit) loginTitleTextView = findViewById(R.id.tv_login) submitButton.setOnClickListener { if (usernameEditText.text.toString() == "chike" && passwordEditText.text.toString() == "password") { loginTitleTextView.text = "Success" } else { loginTitleTextView.text = "Failure" } } } }
在上面的程式碼中,如果輸入的使用者名稱為“chike”且密碼為“password”,則登入成功。對於任何其他輸入,這是一個失敗。我們現在為此寫一個Espresso測試!
轉到 LoginActivity.kt,將游標移動到 LoginActivity 名稱,然後按 Shift-Control-T。 在彈出選單中選擇 Create New Test .... 按照與MainActivity.kt相同的過程 ,單擊“ 確定”按鈕。
import android.support.test.espresso.Espresso import android.support.test.espresso.Espresso.onView import android.support.test.espresso.action.ViewActions import android.support.test.espresso.assertion.ViewAssertions.matches import android.support.test.espresso.matcher.ViewMatchers.withId import android.support.test.espresso.matcher.ViewMatchers.withText import android.support.test.rule.ActivityTestRule import android.support.test.runner.AndroidJUnit4 import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class LoginActivityTest { @Rule @JvmField var activityRule = ActivityTestRule<LoginActivity>( LoginActivity::class.java ) private val username = "chike" private val password = "password" @Test fun clickLoginButton_opensLoginUi() { onView(withId(R.id.et_username)).perform(ViewActions.typeText(username)) onView(withId(R.id.et_password)).perform(ViewActions.typeText(password)) onView(withId(R.id.btn_submit)).perform(ViewActions.scrollTo(), ViewActions.click()) Espresso.onView(withId(R.id.tv_login)) .check(matches(withText("Success"))) } }
這個測試類與我們的測試類非常相似。如果我們執行測試,我們的 LoginActivity
螢幕就會開啟。使用者名稱和密碼分別輸入到 R.id.et_username
和 R.id.et_password
欄位中。接下來,Espresso將單擊“ 提交” 按鈕( R.id.btn_submit
)。它將一直等到 View
帶有id的 R.id.tv_login
文字讀取 成功 。
7. 測試a RecyclerView
RecyclerViewActions
是暴露一組API來操作的類 RecyclerView
。 RecyclerViewActions
是工件內部單獨工件的一部分 espresso-contrib
,也應該新增到 build.gradle中 :
androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.0.2'
請注意,此工件還包含用於UI測試導航抽屜的API DrawerActions
和 DrawerMatchers
。
@RunWith(AndroidJUnit4::class) class MyListActivityTest { // ... @Test fun clickItem() { onView(withId(R.id.rv)) .perform(RecyclerViewActions .actionOnItemAtPosition<RandomAdapter.ViewHolder>(0, ViewActions.click())) } }
要點選a中任意位置的專案RecyclerView,我們會呼叫actionOnItemAtPosition()。我們必須給它一種物品。在我們的例子中,該專案是ViewHolder我們內部的類RandomAdapter。該方法還包含兩個引數; 第一個是位置,第二個是動作(ViewActions.click())。
其他RecyclerViewActions可以執行的是:
actionOnHolderItem():ViewAction對匹配的檢視執行a viewHolderMatcher。這允許我們通過包含在內部ViewHolder而不是位置中的內容來匹配它。
scrollToPosition():返回ViewAction滾動RecyclerView到某個位置的a。
接下來(一旦“添加註釋螢幕”開啟),我們將輸入註釋文字並儲存註釋。我們無需等待新螢幕開啟 - Espresso將自動為我們執行此操作。它等待,直到R.id.add_note_title 找到具有id的檢視 。
8. 測試意圖
Espresso使用另一個名為espresso-intents測試意圖的工件 。這個工件只是Espresso的另一個擴充套件,專注於Intents的驗證和模擬。我們來看一個例子。
首先,我們必須將庫拉espresso-intents入我們的專案。
androidTestImplementation 'com.android.support.test.espresso:espresso-intents:3.0.2'
import android.support.test.espresso.intent.rule.IntentsTestRule import android.support.test.runner.AndroidJUnit4 import org.junit.Rule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class PickContactActivityTest { @Rule @JvmField var intentRule = IntentsTestRule<PickContactActivity>( PickContactActivity::class.java ) }
IntentsTestRule延伸ActivityTestRule,所以他們都有類似的行為。以下是該文件所說的內容:
此類是其擴充套件ActivityTestRule,在每次測試之前初始化Espresso-Intents, Test並在每次測試執行後釋放Espresso-Intents。每次測試後活動都將終止,此規則的使用方式與之相同 ActivityTestRule。
主要區別在於它具有額外的功能,可用於測試startActivity()以及startActivityForResult()模擬和存根。
我們現在將測試一個場景,使用者將點選R.id.btn_select_contact螢幕上的按鈕()從手機的聯絡人列表中選擇一個聯絡人。
// ... @Test fun stubPick() { var result = Instrumentation.ActivityResult(Activity.RESULT_OK, Intent(null, ContactsContract.Contacts.CONTENT_URI)) intending(hasAction(Intent.ACTION_PICK)).respondWith(result) onView(withId(R.id.btn_select_contact)).perform(click()) intended(allOf( toPackage("com.google.android.contacts"), hasAction(Intent.ACTION_PICK), hasData(ContactsContract.Contacts.CONTENT_URI))) //... }
這裡我們使用intending()從espresso-intents庫建立存根為我們的一個模擬響應ACTION_PICK請求。以下是 當用戶單擊帶有id 以選擇聯絡人的按鈕時 PickContactActivity.kt中發生的情況 R.id.btn_select_contact。
fun pickContact(v: View) val i = Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI) startActivityForResult(i, PICK_REQUEST) }
intending()
接受 Matcher
與應提供存根響應的意圖匹配。換句話說, Matcher
哪些標識要求您對存根感興趣。在我們自己的情況下,我們使用 hasAction()
(輔助方法 IntentMatchers
)來查詢我們的 ACTION_PICK
請求。然後我們呼叫 respondWith()
,設定結果 onActivityResult()
。在我們的例子中,結果是 Activity.RESULT_OK
,模擬使用者從列表中選擇聯絡人。
然後我們模擬單擊選擇聯絡人按鈕,該按鈕呼叫 startActivityForResult()
。請注意,我們的存根傳送了模擬響應 onActivityResult()
。
最後,我們使用 intended()
輔助方法簡單地驗證對呼叫 startActivity()
和 startActivityForResult()
使用正確資訊的呼叫。
結論
在本教程中,您學習瞭如何在Android Studio專案中輕鬆使用Espresso測試框架來自動化測試工作流程。
我強烈建議您檢視 官方文件, 以瞭解有關使用Espresso編寫UI測試的更多資訊。
想學習更多Android知識,或者獲取相關資料請加入Android技術開發交流2群:935654177。本群可免費獲取Gradle,RxJava,小程式,Hybrid,移動架構,NDK,React Native,效能優化等技術教程!