Selenium 脑洞大开:CMap 一种 UI 自动化开发模式

Jayvee · 2017年05月19日 · 最后由 Jayvee 回复于 2017年05月22日 · 1307 次阅读

CMap=CtrlMap 控件地图

意图:①抽象控件:将 UI 自动化开发中的 “控件逻辑” 与 “控件信息” 进行隔离 ②自动回归:“控件信息” 有变化时可快速的识别出来进行适配。
主要解决:①简化 “控件逻辑” ②降低由 “控件信息” 变化时引起的适配成本。
如何解决:①控件抽象化:例如在 WEB 自动化中,将"text_field/radio/checkbox/select_list"这些抽象为一种控件,
那么假如有 20 个页面、平均每个页面 10 个控件,原本需要写 20*10 个控件逻辑的,用 CMap 模式后能简化为 20*1 个控件逻辑。

②地图化:例如有一个控件的对象名为"AccoCapac.Line_FastEdt_Name",可以知道它的跳转路径为"AccoCapac->AccoCapac.Line->AccoCapac.Line_FastEdt"

应用实例 1-抽象控件(以 TesterHome 的登录页面为例,先贴出部分关键代码):

from Selenium2Library import Selenium2Library

class CMap(object):
    """ Singleton_Pattern extend Selenium2Library """
    ...
# end class

class Signin(object):
    URL = CMap('https://testerhome.com/account/sign_in', CMap.NONE)
    Commit = CMap('xpath=//input[@name="commit"]', CMap.BUTTON)
    Remember = CMap('xpath=//input[@id="user_remember_me"]', CMap.CHECKBOX)
    User = CMap('xpath=//input[@id="user_login"]', CMap.TEXT_FIELD)
    Pwd = CMap('xpath=//input[@id="user_password"]', CMap.TEXT_FIELD)
    FLAG = Commit  # a special Ctrl<wait_until_element_is_visible>
    @staticmethod
    def GOTO_():
        Signin.URL.go_to()
# end class

Signin.URL.open_browser(browser='firefox')  # create browserObj<Singleton_Pattern>, and open
#Signin.GOTO_()
Signin.FLAG.wait_until_element_is_visible()

mysets = {'User': '123', 'Pwd': '456', 'Remember': True}
for ictrl, ivalue in mysets.items():
    print(' ictrl=%r ivalue=%r' % (ictrl, ivalue))
    getattr(Signin, ictrl).Set(ivalue)
    ifact = getattr(Signin, ictrl).Get()
    print('   ictrl.Get()=%r' % ifact)
    assert ifact == ivalue, "ifact==%r, but=%r" % (ivalue, ifact)

应用实例 2-自动回归(下图是之前一个项目中,全自动生成的 “控件信息” 回归代码):

# 补充: 上面 class CMap 的完整代码:

class CMap(object):
    """ Singleton_Pattern extend Selenium2Library """
    B = None  # browserObj<Singleton_Pattern>
    def __init__(self, locator, ctrlCls, **kw):
        if CMap.B is None:
            CMap.B = Selenium2Library(**kw)
        self._locator = locator
        self._ctrlCls = ctrlCls

    def __getattr__(self, attr):
        return _CMapHelper_BrowserDo(method=attr, arg0=self._locator)

    # Abstract_Methods
    def Set(self, value=None):
        self._ctrlCls(opt='Set', locator=self._locator, value=value)

    def Get(self, value=None):
        return self._ctrlCls(opt='Get', locator=self._locator)

    # Abstract_Methods's real_method
    @staticmethod
    def __TEXT_FIELD__(opt, locator, value=None):
        if opt == 'Set':
            CMap.B.input_text(locator, value)
        elif opt == 'Get':
            return CMap.B.get_webelement(locator).get_attribute('value')

    @staticmethod
    def __BUTTON__(opt, locator, value=None):
        if opt == 'Set':
            CMap.B.click_element(locator)
        elif opt == 'Get':
            return CMap.B.get_text(locator)

    @staticmethod
    def __CHECKBOX__(opt, locator, value=None):
        """ value==is_selected() # True/False """
        elementGet = CMap.B.get_webelement(locator)
        if opt == 'Set':
            return elementGet.click() if elementGet.is_selected()!=value else None
        elif opt == 'Get':
            return elementGet.is_selected()

    # ctrlCls
    TEXT_FIELD = __TEXT_FIELD__
    BUTTON = __BUTTON__
    CHECKBOX = __CHECKBOX__
    NONE = None
# end class

class _CMapHelper_BrowserDo(object):
    def __init__(self, method, arg0):
        self.method = method
        self.arg0 = arg0
    def __call__(self, *args, **kw):
        return getattr(CMap.B, self.method)(self.arg0, *args, **kw)
# end class
共收到 5 条回复 时间 点赞

TestHome ==》TesterHome

这个就是 PageObject 的改造吧

看了下 CMap 的实现,我的理解是针对 TextField,Button,Checkbox 三种常用控件的常用操作都封装成 get 和 set 方法,简化掉不同控件的各种不同的调用(如 TestField 的 send_keys,Button 的 click,CheckBox 的 click ,所以用例里面只需要对控件对象进行 set 和 get 就好了。

例如 TesterHome 例子里的就是遍历这个字典,分别对 User,Pwd 和 Remember 三个控件进行:

User set 为 123(send_keys)
校验 User 当前的值是否为 123
Pwd set 为 456(send_keys)
校验 Pwd 当前的值是否为 456
Remember set 为 TRUE(click)
校验 Remember 当前的值是否为 TRUE(is_selected)

这种方式确实减少了一些学习成本,而且个人觉得大部分控件应该都能用 set 和 get 进行简化的。

至于自动回归和地图化,很感兴趣,但信息有点少,表示没看懂。希望楼主可以补充一些例子说明下~

感觉有点策略模式的味道,关键字驱动思想,通过 Map 映射控件和具体的 Action
然后根据不同的控件调用通用方法:Map.get(控件).Set() Map.get(控件).Get()

陈恒捷 回复

差不多吧, 我给出的只是简单例子、对"TextField,Button,Checkbox"三类基础控件进行封装,实际应用中可以更广泛,所有基础控件类型、自定义控件类型,都是可以用起来的。

至于自动回归和地图化,我先给个算法思路,有空了再贴例子:
step0> 每个一级 Page 为一个类,它可以包含多个、多层的二级页面、用下划线分割、GOTO_方法为页面跳转
step1> 遍历出所有的 Page 类、及它的所有 GOTO_方法。
例如本帖正文中的例子,对于 AccoCapac 这个 Page 类,可以遍历出"GOTO_、Line_GOTO_、Line_FastEdt_GOTO_"这三个 GOTO_方法,即这个 Page 下有 1 个一级页面、2 个二级页面。
step2> 以每个二级页面 (Page.Sub_) 为一个测试集,用数据驱动的方式设计测试用例 (Page.Sub_*)。
例如本帖正文中的例子,对于 AccoCapac 这个 Page 类中的 AccoCapac.Line_这个二级页面,可以设计一个测试集:
它包含 2 个测试数据"AccoCapac.Line_Del_、AccoCapac.Line_FastEdt_";
它的 setup 环境设置部分,应该是地图化的页面跳转,AccoCapac.GOTO_(); AccoCapac.Line_GOTO_()
step3> 根据不同的自动化测试框架,生成对应的测试脚本。

恒温 回复

赞~(≧▽≦)/~ 很细心

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册