在这篇文章中,您将学习如何使用Espresso测试框架编写UI测试并自动化测试工做流程,而不是使用繁琐且极易出错的手动过程。html
Espresso是用于在Android中编写UI测试的测试框架。根据官方文档,您能够:java
使用Espresso编写简洁,美观,可靠的Android UI测试。android
手动测试的一个问题是执行起来既费时又乏味。例如,要在Android应用中测试登陆屏幕(手动),您必须执行如下操做:git
usernameEditText
和passwordEditText
可见。而不是花费全部这些时间手动测试咱们的应用程序,最好花更多时间编写代码,使咱们的应用程序脱颖而出!并且,即便手动测试繁琐且速度很慢,它仍然容易出错,您可能会错过一些极端状况。github
自动化测试的一些优势包括:web
在本教程中,咱们将经过将Espresso集成到Android Studio项目中来了解Espresso。咱们将为登陆屏幕和a编写UI测试RecyclerView
,咱们将学习测试意图。数据库
质量不是一种行为,而是一种习惯。- 巴勃罗毕加索小程序
为了可以学习本教程,您须要:性能优化
您能够在咱们的GitHub仓库中找到本教程的示例项目(在Kotlin中),以便您轻松跟进。架构
启动Android Studio 3并建立一个名为空活动的新项目 MainActivity
。必定要检查 包含Kotlin支持。
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的同步,若是您在测试设备上容许动画,则不知道如何等待动画完成,可能致使某些测试失败。要关闭测试设备上的动画,请转到**“设置”** >“ 开发人员选项”,而后关闭“绘图”部分下的全部如下选项:
首先,咱们开始测试登陆屏幕。如下是登陆流程的启动方式:用户启动应用程序,显示的第一个屏幕包含一个登陆按钮。当登陆按钮被点击,它打开了LoginActivity
窗口。此屏幕仅包含两个EditText
s(用户名和密码字段)和一个“ **提交”**按钮。
这是咱们的MainActivity
布局:
这是咱们的LoginActivity
布局:
咱们如今为咱们的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 的匹配器。要运行测试,能够单击方法旁边的绿色三角形或类名。单击类名旁边的绿色三角形将运行该类中的全部测试方法,而方法旁边的那个将仅针对该方法运行测试。
万岁!咱们的测试经过!
在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()
模拟双击a View
。longClick()
模仿长按a View
。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
用户是否可见。
如今你能够再次运行测试!
若是您正确执行了全部步骤,则测试应成功经过。
这是执行测试过程当中发生的事情:
推出了 MainActivity
使用该activityRule
领域。
验证登陆按钮(R.id.btn_login
)是否isDisplayed()
对用户可见()。
模拟click()
该按钮上的单击操做()。
验证,若是LoginActivity
经过检查是否显示给用户TextView
id为R.id.tv_login
在LoginActivity
可见。
您始终能够参考 Espresso备忘单 以查看不一样的视图匹配器,查看操做和查看可用的断言。
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
文本读取 成功。
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,性能优化等技术教程!