使用Espresso测试Android用户界面

本文介绍了如何使用Espresso测试框架在Android中编写UI测试,以自动化测试流程,提高效率和测试覆盖率。Espresso允许开发者执行相同的测试用例,避免手动测试的耗时和错误。教程涵盖了设置Espresso、关闭动画、编写测试用例、测试屏幕和意图等步骤,旨在帮助开发者更好地理解和应用Espresso进行Android应用的UI测试。
摘要由CSDN通过智能技术生成

在本文中,您将学习如何使用Espresso测试框架编写UI测试以及如何自动化测试工作流程,而不是使用乏味且容易出错的手动过程。

Espresso是用于在Android中编写UI测试的测试框架。 根据官方文档,您可以:

使用Espresso编写简洁,美观且可靠的Android UI测试。

1.为什么要使用意式浓缩咖啡?

手动测试的问题之一是执行起来很耗时且乏味。 例如,要在Android应用中测试登录屏幕(手动),您必须执行以下操作:

  1. 启动应用程序。
  2. 导航到登录屏幕。
  3. 确认usernameEditTextpasswordEditText是否可见。
  4. 在各自的字段中键入用户名和密码。
  5. 确认登录按钮是否也可见,然后单击该登录按钮。
  6. 检查该登录成功或失败时是否显示正确的视图。

与其花所有这些时间来手动测试我们的应用程序,不如花更多时间编写使我们的应用程序与众不同的代码! 而且,即使手动测试很繁琐且相当缓慢,它仍然容易出错,并且您可能会错过一些极端情况。

自动化测试的一些优点包括:

  • 自动化测试每次执行时都执行完全相同的测试用例。
  • 在将问题发送给质量检查团队之前,开发人员可以Swift发现问题。
  • 与进行手动测试不同,它可以节省大量时间。 通过节省时间,软件工程师和质量检查团队可以将更多的时间花费在具有挑战性和奖励性的任务上。
  • 实现了更高的测试覆盖率,从而带来了更高质量的应用程序。

在本教程中,我们将通过将Espresso集成到Android Studio项目中来对其进行学习。 我们将为登录屏幕和RecyclerView编写UI测试,并了解测试意图。

质量不是一种行为,而是一种习惯。 - 巴勃罗毕加索

2.先决条件

要遵循本教程,您需要:

可以在我们的GitHub存储库中找到本教程的示例项目(在Kotlin中),因此您可以轻松地继续学习。

3.创建一个Android Studio项目

启动您的Android Studio 3并创建一个名为MainActivity的空活动的新项目。 确保选中包括Kotlin支持

创建Android项目对话框

4.设置Espresso和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

针对Android包(应用程序)运行JUnit3和JUnit4的Instrumentation进行测试。

请注意, Instrumentation只是用于实现应用程序工具代码的基类。

关闭动画

Espresso的同步(不知道如何等待动画完成)会导致某些测试失败-如果您允许在测试设备上播放动画。 要关闭测试设备上的动画,请转到“设置” >“ 开发人员选项” ,然后关闭“绘图”部分下的以下所有选项:

  • 窗口动画比例
  • 过渡动画比例
  • 动画师持续时间量表

5.在Espresso中编写您的第一个测试

首先,我们开始测试登录屏幕。 登录流程的开始过程如下:用户启动应用程序,并且显示的第一个屏幕包含一个“ 登录”按钮。 单击该登录按钮后,它将打开LoginActivity屏幕。 该屏幕仅包含两个EditText (用户名和密码字段)和一个Submit按钮。

这是我们的MainActivity布局:

MainActivity布局

这是我们的LoginActivity布局的样子:


现在让我们为MainActivity类编写一个测试。 转到您的MainActivity类,将光标移至MainActivity名称,然后按Shift-Control-T 。 在弹出菜单中选择“ 创建新测试... ”。

创建新的测试对话框

按下确定按钮,另一个对话框出现。 选择androidTest目录,然后再次单击OK按钮。 请注意,由于我们正在编写检测测试(特定于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的测试。

测试活动

因为我们要测试一个活动,所以我们需要做一些设置。 我们需要在执行之前通知Espresso要打开或启动哪个活动,并在执行任何测试方法后销毁该活动。

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然后在其后销毁它。

我们还包括@JvmField Kotlin批注。 这只是指示编译器不要为该属性生成getter和setter,而是将其公开为简单的Java字段。

这是编写Espresso测试的三个主要步骤:

  • 查找您要测试的小部件(例如TextViewButton )。
  • 在该小部件上执行一项或多项操作。
  • 验证或检查该小部件现在是否处于特定状态。

下列类型的注释可以应用于测试类内部使用的方法。

  • @BeforeClass :这指示此注释所应用的静态方法必须在类中的所有测试之前执行一次。 例如,这可用于建立与数据库的连接。
  • @Before :指示此注释所附加的方法必须在类中的每个测试方法之前执行。
  • @Test :指示此注释附加到的方法应作为测试用例运行。
  • @After :指示此注释所附加的方法应在每个测试方法之后运行。
  • @AfterClass :指示此注释所附加的方法应在类中的所有测试方法运行之后运行。 在这里,我们通常会关闭@BeforeClass中打开的资源。

使用onView()查找View

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()的参数类型是Matcher 。 请注意, Matcher API不是来自Android SDK,而是来自Hamcrest项目 。 Hamcrest的匹配库位于我们通过Gradle拉出的Espresso库中。

onView(withId(R.id.btn_login))将返回一个ViewInteraction ,它用于ID为R.id.btn_loginView 。 在上面的示例中,我们使用withId()查找具有给定id的小部件。 我们可以使用的其他视图匹配器是:

  • withText() :返回一个匹配器,该匹配器根据其Text属性值匹配TextView
  • withHint() :返回一个根据其提示属性值匹配TextView的匹配器。
  • withTagKey() :返回一个基于标记键匹配View的匹配器。
  • withTagValue() :返回一个基于标记属性值匹配View的匹配器。

首先,让我们测试一下按钮是否真正显示在屏幕上。

onView(withId(R.id.btn_login)).check(matches(isDisplayed()))

在这里,我们只是在确认用户是否可以看到具有给定id( R.id.btn_login )的按钮,因此我们使用check()方法来确认基础View是否具有特定状态-在本例中,如果它是可见的。

ViewAssertion matches()静态方法返回一个通用的ViewAssertion ,它断言一个视图存在于视图层次结构中,并由给定的视图匹配器进行匹配。 给定的视图匹配器通过调用isDisplayed()返回。 如方法名称所建议, isDisplayed()是一个匹配器,用于将当前在屏幕上显示的View匹配给用户。 例如,如果我们要检查按钮是否已启用,则只需将isEnabled()传递给matches()

我们可以传递给matches()方法的其他流行的视图匹配器是:

  • hasFocus() :返回与当前具有焦点的View匹配的匹配器。
  • isChecked() :返回一个匹配器,该匹配器仅在视图为CompoundButton (或其子类型)并且处于选中状态时接受。 此方法的反义是isNotChecked()
  • isSelected() :返回匹配选定View的匹配器。

要运行测试,可以单击方法或类名旁边的绿色三角形。 单击类名旁边的绿色三角形将运行该类中的所有测试方法,而一个方法旁边的一个仅将针对该方法运行测试。

MainActivityTest类

万岁! 我们的测试通过了!

Android Studio测试结果工具栏

在视图上执行操作

在通过调用onView()返回的ViewInteraction对象上,我们可以模拟用户可以在小部件上执行的动作。 例如,我们可以通过简单地调用ViewActions类内部的click()静态方法来模拟click动作。 这将为我们返回一个ViewAction对象。

该文档说ViewAction是:

负责在给定的View元素上执行交互。
@Test
fun clickLoginButton_opensLoginUi() {
    // ...

    onView(withId(R.id.btn_login)).perform(click())
}

我们通过首先调用perform()执行click事件。 此方法在当前视图匹配器选择的视图上执行给定操作。 请注意,我们可以将单个动作或动作列表(按顺序执行)传递给它。 在这里,我们给了它click() 。 其他可能的操作是:

  • typeText()模仿将文本键入到EditText
  • clearText()模拟在EditText清除文本。
  • doubleClick()模拟双击View
  • longClick()模仿长按View
  • scrollTo()模拟将ScrollView到可见的特定View
  • swipeLeft()模拟跨View的垂直中心从右向左滑动。

ViewActions类中可以找到更多模拟。

使用视图断言进行验证

让我们完成测试,以验证每当单击“ 登录”按钮时是否显示LoginActivity屏幕。 尽管我们已经看到了如何在ViewInteraction上使用check() ,但让我们再次使用它,并将其传递给另一个ViewAssertion

@Test
fun clickLoginButton_opensLoginUi() {
    // ... 
    
    onView(withId(R.id.tv_login)).check(matches(isDisplayed()))
}

LoginActivity布局文件中,除了EditText和一个Button ,我们还有一个ID为R.id.tv_loginTextView 。 因此,我们只需进行检查以确认TextView对用户可见。

现在您可以再次运行测试!

Android Studio测试结果工具栏

如果正确执行所有步骤,则测试应成功通过。

这是执行测试过程中发生的事情:

  1. 使用activityRule字段启动MainActivity
  2. 验证用户是否可以看到登录按钮( R.id.btn_login )( isDisplayed() )。
  3. 模拟该按钮上的点击动作( click() )。
  4. 验证如果LoginActivity通过检查是否显示给用户TextView id为R.id.tv_loginLoginActivity是可见的。

您始终可以参考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 。 在弹出菜单中选择“ 创建新测试... ”。 遵循与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_usernameR.id.et_password字段中。 接下来,Espresso将单击Submit按钮( R.id.btn_submit )。 它将等待,直到找到ID为R.id.tv_loginView且文本读取为Success为止。

7.测试一个RecyclerView

RecyclerViewActions是公开用于在RecyclerView上运行的一组API的类。 RecyclerViewActionsespresso-contrib构件内部单独构件的一部分,该构件也应添加到build.gradle中

androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.0.2'

请注意,此工件还包含用于通过DrawerActionsDrawerMatchers测试导航抽屉的UI的API。

@RunWith(AndroidJUnit4::class)
class MyListActivityTest {
    // ... 
    @Test
    fun clickItem() {
        onView(withId(R.id.rv))
                .perform(RecyclerViewActions
                        .actionOnItemAtPosition<RandomAdapter.ViewHolder>(0, ViewActions.click()))

    }
}

要单击RecyclerView中任意位置的项目,我们调用actionOnItemAtPosition() 。 我们必须给它一种物品。 在我们的例子中,该项是我们RandomAdapter内的ViewHolder类。 此方法还具有两个参数: 第一个是位置,第二个是动作( ViewActions.click() )。

可以执行的其他RecyclerViewActions是:

  • actionOnHolderItem() :对与viewHolderMatcher匹配的视图执行ViewAction 。 这使我们可以根据ViewHolder包含的ViewHolder而不是位置进行匹配。
  • scrollToPosition() :返回一个ViewAction ,它将RecyclerView滚动到某个位置。

接下来(打开“添加注释屏幕”),我们将输入注释文本并保存注释。 我们无需等待新屏幕打开,Espresso将自动为我们完成此操作。 等待直到找到ID为R.id.add_note_title的视图。

8.测试意图

Espresso利用另一个名为espresso-intents工件来测试意图。 该工件只是Espresso的另一个扩展,它专注于Intent的验证和模拟。 让我们来看一个例子。

首先,我们必须将意式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的扩展,它在用Test注释的每个测试之前初始化Espresso-Intents,并在每次测试运行后释放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)))
                
    //... 
}

在这里,我们使用espresso-intents库中的intending()来为我们的ACTION_PICK请求设置一个带有模拟响应的存根。 这是当用户单击ID为R.id.btn_select_contact的按钮来选择联系人时, PickContactActivity.kt中发生的情况。

fun pickContact(v: View) 
    val i = Intent(Intent.ACTION_PICK,
            ContactsContract.Contacts.CONTENT_URI)
    startActivityForResult(i, PICK_REQUEST)
}

intending()接受一个Matcher ,该匹配Matcher应匹配应为其提供存根响应的意图。 换句话说, Matcher识别您对存根感兴趣的请求。 在我们自己的情况下,我们利用hasAction()在一个辅助方法IntentMatchers )找到我们ACTION_PICK请求。 然后,我们调用respondWith() ,它为onActivityResult()设置结果。 在我们的例子中,结果为Activity.RESULT_OK ,模拟用户从列表中选择一个联系人。

然后,我们模拟单击选择联系人按钮,该按钮将调用startActivityForResult() 。 请注意,我们的存根将模拟响应发送到onActivityResult()

最后,我们使用intended()帮助方法来简单地验证对startActivity()startActivityForResult()的调用是使用正确的信息进行的。

结论

在本教程中,您学习了如何在Android Studio项目中轻松使用Espresso测试框架来自动化测试工作流程。

我强烈建议您查阅官方文档,以了解有关使用Espresso编写UI测试的更多信息。

翻译自: https://code.tutsplus.com/tutorials/testing-android-user-interfaces-with-espresso--cms-31687

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值