Appium Python-Appium 设计模式问题求教

点点寒彬 · 2016年04月27日 · 最后由 皆非 回复于 2017年10月23日 · 2217 次阅读

背景

最近在研究 Python——Appium 的自动化实现,大概两个多月吧,感觉现在遇到了一些瓶颈,想请社区的牛人给一个方向,主要还是关于设计模式的。

现在的进度

正常的线性代码完成 Android 原生和混合 App 是没问题了,在写的过程中我发现实用性并不大,开发一点很小的变动我的代码就要做大改。感觉很麻烦,于是找了一个叫 Page Object 的设计模式,按照这个模式写,我现在实现了这么些东西。源代码在这里WeStockTest,还没写完。

元素

因为就我一个人,我就直接使用配置文件处理,代码如下:

CONNECT = {
    'platformName': 'Android',
    'platformVersion': '4.4.4',
    'deviceName': '5136b01e',
    'appPackage': 'com.weizq',
    'appActivity': 'com.zztzt.android.simple.app.MainActivity',
    "baseUrl": "http://127.0.0.1:4723/wd/hub"
}

COMMON = {
    'native_caixun': (By.NAME, u'财讯'),
    'native_hangqing': (By.NAME, u'行情'),
    'native_faxian': (By.NAME, u'发现'),
    'native_wo': (By.NAME, u'我'),
    'data_url': '/Users/SvenWeng/PycharmProjects/WeStock/Data/data.json',
    'view_title': (By.CLASS_NAME, 'android.widget.TextView')
}

公共方法

class WebDdriver(object):
    def __init__(self, driver):
        self.driver = driver

    def __str__(self):
        return 'webDdriver'

    def find_element(self, *loc):
        """
        定位元素,定位正确后返回元素的信息,外部调用传入元组参数必须有*,
        例如:
        find_element(*self.native_caixun)

        :param loc: 元组类型,结构必须是(By.NAME, u'财讯')
        :return: element
        """
        try:
            element = WebDriverWait(self.driver, 10).until(lambda x: x.find_element(*loc))
            return element
        except NoSuchElementException, e:
            print 'Error details :%s' % (e.args[0])

    def find_elements(self, *loc):
        """
        定位元素,定位正确后返回元素的信息,外部调用传入元组参数必须有*,
        例如:
        find_elements(*self.native_caixun)

        :param loc: 元组类型,结构必须是(By.NAME, u'财讯')
        :return: elements
        """
        try:
            # return self.driver.find_elements(*loc)
            elements = WebDriverWait(self.driver, 10).until(lambda x: x.find_elements(*loc))
            return elements
        except NoSuchElementException, e:
            print 'Error details :%s' % (e.args[0])

单个页面的方法

class Caixun(AppUI):

    # ----------------------财讯所有数据--------------------------
    native_caixun = config.COMMON['native_caixun']

    tuijian = config.CAIXUN['tuijian']
    gupiao = config.CAIXUN['gupiao']
    jijin = config.CAIXUN['jijin']
    zhaiquan = config.CAIXUN['zhaiquan']
    xinsanban = config.CAIXUN['xinsanban']
    neirong = config.CAIXUN['zixunneirong']
    plusbtn = config.CAIXUN['jiahao']
    pindao = config.CAIXUN['pindao']

    img_url = config.CAIXUN['imgurl']

    # ----------------------------------------------------------

    # ---------------------执行方法------------------------------

    def clickCaixun(self):
        """
        点击底部导航的财讯
        :return:None
        """
        self.find_element(*self.native_caixun).click()
        time.sleep(2)

    def get_screen(self, name):
        """
        对当前屏幕截图,函数中调用了getScreenshot,在上方定义常量的时候必须定义img_url
        :param name: 保存图片的名称
        :return:None
        """
        self.getScreenshot(name, self.img_url)

    def clickTuijian(self):
        """
        点击推荐
        :return: None
        """
        self.find_element(*self.tuijian).click()

    def clickGupiao(self):
        """
        点击股票
        :return: None
        """
        self.find_element(*self.gupiao).click()

测试步骤

class CaixunTest(AppTestCase, Caixun):

    def testAClickTitle(self):
        """测试财讯的频道内容是否正确"""
        self.clickCaixun()
        self.assertEqual(self.get_title(), u'财讯')
        self.clickTuijian()
        self.get_screen('tuijian')
        self.clickNeirong()
        self.get_screen('tuijianneirong')
        self.sysback()
        self.clickGupiao()
        self.get_screen('gupiao')
        self.clickNeirong()
        self.get_screen('gupiaoneirong')
        self.sysback()
        self.clickJijin()
        self.get_screen('jijin')
        self.clickNeirong()
        self.get_screen('jijinneirong')
        self.sysback()
        self.clickZhaiquan()
        self.get_screen('zhaiquan')
        self.clickNeirong()
        self.get_screen('zhaiquanneirong')
        self.sysback()
        self.clickXinsanban()
        self.get_screen('xinsanban')
        self.clickNeirong()
        self.get_screen('xinsanbanneirong')

    def testBAddPindao(self):
        """测试增加频道功能"""
        self.clickCaixun()
        self.clickPlus()
        self.assertEqual(self.get_title(), u'频道管理')
        # 校验推荐不可点击
        self.assertFalse(self.checkTuijianEnabled())
        # 校验其他可以点击
        self.assertTrue(self.checkOtherAbled())

        text = self.clickTextItem()
        self.assertEqual(text, self.getFifthItemText())
        self.clickItem(5)

    def testCNewPindao(self):
        """测试新增频道内容显示"""
        self.clickCaixun()
        self.clickPlus()
        text = self.clickTextItem()
        self.sysback()
        ori = self.getLocation(*config.CAIXUN['xinsanban'])
        new = self.getLocation(*config.CAIXUN['tuijian'])
        self.driver.swipe(ori[0], ori[1], new[0], new[1])
        self.find_element(*(By.NAME, text)).click()
        self.clickNeirong()
        self.get_screen(text)

困惑

现在最大的困惑就是再怎么走下去,和一个朋友聊的时候他说这个迷茫期是很正常的,说明处在一个比较正常的十字路口,但是下一步怎么走他也没说。

我说说几个比较明确的疑问吧:

  1. 我这种设计方式有哪些比较严重的缺陷?(我自己感觉除了抽离出元素,好像和线性撸代码差别不是特别的大)
  2. 我想过关键字驱动的方法,但是步骤中有很多地方涉及到流程的控制,我没太明白这一块要怎么去设计
  3. 关于 Android 的性能方面要如何提升?参考群里的优质帖子,我自己也写了一个监控工具:《AndroidTools》,但是具体这些指标都是死的,不太明白哪些场景应该用哪些手段来测试哪些指标。
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 14 条回复 时间 点赞

UI 自动化做的不多,但是只从你最后的用例代码来看,我只看到了一堆截图代码,可读性太差了

1、如果非得每个点击都执行截图操作,你应该实现一个自动截图的装饰器
2、你的 pageobject 不够抽象,感觉还是过程式的代码
3、可以增加 Action 层,把常用的点击流程封装起来
4、不要把时间浪费在关键字驱动上面

#1 楼 @jacexh 刚好那部分需要用截图处理,其他部分还没传到 github 上

def testJiangXu(self):
        """测试自选页面降序排列"""
        num = self.getLength() - 1
        self.clickNameNum()
        a = 999999
        for x in range(num):
            try:
                int(self.getStockNum(x))
                self.assertTrue(int(a) > int(self.getStockNum(x)))
                a = self.getStockNum(x)
            except ValueError:
                pass

    def testShengXu(self):
        """测试自选页面升序排列"""
        num = self.getLength() - 1
        self.clickNameNum()
        self.clickNameNum()
        a = 0
        for x in range(num):
            try:
                int(self.getStockNum(x))
                self.assertTrue(int(a) < int(self.getStockNum(x)))
                a = self.getStockNum(x)
            except ValueError:
                pass

我自己的代码大概是这样的。

2、你的 pageobject 不够抽象,感觉还是过程式的代码

我也是这么觉得的,所以才想请教一下这部分要怎么处理

3、可以增加 Action 层,把常用的点击流程封装起来

这部分我是这么处理的

def clickHangQing(self):
        """
        点击导航栏的行情按钮
        :return: None
        """
        self.find_element(*self.native_hangqing).click()

    def clickEditSelect(self):
        """
        点击编辑自选,进入编辑自选页面
        :return: None
        """
        self.find_elements(*self.edit_select)[0].click()

    def clickFindStock(self):
        """
        点击放大镜,进入查询自选股界面
        :return: None
        """
        self.find_elements(*self.edit_select)[1].click()

    def clickZiXuan(self):
        """
        点击行情页面的自选
        :return: None
        """
        if self.get_title() != u'行情':
            try:
                self.clickHangQing()
            except Exception as e:
                print e
                print '行情导航栏无法点击,请检查APP的状态'
        else:
            self.find_element(*self.zixuan).click()

但是感觉耦合性还是比较差。

我感觉脚本与脚本之间存在复用,比如财讯有 5 个测试用例,那么这几个脚本之间存在复用。
可以再抽象一些方法放入 page class

好高深啊,新手看不懂啊。我想请教个问题

#3 楼 @pacerron 我想想要怎么修改,现在每一个测试用例都是单独的,除了调用基类的东西基本上没有做复用,就像
#1 楼 @jacexh 说的那样,代码写的还不够抽象

新手看起来挺明白的 希望能早日赶上楼主的进度 共勉

#4 楼 @abcfleeting 你倒是说呀,我这强迫症看着好难受

我给一个思路,设计一个 pythonGUI,然后将单个操作分开,通过 GUI,设置参数,来控制操作组合!

#9 楼 @qwerty 不好意思我没太明白,是指用 GUI 来设置所有元素的参数吗?

#10 楼 @wyb199026 我举个栗子,我这里有一个修改资料类,有八个可修改内容,假设我现在就需要修改两个,{‘name’:'qwerty','sex':'male'},将字典传入修改资料类,启动 start 方法进行这两个内容的修改,name 对应哪个控件,sex 又对应哪个控件,这个再配置一下

#11 楼 @qwerty 明白了,非常感谢

谢谢楼主!看完之后很有启发!

点点寒彬 回复

楼主还在吗,可以交流下吗?

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