简介
江湖有言:”代码写死一时爽,框架重构火葬场“,更有人戏言:”代码动态一时爽,一直动态一直爽“,虽然听起来有点耸人听闻,但也没有想象中的那么严重,我们在开发写代码的时候留心和注意就可以了。
为了重构时,少掉些头发,在开发的时候就得注意了。
写死代码后,有变动后出现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,可以加宏哥的微信拉你进群,请备注:进群)