前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于Kotlin DSL的Espresso和UIAutomator的融合客户端自动化

基于Kotlin DSL的Espresso和UIAutomator的融合客户端自动化

作者头像
用户5521279
发布2020-05-08 16:04:21
2.2K0
发布2020-05-08 16:04:21
举报
文章被收录于专栏:搜狗测试搜狗测试

前言

最近小编在探索端对端测试相关的topic,在Android端的自动化测试上,可供我们选择的库并不是很多,而其中小编使用最多的两个库分别是Espresso和UIAutomator。尽管两者都可以达成我们的最终目的,但实现的过程还是有所区别的:

  • Espresso是用于Android测试的白盒解决方案,以沙盒化的形式测试当前应用程序。
  • UIAutomator是一个常用的Android端黑盒测试解决方案,它在设备维度上运行,故而提供了应用程序及程序之外的操作及测试方法。

为了进行充分的端对端测试,我们便需要利用好两者的优势,以实现在合适的地方对程序进行合适的自动化测试。然而,如果我们想设计一套自顶向下,设备、接口、代码层级均可自动化执行且有一定校验的框架或系统时,就会发现这两个完全不同语法的库融合一起后,可读性和可维护性几乎等于零。

因此,本文提出了一种基于Kotlin DSL写法的Espresso和UIAutomator融合方案,解决在不同库下的客户端自动化框架、用例的可读性、可维护性问题。

Espresso

在Espresso中,我们一般会处理三种类型的对象:匹配器、ViewAction和ViewAssertions。按照语法,结合这三种对象,我们可以实现如以下click这一类的操作,如下所示:

代码语言:javascript
复制
Espresso.onView(Matchers.withId(R.id.activityLoginBtnSubmit)).perform(ViewActions.click())

UIAutomator

相较于Espresso,黑盒的UIAutomator使用要复杂得多。比如我们要查询UI层次结构中的特定对象,就需要设定好一些先决条件:

1、从InstrumentationRegistry获取上下文

2、将资源ID转换为资源名称

3、创建UIDevice对象,它在UIAutomator中属于God对象,即每次调用都会需要用到UIDevice实例

4、定义UISelector,UISelector的作用是可以通过资源ID查询想要的UI组件,但是UIAutomator中没有这种方法,所以我们需要用到步骤2中的资源名称,通过资源名称查询UI组件,进而实现UISelector

5、通过使用UIDevice和UISelector实例化UIObject。实例化完成后,我们就可以和UIComponent进行交互了

代码语言:javascript
复制
val instrumentation = InstrumentationRegistry.getInstrumentation()
val uiDevice = UiDevice.getInstance(instrumentation)
val appContext = InstrumentationRegistry.getInstrumentation().targetContext


val loginButtonSelector = UiSelector().resourceId(appContext.resources.getResourceName(
        R.id.activityLoginBtnSubmit
    )
)


val loginButton = uiDevice.findObject(loginButtonSelector)
loginButton.click()

现在,若我们将Espresso和UIAutomator结合起来,通过UI组件的动作来检查层次结构深处的某些View,那么就需要同时使用Espresso对象和UIAutomator对象(其中还包含了UIAutomator资源初始化等工作)。假设这一条case的编写、改进、维护成本在一个季度内评估为30min,那么1000条case维护起来的工作量可想而知。

Kotlin DSL带来的新思路

还好小编在调研阶段就意识到了这个问题,因此决定使用Kotlin的功能编写DSL以统一两个库的语法。DSL(domain specific language),即领域专用语言:专门解决某一特定问题的计算机语言,比如大家耳熟能详的 SQL 和正则表达式就属于DSL。而在Kotlin中,DSL 则是对 Kotlin 所有语法糖的一个大融合,它的代码结构通常是链式调用、lambda 嵌套,并且接近于日常使用的英语句子,我们可以愉悦的使用 DSL 风格的 API,同时,由于DSL语法更合逻辑且更易于掌握,因此历史代码可以更轻松地移交给其他同事。

代码语言:javascript
复制
click on button(R.id.activityLoginBtnLogin)

上面是基于Kotlin DSL实现的一个例子,是不是很清晰易懂呢?以下是融合UIAutomator和Espresso语法的一个实例:

Espresso语法:

代码语言:javascript
复制
class MainActivityTest {
    @Test
    fun shouldLoginDemoUser(){
        onView(withId(R.id.activityLoginEditTextUsername)).perform(typeText("dummyUsername"))
        onView(withId(R.id.activityLoginEditTextPassword)).perform(typeText("dummyPassword"))
        onView(withId(R.id.activityLoginBtnLogin)).perform(click())
        
        Intents.intended(IntentMatchers.hasComponent(MainActivity::class.java.name))
    }
}

UIAutomator语法:

代码语言:javascript
复制
class MainActivityTest {
    @Test
    fun shouldLoginDemoUser(){
        val instrumentation = InstrumentationRegistry.getInstrumentation()
        val uiDevice = UiDevice.getInstance(instrumentation)
        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
        
        val usernameSelector = UiSelector().resourceId(appContext.resources.getResourceName(
                R.id.activityLoginEditTextUsername
            )
        )
        val usernameTextField = uiDevice.findObject(usernameSelector)
        usernameTextField.text = "dummyUsername"
        val passwordSelector = UiSelector().resourceId(appContext.resources.getResourceName(
                R.id.activityLoginEditTextPassword
            )
        )
        val passwordTextField = uiDevice.findObject(passwordSelector)
        passwordTextField.text = "dummyPassword"
        val loginButtonSelector = UiSelector().resourceId(appContext.resources.getResourceName(
                R.id.activityLoginBtnLogin
            )
        )
        val loginButton = uiDevice.findObject(loginButtonSelector)
        loginButton.click()
        
        Intents.intended(IntentMatchers.hasComponent(MainActivity::class.java.name))
    }
}

融合语法:

代码语言:javascript
复制
class MainActivityTest {
    @Test
    fun shouldLoginDemoUser(){
        typeText("dummyUsername") into text(R.id.activityLoginEditTextUsername)
        typeText("dummyPassword") into text(R.id.activityLoginEditTextPassword)
        click on button(R.id.activityLoginBtnLogin)
        
        MainActivity::class verifyThat { itIsDisplayed() }
    }
}

后续优化思考

  • 在后续项目发展过程中,我们肯定会在UI组件上使用越来越多的操作和断言,因此DSL的量级会随着时间不断增长。在项目成熟度发展到某一节点时,维护功能集合会变得很困难,因此我们必须对其进行整理集合,使其独立于我们正在测试的程序。当前Github上已有Android Test KTX可供大家使用。
  • 尽管UIAutomator对我们来说效果很好,但这也是造成大多数麻烦的原因。我们如果要自行更新或增加Kotlin DSL库的内容,可以将UIAutomator和Espresso相同的操作通过Espresso实现,并集合在库中。
  • 可以考虑将DSL结合Kotlin的Robot模式使用,进一步提升测试case的可读性:
代码语言:javascript
复制
@Test
fun shouldLoginToTheApp() {
  withLoginRobot {
    login("john_smith", "p@$$w0rd")
  } andThen {
    acceptTermsOfUse()
  } andThenWithPermissionRobot {
    acceptAllPermissions()
  } andVerifyThat {
    userIsLoggedIn()
  }
}
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 搜狗测试 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档