Appium+python自动化(三十二)- 代码写死一时爽,框架重构火葬场 – PageObject+unittest(超详解)

  • 2019 年 10 月 3 日
  • 筆記

简介

江湖有言:”代码写死一时爽,框架重构火葬场“,更有人戏言:”代码动态一时爽,一直动态一直爽?“,虽然听起来有点耸人听闻,但也没有想象中的那么严重,我们在开发写代码的时候留心和注意就可以了。

为了重构时,少掉些头发,在开发的时候就得注意了。

 

写死代码后,有变动后出现bug后我们的反应

 

大佬和菜鸟对遗留写死代码的反应

最后和宏哥一起膜拜一下能够重构写死代码的大牛

是不是有宏哥的风范啊

闲话少说,进入今天的主题:PageObject+unittest。

问题思考

前面我们都是基于线性模型来编写测试脚本,而且元素定位方式和属性值都是写死的。在业务场景简单的情况下这样写无可厚非,但是一旦遇到产品需求变更,业务逻辑比较复杂需要维护的时候就非常麻烦了,那么该如何应对这种情况呢?

场景案例

结合前面我们所学,测试考研帮App登录场景,按照线性模型来构造出脚本如下: 考研帮登录测试场景

kyb_login.py

代码实现

参考代码

# coding=utf-8  # 1.先设置编码,utf-8可支持中英文,如上,一般放在第一行    # 2.注释:包括记录创建时间,创建人,项目名称。  '''  Created on 2019-8-16  @author: 北京-宏哥   QQ交流群:707699217  Project:学习和使用appium自动化测试-代码写死一时爽,框架重构火葬场 - PageObject+unittest  '''  # 3.导入模块  from appium import webdriver  import yaml  from selenium.common.exceptions import NoSuchElementException  import logging  import logging.config      CON_LOG='../log/log.conf'  logging.config.fileConfig(CON_LOG)  logging=logging.getLogger()      stream=open('../yaml/desired_caps.yaml','r')  data=yaml.load(stream)    desired_caps={}  desired_caps['platformName']=data['platformName']    desired_caps['platformVersion']=data['platformVersion']  desired_caps['deviceName']=data['deviceName']    desired_caps['app']=data['app']  desired_caps['noReset']=data['noReset']    desired_caps['unicodeKeyboard']=data['unicodeKeyboard']  desired_caps['resetKeyboard']=data['resetKeyboard']    desired_caps['appPackage']=data['appPackage']  desired_caps['appActivity']=data['appActivity']    driver = webdriver.Remote('http://'+str(data['ip'])+':'+str(data['port'])+'/wd/hub', desired_caps)    def check_updateBtn():      logging.info("check_updateBtn")        try:          element = driver.find_element_by_id('android:id/button2')      except NoSuchElementException:          logging.info('update element is not found!')      else:          element.click()      def check_skipBtn():      logging.info("check_skipBtn")      try:          element = driver.find_element_by_id('com.tal.kaoyan:id/tv_skip')      except NoSuchElementException:          logging.info('skipBtn element is not found!')      else:          element.click()    check_updateBtn()  check_skipBtn()    logging.info('start login...')    driver.find_element_by_id('com.tal.kaoyan:id/login_email_edittext').send_keys('自学网2018')  driver.find_element_by_id('com.tal.kaoyan:id/login_password_edittext').send_keys('zxw2018')  driver.find_element_by_id('com.tal.kaoyan:id/login_login_btn').click()  logging.info('login finished')

案例分析

上面的脚本看似都比较完善,有了log采集,参数配置、启动时页面元素自动检测。但是也存在一些不足之处:

  • 公共模块和业务模块混合在一起显得代码冗余等
  • 测试场景单一(如果要实现如下测试场景该怎么办?)
  • 元素定位属性和代码混杂在一起

以上这些都是需要优化的地方。

测试场景

操作步骤

预期结果

多账号登录

不同的用户名密码来进行登录

能够正常登录

异常登录

用户名或者密码错误、或者为空进行登录,

登录失败,同时界面要给出相应的提示

注册

点击注册,然后进行注册信息填写

能够注册成功

重构优化思路

  • 将一些公共的内容(如:check_updateBtn,check_skipBtn,capability)抽离出来。
  • 元素定位方法和元素属性值与业务代码分离
  • 登录功能模块封装为一个独立的模块
  • 使用unittest进行用例综合管理

Page Object

Page Object是Selenium自动化测试项目开发实践的最佳设计模式之一,通过对界面元素的封装减少冗余代码,同时在后期维护中,若元素定位发生变化,只需要调整页面元素封装的代码,提高测试用例的可维护性。

脚本实现

封装App启动配置信息

desired_caps.py

代码实现

参考代码

# coding=utf-8  # 1.先设置编码,utf-8可支持中英文,如上,一般放在第一行    # 2.注释:包括记录创建时间,创建人,项目名称。  '''  Created on 2019-8-16  @author: 北京-宏哥   QQ交流群:707699217  Project:学习和使用appium自动化测试-代码写死一时爽,框架重构火葬场 - PageObject+unittest  '''  # 3.导入模块  from appium import webdriver  import yaml  import logging  import logging.config    CON_LOG='../log/log.conf'  logging.config.fileConfig(CON_LOG)  logging=logging.getLogger()    def appium_desired():      file = open('../yaml/desired_caps.yaml', 'r')      data = yaml.load(file)        desired_caps={}      desired_caps['platformName']=data['platformName']      desired_caps['platformVersion']=data['platformVersion']      desired_caps['deviceName']=data['deviceName']        desired_caps['app']=data['app']      desired_caps['appPackage']=data['appPackage']      desired_caps['appActivity']=data['appActivity']      desired_caps['noReset']=data['noReset']        desired_caps['unicodeKeyboard']=data['unicodeKeyboard']      desired_caps['resetKeyboard']=data['resetKeyboard']        logging.info('start app...')      driver=webdriver.Remote('http://'+str(data['ip'])+':'+str(data['port'])+'/wd/hub',desired_caps)      driver.implicitly_wait(8)      return driver    if __name__ == '__main__':      appium_desired()

记得在原来的yaml配置表desired_caps.yaml补充如下内容:

unicodeKeyboard: True 

resetKeyboard: True

封装基类: 

baseView.py

代码实现

参考代码

# coding=utf-8  # 1.先设置编码,utf-8可支持中英文,如上,一般放在第一行    # 2.注释:包括记录创建时间,创建人,项目名称。  '''  Created on 2019-8-16  @author: 北京-宏哥   QQ交流群:707699217  Project:学习和使用appium自动化测试-代码写死一时爽,框架重构火葬场 - PageObject+unittest  '''  # 3.定义类  class BaseView(object):      def __init__(self,driver):          self.driver=driver        def find_element(self,*loc):          return self.driver.find_element(*loc)

封装通用公共类

common_fun.py

代码实现

参考代码

# coding=utf-8  # 1.先设置编码,utf-8可支持中英文,如上,一般放在第一行    # 2.注释:包括记录创建时间,创建人,项目名称。  '''  Created on 2019-8-16  @author: 北京-宏哥   QQ交流群:707699217  Project:学习和使用appium自动化测试-代码写死一时爽,框架重构火葬场 - PageObject+unittest  '''  # 3.导入模块  from page_object.baseView import BaseView  from page_object.desired_caps import appium_desired  from selenium.common.exceptions import NoSuchElementException  import logging  from selenium.webdriver.common.by import By    class Common(BaseView):      cancelBtn=(By.ID,'android:id/button2')      skipBtn=(By.ID,'com.tal.kaoyan:id/tv_skip')        def check_cancelBtn(self):          logging.info('==========check_cancelBtn=========')          try:              cancelBtn = self.driver.find_element(*self.cancelBtn)          except NoSuchElementException:              logging.info('no cancelBtn')          else:              cancelBtn.click()        def check_skipBtn(self):          logging.info('=========check skipBtn=============')            try:              skipBtn = self.driver.find_element(*self.skipBtn)          except NoSuchElementException:              logging.info('no skipBtn')          else:              skipBtn.click()    if __name__ == '__main__':      driver=appium_desired()      com=Common(driver)      com.check_cancelBtn()      com.check_skipBtn()

封装登录操作 

loginView.py

代码实现

参考代码

# coding=utf-8  # 1.先设置编码,utf-8可支持中英文,如上,一般放在第一行    # 2.注释:包括记录创建时间,创建人,项目名称。  '''  Created on 2019-8-16  @author: 北京-宏哥   QQ交流群:707699217  Project:学习和使用appium自动化测试-代码写死一时爽,框架重构火葬场 - PageObject+unittest  '''  # 3.导入模块  import logging  from page_object.common_fun import Common  from page_object.desired_caps import appium_desired  from selenium.webdriver.common.by import By    class LoginView(Common):      username_type=(By.ID,'com.tal.kaoyan:id/login_email_edittext')      password_type=(By.ID,'com.tal.kaoyan:id/login_password_edittext')      loginBtn=(By.ID,'com.tal.kaoyan:id/login_login_btn')        def login_action(self,username,password):          self.check_cancelBtn()          self.check_skipBtn()            logging.info('============login_action==============')          logging.info('username is:%s' %username)          self.driver.find_element(*self.username_type).send_keys(username)            logging.info('password is:%s'%password)          self.driver.find_element(*self.password_type).send_keys(password)            logging.info('click loginBtn')          self.driver.find_element(*self.loginBtn).click()          logging.info('login finished!')    if __name__ == '__main__':      driver=appium_desired()      l=LoginView(driver)      l.login_action('北京-宏哥-2019','bjhg2019')

unittest用例封装

测试场景

使用如下账号进行分别登录测试

用户名

密码

自学网2018

zxw2018

自学网2017

zxw2017

666

222

Tips必备基础知识:Selenium自动化第六章-unittest单元测试框架

1.封装用例启动结束时的配置: 

myunit.py

代码实现

参考代码

# coding=utf-8  # 1.先设置编码,utf-8可支持中英文,如上,一般放在第一行    # 2.注释:包括记录创建时间,创建人,项目名称。  '''  Created on 2019-8-16  @author: 北京-宏哥   QQ交流群:707699217  Project:学习和使用appium自动化测试-代码写死一时爽,框架重构火葬场 - PageObject+unittest  '''  # 3.导入模块  import unittest  from page_object.desired_caps import appium_desired  import logging  from time import sleep    class StartEnd(unittest.TestCase):      def setUp(self):          logging.info('=====setUp====')          self.driver=appium_desired()        def tearDown(self):          logging.info('====tearDown====')          sleep(5)          self.driver.close_app()

2.用例封装 

test_login.py

代码实现

参考代码

# coding=utf-8  # 1.先设置编码,utf-8可支持中英文,如上,一般放在第一行    # 2.注释:包括记录创建时间,创建人,项目名称。  '''  Created on 2019-8-16  @author: 北京-宏哥   QQ交流群:707699217  Project:学习和使用appium自动化测试-代码写死一时爽,框架重构火葬场 - PageObject+unittest  '''  # 3.导入模块  from unittest.myunit import StartEnd  from page_object.loginView import LoginView  import unittest  import logging    class TestLogin(StartEnd):        def test_login_bjhg2019(self):          logging.info('======test_login_bjhg-2019=====')          l=LoginView(self.driver)          l.login_action('北京宏哥-2018','bjhg-2019')        def test_login_bjhg2018(self):          logging.info('======test_login_bjhg-2018=====')          l=LoginView(self.driver)          l.login_action('北京宏哥-2018','bjhg-2018')        def test_login_error(self):          logging.info('======test_login_error=====')          l = LoginView(self.driver)          l.login_action('6666', '222')    if __name__ == '__main__':      unittest.main()

小结

1.代码运行流程图

 

 2.宏哥箴言:

代码写死一时爽,框架重构火葬场。此处功能将来必改,不要写死!

 

 3.最后大家要且行且珍惜,出来混迟早晚要还的。(^__^) 嘻嘻……

 

您的肯定就是我进步的动力。如果你感觉还不错,就请鼓励一下吧!记得点波 推荐 哦!!!(点击右边的小球即可!(^__^) 嘻嘻……)

 

.

       个人公众号                                                             微信群 (微信群已满100,可以加宏哥的微信拉你进群,请备注:进群)