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

