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

2020/04/23 08:00
阅读数 193

前言

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

  • Espresso是用于Android测试的白盒解决方案,以沙盒化的形式测试当前应用程序。

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


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


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


Espresso

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

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进行交互了

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语法更合逻辑且更易于掌握,因此历史代码可以更轻松地移交给其他同事。

click on button(R.id.activityLoginBtnLogin)

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


Espresso语法:

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语法:

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))    }}


融合语法:

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的可读性:

@Testfun shouldLoginToTheApp() {  withLoginRobot {    login("john_smith", "p@$$w0rd")  } andThen {    acceptTermsOfUse()  } andThenWithPermissionRobot {    acceptAllPermissions()  } andVerifyThat {    userIsLoggedIn()  }}






搜狗测试微信号:Qa_xiaoming

搜狗测试QQ粉丝群:459645679

本文分享自微信公众号 - 搜狗测试(SogouQA)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部