架构基础 -- POM设计模式
Page Object Model Design Pattern (页面对象模型设计模式) , 这种模型很大程度的 增强了方法的复用 , 使测试代码的可读性和层次感也得到了明显的优化.
本系列项目位于github:
https://github.com/rawqing/EspressoBlackBoxDemo
POM 模式的基本理论这里就不讲了 , 大家课参考 Design Patterns in Test Automation I
虽然不是说的Espresso
,但基本原理都是一样的 .
一. 在此之前
虽然 espresso 的api已经封装得很好了, 但为了更好的使用 , 我们还是需要对其进行二次封装.
- 获取
ActivityTestRule
黑盒测试获取ActivityTestRule
只有通过反射的方式 , 但也不能每一个测试类都 使用Class.forName(className)
. 并且kotlin
对泛型的要求比较严格 , so ,这里 我们使用java
进行反射封装:public static ActivityTestRule getActivityTestRule(String activityName) { return new ActivityTestRule(getFixedClass(activityName)); } /** * 返回给定全类名的Class对象 (已处理异常) * @param className * @return */ public static Class<?> getFixedClass(String className){ try { return Class.forName(className); } catch (ClassNotFoundException e) { e.printStackTrace(); } return null; }
- 封装
ViewElement
这是对ViewInteraction
的二次封装 (这里只示例简单封装)package yq.test.espressoblackboxdemo.expansions import android.support.test.espresso.Espresso.onView import android.support.test.espresso.ViewAssertion import android.support.test.espresso.ViewInteraction import android.support.test.espresso.action.ViewActions import android.support.test.espresso.matcher.RootMatchers import android.support.test.espresso.matcher.ViewMatchers.* import android.support.test.rule.ActivityTestRule import android.view.View import org.hamcrest.Matcher import org.hamcrest.Matchers import org.hamcrest.Matchers.allOf class ViewElement( private var id: String? = null, private var text: String? = null, private var isToast: Boolean = false, private var atr: ActivityTestRule<*>? = null, private var children: MutableList<ViewElement> = ArrayList() ) { private var interaction : ViewInteraction? = null /** View 查找部分 **/ /** * 将 ViewElement 中的成员属性转换成 Matcher<View> * 这是关键的方法 */ private fun way(): Matcher<View> { val list = ArrayList<Matcher<View>>() id?.let { list.add(withResourceName(it)) } text?.let { list.add(withText(it)) } children.let { it.forEach {el-> list.add(withChild(el.way())) } } return allOf(list) } /** * 创建 ViewInteraction 实例 * 将区分 toast 和 普通 View */ private fun createInteraction(): ViewInteraction? { if (interaction == null) interaction = if (isToast) { onView(this.way()) .inRoot(RootMatchers.withDecorView(Matchers.not<View>(Matchers.`is`<View>(atr!!.activity.window.decorView)))) } else { onView(this.way()) } return interaction } /** 二次封装部分 **/ fun replaceText(text: String): ViewElement { this.createInteraction()?.perform(ViewActions.replaceText(text)) return this } fun click(): ViewElement { this.createInteraction()?.perform(ViewActions.click()) return this } fun check(viewAssertion: ViewAssertion): ViewElement { this.createInteraction()?.check(viewAssertion) return this } /** setter **/ fun setId(id: String): ViewElement { this.id = id return this } fun setText(text: String): ViewElement { this.text = text return this } fun setChildren(vararg children: ViewElement): ViewElement { this.children = children.toMutableList() return this } fun beToast(isToast: Boolean): ViewElement { this.isToast = isToast return this } fun setActivityTestRule(atr: ActivityTestRule<*>?): ViewElement { this.atr = atr return this } }
二.分层设计
先来看一下目录结构
View
层 (Page Object)package yq.test.espressoblackboxdemo.views import android.support.test.espresso.assertion.ViewAssertions.matches import android.support.test.espresso.matcher.ViewMatchers.isDisplayed import android.support.test.rule.ActivityTestRule import yq.test.espressoblackboxdemo.expansions.ViewElement class LoginView { private val email = ViewElement().setId("email") private val password = ViewElement("password") private val loginBtn = ViewElement(id = "email_sign_in_button") /** * 执行登录 */ fun doLogin(emailText: String, pwd: String): LoginView { email.replaceText(emailText) password.replaceText(pwd) loginBtn.click() return this } /** * 检查登录错误时的错误提示 */ fun checkLoginError(toastMsg: String ,atr: ActivityTestRule<*>): LoginView { ViewElement(text = toastMsg, isToast = true ,atr = atr) .check(matches(isDisplayed())) return this } }
这里还嵌入了
链式调用模式
. Page 中的每个方法都带上return this
,当然 如果是登录成功了 , 那么就应该return AccountView
或者是其他登录成功后应当 展示的Activity View
.Tests
层, 也就是具体的test case
package yq.test.espressoblackboxdemo.tests import android.support.test.runner.AndroidJUnit4 import org.junit.Test import org.junit.runner.RunWith import org.junit.Rule import yq.test.espressoblackboxdemo.utils.UtilJ.getActivityTestRule import yq.test.espressoblackboxdemo.views.LoginView @RunWith(AndroidJUnit4::class) class LoginTest { @get:Rule var atr = getActivityTestRule("yq.test.logindemo.LoginActivity") @Test fun loginTest(){ val login = LoginView() login .doLogin("hello" ,"") .checkLoginError("This email address is invalid" ,atr) } }
- 运行结果
三. 总结
POM 到这里就结束了 . 到目前, 我们总共也只分了两层(扩展 和 工具类不算在分层架构里面).
当然 , 还有 data层
我们目前还没有涉及 ,这将在后面的 数据驱动
章节讲述