六、UI自动化测试06--PO设计模式
目录
- 一、PO 设计模式
- 1. v1 版本
- 1.1 v1.1
- 1.2 v1.2
- 2. v2 版本
- 3. ⽅法封装套路
- 4. v3 版本
- 4.1 浏览器对象管理类的实现
- 4.2 浏览器对象管理类的优化
- 4.3 浏览器对象管理类的使⽤
- 4.4 获取弹窗信息⽅法的封装
- 5. PO 设计模式
- 6. v4 版本
- 6.1 PO⻚⾯元素封装步骤
- 6.2 测试⽤例的最终代码样式
- 7. v5 版本
- 7.1 PO ⽂件对象层代码优化
- 7.2 PO ⽂件操作层代码优化
一、PO 设计模式
1. v1 版本
说明: 通过测试执⾏框架 pytest, 可以整合所有的同⼀模块的测试⽤例脚本, 并且需要尽⼒符合⼿⼯测试的操作业务逻辑, 最终实现执⾏单个测试脚本, 执⾏同⼀模块的所有测试⽤例
1.1 v1.1
- 通过测试⽅法整合测试脚本
"""
整合多个脚本⾄同⼀个测试⽤例中
"""
import pytest
from time import sleep
from selenium import webdriverclass TestLogin(object):"""登录测试类"""def test_account_does_not_exist(self):"""账号不存在测试⽅法"""driver = webdriver.Chrome()driver.get('http://127.0.0.1/')driver.maximize_window() # 窗⼝最⼤化driver.implicitly_wait(10) # 隐式等待# 1. 点击⾸⻚的‘登录’链接,进⼊登录⻚⾯driver.find_element_by_link_text('登录').click()# 2. 输⼊⼀个不存在的⽤户名driver.find_element_by_id('username').send_keys('13811110001')# 3. 输⼊密码driver.find_element_by_id('password').send_keys('123456')# 4. 输⼊验证码driver.find_element_by_id('verify_code').send_keys('8888')# 5. 点击登录按钮driver.find_element_by_name('sbtbutton').click()# 6. 获取错误提示信息# 获取元素⽂本值: 元素对象.textmsg = driver.find_element_by_class_name('layui-layercontent').textprint('错误信息为:', msg)sleep(3)driver.quit()def test_wrong_password(self):"""密码错误测试⽅法"""driver = webdriver.Chrome()driver.get('http://127.0.0.1/')driver.maximize_window() # 窗⼝最⼤化driver.implicitly_wait(10) # 隐式等待# 1. 点击⾸⻚的‘登录’链接,进⼊登录⻚⾯driver.find_element_by_link_text('登录').click()# 2. 输⼊⽤户名driver.find_element_by_id('username').send_keys('13800001111')# 3. 输⼊错误密码driver.find_element_by_id('password').send_keys('error')# 4. 输⼊验证码driver.find_element_by_id('verify_code').send_keys('8888')# 5. 点击登录按钮driver.find_element_by_name('sbtbutton').click()# 6. 获取错误提示信息# 获取元素⽂本值: 元素对象.textmsg = driver.find_element_by_class_name('layui-layer-content').textprint('错误信息为:', msg)sleep(3)driver.quit()if __name__ == '__main__':pytest.main(['-s', 'tpshop_login_1.py'])
1.2 v1.2
- 通过 setup() 和 teardown() ⽅法优化多个脚本相同的前置后后置操作
"""
整合多个脚本⾄同⼀个测试⽤例中
"""
import pytest
from time import sleep
from selenium import webdriverclass TestLogin(object):"""登录测试类"""def setup(self):self.driver = webdriver.Chrome()self.driver.get('http://127.0.0.1/')self.driver.maximize_window() # 窗⼝最⼤化self.driver.implicitly_wait(10) # 隐式等待def teardown(self):sleep(3)self.driver.quit()def test_account_does_not_exist(self):"""账号不存在测试⽅法"""# driver = webdriver.Chrome()# driver.get('http://127.0.0.1/')# driver.maximize_window() # 窗⼝最⼤化# driver.implicitly_wait(10) # 隐式等待# 1. 点击⾸⻚的‘登录’链接,进⼊登录⻚⾯self.driver.find_element_by_link_text('登录').click()# 2. 输⼊⼀个不存在的⽤户名self.driver.find_element_by_id('username').send_keys('13811110001')# 3. 输⼊密码self.driver.find_element_by_id('password').send_keys('123456')# 4. 输⼊验证码self.driver.find_element_by_id('verify_code').send_keys('8888')# 5. 点击登录按钮self.driver.find_element_by_name('sbtbutton').click()# 6. 获取错误提示信息# 获取元素⽂本值: 元素对象.textsleep(2)msg = self.driver.find_element_by_class_name('layui-layer-content').textprint('错误信息为:', msg)# sleep(3)# driver.quit()def test_wrong_password(self):"""密码错误测试⽅法"""# driver = webdriver.Chrome()# driver.get('http://127.0.0.1/')# driver.maximize_window() # 窗⼝最⼤化# driver.implicitly_wait(10) # 隐式等待# 1. 点击⾸⻚的‘登录’链接,进⼊登录⻚⾯self.driver.find_element_by_link_text('登录').click()# 2. 输⼊⽤户名self.driver.find_element_by_id('username').send_keys('138000011
11')# 3. 输⼊错误密码self.driver.find_element_by_id('password').send_keys('error')# 4. 输⼊验证码self.driver.find_element_by_id('verify_code').send_keys('8888')# 5. 点击登录按钮self.driver.find_element_by_name('sbtbutton').click()# 6. 获取错误提示信息# 获取元素⽂本值: 元素对象.textsleep(2)msg = self.driver.find_element_by_class_name('layuilayer-content').textprint('错误信息为:', msg)# sleep(3)# driver.quit()if __name__ == '__main__':pytest.main(['-s', 'tpshop_login_2.py'])
2. v2 版本
- 通过 setup_class() 和 teardown_class() ⽅法使脚本的执⾏逻辑跟符合⼿⼯测试逻辑
"""
整合多个脚本⾄同⼀个测试⽤例中
"""
import pytest
from time import sleep
from selenium import webdriverclass TestLogin(object):"""登录测试类"""def setup_class(self):self.driver = webdriver.Chrome()self.driver.get('http://127.0.0.1/')self.driver.maximize_window() # 窗⼝最⼤化self.driver.implicitly_wait(10) # 隐式等待def teardown_class(self):sleep(3)self.driver.quit()def setup(self):# self.driver = webdriver.Chrome()# self.driver.get('http://127.0.0.1/')# self.driver.maximize_window() # 窗⼝最⼤化# self.driver.implicitly_wait(10) # 隐式等待# 打开⾸⻚self.driver.get('http://127.0.0.1/')# 点击登录self.driver.find_element_by_link_text('登录').click()def teardown(self):# sleep(3)# self.driver.quit()passdef test_account_does_not_exist(self):"""账号不存在测试⽅法"""# 1. 点击⾸⻚的‘登录’链接,进⼊登录⻚⾯# self.driver.find_element_by_link_text('登录').click()# 2. 输⼊⼀个不存在的⽤户名self.driver.find_element_by_id('username').send_keys('13811110001')# 3. 输⼊密码self.driver.find_element_by_id('password').send_keys('123456')# 4. 输⼊验证码self.driver.find_element_by_id('verify_code').send_keys('8888')# 5. 点击登录按钮self.driver.find_element_by_name('sbtbutton').click()# 6. 获取错误提示信息# 获取元素⽂本值: 元素对象.textsleep(2)msg = self.driver.find_element_by_class_name('layui-layer-content').textprint('错误信息为:', msg)def test_wrong_password(self):"""密码错误测试⽅法"""# 1. 点击⾸⻚的‘登录’链接,进⼊登录⻚⾯# self.driver.find_element_by_link_text('登录').click()# 2. 输⼊⽤户名self.driver.find_element_by_id('username').send_keys('13800001111')# 3. 输⼊错误密码self.driver.find_element_by_id('password').send_keys('error')# 4. 输⼊验证码self.driver.find_element_by_id('verify_code').send_keys('8888')# 5. 点击登录按钮self.driver.find_element_by_name('sbtbutton').click()# 6. 获取错误提示信息# 获取元素⽂本值: 元素对象.textsleep(2)msg = self.driver.find_element_by_class_name('layui-layer-content').textprint('错误信息为:', msg)if __name__ == '__main__':pytest.main(['-s', 'tpshop_login_3.py'])
3. ⽅法封装套路
-
- 确定⽅法的存放位置: 找位置
-
- 给⽅法起个合适名字: 起名字
-
- 放⼊要封装的代码内容: 放代码
-
- 确认是否需要参数和返回值: 确必要
-
- 调⽤封装好的⽅法使⽤: 做调⽤
4. v3 版本
4.1 浏览器对象管理类的实现
"""
公共⽅法模块: 习惯命名 utils
"""
from selenium import webdriver
from time import sleepclass DriverUtil(object):"""浏览器对象管理类"""driver = None # 浏览器对象变量初始化状态def get_driver(self):"""获取浏览器对象⽅法"""# 说明: 为了防⽌在同⼀次测试过程中, 调⽤获取浏览器对象⽅法时,# 都会创建⼀个新的浏览器对象, 因此有必要先判断对象是否存在,
不存在时在创建!if self.driver is None:self.driver = webdriver.Chrome()self.driver.get('http://127.0.0.1/')self.driver.maximize_window() # 窗⼝最⼤化self.driver.implicitly_wait(10) # 隐式等待return self.driverdef quit_driver(self):"""退出浏览器对象⽅法"""# 说明: 必须保证浏览器对象存在, 才能执⾏退出操作if self.driver: # 等价于: if self.driver is not None:sleep(3)self.driver.quit()self.driver = None # 保险⼿段: 移除对象后, 保留对象变量, 以备下⼀次使⽤if __name__ == '__main__':my_driver = DriverUtil()my_driver.get_driver()sleep(2)my_driver.quit_driver()
4.2 浏览器对象管理类的优化
"""
公共⽅法模块: 习惯命名 utils
"""
from selenium import webdriver
from time import sleepclass DriverUtil(object):"""浏览器对象管理类"""# 说明: 对象变量只需要在类定义内部使⽤, 因此定义为私有__driver = None # 浏览器对象变量初始化状态@classmethoddef get_driver(cls):"""获取浏览器对象⽅法"""# 说明: 为了防⽌在同⼀次测试过程中, 调⽤获取浏览器对象⽅法时,# 都会创建⼀个新的浏览器对象, 因此有必要先判断对象是否存在, 不存在时在创建!if cls.__driver is None:cls.__driver = webdriver.Chrome()cls.__driver.get('http://127.0.0.1/')cls.__driver.maximize_window() # 窗⼝最⼤化cls.__driver.implicitly_wait(10) # 隐式等待return cls.__driver@classmethoddef quit_driver(cls):"""退出浏览器对象⽅法"""# 说明: 必须保证浏览器对象存在, 才能执⾏退出操作if cls.__driver: # 等价于: if self.driver is not None:sleep(3)cls.__driver.quit()cls.__driver = None # 保险⼿段: 移除对象后, 保留对象变量, 以备下⼀次使⽤if __name__ == '__main__':# my_driver = DriverUtil()# my_driver.get_driver()# sleep(2)# my_driver.quit_driver()# 说明: 定义为类⽅法, 可以直接由类对象调⽤, 省略实例化对象步骤DriverUtil.get_driver() # 获取浏览器对象sleep(2)DriverUtil.quit_driver() # 退出浏览器对象
4.3 浏览器对象管理类的使⽤
"""
整合多个脚本⾄同⼀个测试⽤例中
"""
import pytest
from time import sleep
from selenium import webdriverfrom utils import DriverUtilclass TestLogin(object):"""登录测试类"""def setup_class(self):# self.driver = webdriver.Chrome()# self.driver.get('http://127.0.0.1/')# self.driver.maximize_window() # 窗⼝最⼤化# self.driver.implicitly_wait(10) # 隐式等待self.driver = DriverUtil.get_driver() # 获取浏览器对象def teardown_class(self):# sleep(3)# self.driver.quit()DriverUtil.quit_driver() # 退出浏览器对象def setup(self):# 打开⾸⻚self.driver.get('http://127.0.0.1/')# 点击登录self.driver.find_element_by_link_text('登录').click()def teardown(self):passdef test_account_does_not_exist(self):"""账号不存在测试⽅法"""# 2. 输⼊⼀个不存在的⽤户名self.driver.find_element_by_id('username').send_keys('13811110001')# 3. 输⼊密码self.driver.find_element_by_id('password').send_keys('123456')# 4. 输⼊验证码self.driver.find_element_by_id('verify_code').send_keys('8888')# 5. 点击登录按钮self.driver.find_element_by_name('sbtbutton').click()# 6. 获取错误提示信息# 获取元素⽂本值: 元素对象.textsleep(2)msg = self.driver.find_element_by_class_name('layui-layer-content').textprint('错误信息为:', msg)def test_wrong_password(self):"""密码错误测试⽅法"""# 2. 输⼊⽤户名self.driver.find_element_by_id('username').send_keys('13800001111')# 3. 输⼊错误密码self.driver.find_element_by_id('password').send_keys('error')# 4. 输⼊验证码self.driver.find_element_by_id('verify_code').send_keys('8888')# 5. 点击登录按钮self.driver.find_element_by_name('sbtbutton').click()# 6. 获取错误提示信息# 获取元素⽂本值: 元素对象.textsleep(2)msg = self.driver.find_element_by_class_name('layui-layer-content').textprint('错误信息为:', msg)if __name__ == '__main__':pytest.main(['-s', 'tpshop_login_3.py'])
4.4 获取弹窗信息⽅法的封装
from selenium import webdriver
from time import sleepdef get_alert_msg():"""获取弹窗信息⽅法"""sleep(2)# msg = self.driver.find_element_by_class_name('layui-layercontent').text# msg = driver.find_element_by_class_name('layui-layercontent').textmsg =
DriverUtil.get_driver().find_element_by_class_name('layui-layer-content').textreturn msgclass DriverUtil(object):"""浏览器对象管理类"""__driver = None # 浏览器对象变量初始化状态@classmethoddef get_driver(cls):"""获取浏览器对象⽅法"""if cls.__driver is None:cls.__driver = webdriver.Chrome() # 浏览器类型cls.__driver.get('http://127.0.0.1/') # 项⽬地址cls.__driver.maximize_window() # 窗⼝最⼤化cls.__driver.implicitly_wait(10) # 隐式等待return cls.__driver@classmethoddef quit_driver(cls):"""退出浏览器对象⽅法"""if cls.__driver:sleep(3)cls.__driver.quit()cls.__driver = Noneif __name__ == '__main__':DriverUtil.get_driver() # 获取浏览器对象sleep(2)DriverUtil.quit_driver() # 退出浏览器对象
5. PO 设计模式
-
说明: PO 模式⼜可以叫 POM(P:Page O:Object), 是 UI ⾃动化测试中⼀个⾮常流⾏的设计模式(代码套路)
-
核⼼: 将元素定位及操作和业务逻辑, 拆分三个层⾯(每个层⾯对应⼀个单独的类), 然后通过调⽤完成最终的测试执⾏⾏为的过程
-
三个层⾯: 对象库层/操作层/业务层
6. v4 版本
6.1 PO⻚⾯元素封装步骤
-
- 对应⻚⾯创建⻚⾯ PO 代码⽂件, 命名规则: ⻚⾯功能_page.py, 例如⾸⻚: index_page.py
-
- 定义三个类: 对象层(XxxPage)/操作层(XxxHandle)/业务层(XxxTask)
-
- 对象层:
- 1> init ⽅法中获取浏览器对象
- 2> ⾃定义⽅法: 封装元素定位⽅法
- 3> 封装元素定位⽅法需要添加返回值!
-
- 操作层:
- 1> init ⽅法获取对象层对象, 根据类名写对象变量名
- 2> ⾃定义⽅法: 封装元素操作⽅法
-
- 业务层:
- 1> init ⽅法获取操作层对象, 根据类名写对象变量名
- 2> ⾃定义⽅法: 封装测试业务逻辑
-
- 在测试⽤例⽂件中, 实例化业务层对象, 调⽤测试业务⽅法, 执⾏测试
代码示例: ⾸⻚⻚⾯
"""
⾸⻚⻚⾯
"""
from utils import DriverUtilclass IndexPage(object):"""⾸⻚对象层"""def __init__(self):self.driver = DriverUtil.get_driver() # 获取浏览器对象def find_login(self):"""定位登录⽅法"""# self.driver.find_element_by_link_text('登录')# self.driver = DriverUtil.get_driver() # 获取浏览器对象return self.driver.find_element_by_link_text('登录')class IndexHandle(object):"""⾸⻚操作层"""def __init__(self):self.index_page = IndexPage() # 获取对象层对象def click_login(self):"""点击登录⽅法"""# element = IndexPage()# element.find_login().click()self.index_page.find_login().click()class IndexTask(object):"""⾸⻚业务层"""def __init__(self):self.index_handle = IndexHandle() # 获取操作层对象def go_to_login(self):"""跳转登录⻚⾯⽅法"""self.index_handle.click_login()
代码示例: 登录⻚⾯
"""
登录⻚⾯
"""
from utils import DriverUtilclass LoginPage(object):"""登录对象层"""def __init__(self):self.driver = DriverUtil.get_driver() # 获取浏览器对象def find_username(self):"""定位⽤户名⽅法"""return self.driver.find_element_by_id('username')def find_password(self):"""定位密码⽅法"""return self.driver.find_element_by_id('password')def find_verify_code(self):"""定位验证码⽅法"""return self.driver.find_element_by_id('verify_code')def find_login_btn(self):"""定位登录按钮⽅法"""return self.driver.find_element_by_name('sbtbutton')class LoginHandle(object):"""登录操作层"""def __init__(self):self.login_page = LoginPage() # 获取对象层对象def input_username(self, name):"""输⼊⽤户名⽅法"""self.login_page.find_username().send_keys(name)def input_password(self, pwd):"""输⼊密码⽅法"""self.login_page.find_password().send_keys(pwd)def input_verify_code(self, code):"""输⼊验证码⽅法"""self.login_page.find_verify_code().send_keys(code)def click_login_btn(self):"""点击登录按钮⽅法"""self.login_page.find_login_btn().click()class LoginTask(object):"""登录业务层"""def __init__(self):self.login_handle = LoginHandle() # 获取操作层对象def login_method(self, name, pwd, code):"""登录⽅法"""self.login_handle.input_username(name) # 输⼊⽤户名self.login_handle.input_password(pwd) # 输⼊密码self.login_handle.input_verify_code(code) # 输⼊验证码self.login_handle.click_login_btn() # 点击登录按钮
6.2 测试⽤例的最终代码样式
"""
整合多个脚本⾄同⼀个测试⽤例中
"""
import pytest
from utils import DriverUtil, get_alert_msg
from v4.index_page import IndexTask
from v4.login_page import LoginTaskclass TestLogin(object):"""登录测试类"""def setup_class(self):self.driver = DriverUtil.get_driver() # 获取浏览器对象self.index_task = IndexTask() # 实例化⾸⻚业务层对象self.login_task = LoginTask() # 实例化登录⻚⾯业务层对象def teardown_class(self):DriverUtil.quit_driver() # 退出浏览器对象def setup(self):# 打开⾸⻚self.driver.get('http://127.0.0.1/')# 点击登录# self.driver.find_element_by_link_text('登录').click()self.index_task.go_to_login() # 跳转登录def teardown(self):passdef test_account_does_not_exist(self):"""账号不存在测试⽅法"""# # 2. 输⼊⼀个不存在的⽤户名#
self.driver.find_element_by_id('username').send_keys('13811110001')# # 3. 输⼊密码#
self.driver.find_element_by_id('password').send_keys('123456')# # 4. 输⼊验证码#
self.driver.find_element_by_id('verify_code').send_keys('8888')# # 5. 点击登录按钮# self.driver.find_element_by_name('sbtbutton').click()self.login_task.login_method('13811110001', '123456', '8888')# 6. 获取错误提示信息msg = get_alert_msg() # 获取弹窗信息print('错误信息为:', msg)def test_wrong_password(self):"""密码错误测试⽅法"""# # 2. 输⼊⽤户名#
self.driver.find_element_by_id('username').send_keys('13800001111')# # 3. 输⼊错误密码#self.driver.find_element_by_id('password').send_keys('error')# # 4. 输⼊验证码#
self.driver.find_element_by_id('verify_code').send_keys('8888')# # 5. 点击登录按钮# self.driver.find_element_by_name('sbtbutton').click()self.login_task.login_method('13800001111', 'error', '8888')# 6. 获取错误提示信息msg = get_alert_msg() # 获取弹窗信息print('错误信息为:', msg)if __name__ == '__main__':pytest.main(['-s', 'tpshop_login.py'])
7. v5 版本
7.1 PO ⽂件对象层代码优化
class LoginPage(object):"""登录对象层"""def __init__(self):self.driver = DriverUtil.get_driver() # 获取浏览器对象# 说明: 将元素的定位⽅法及特征值封装成属性, 能够实现集中管理⽬标元素的定位⽅法及特征值self.name = (By.ID, 'username') # ⽤户名self.pwd = (By.ID, 'password') # 密码self.code = (By.ID, 'verify_code') # 验证码self.btn = (By.NAME, 'sbtbutton') # 登录按钮def find_username(self):"""定位⽤户名⽅法"""# return self.driver.find_element_by_id('username')# return self.driver.find_element(By.ID, 'username')return self.driver.find_element(self.name[0], self.name[1])def find_password(self):"""定位密码⽅法"""# return self.driver.find_element_by_id('password')return self.driver.find_element(self.pwd[0], self.pwd[1])def find_verify_code(self):"""定位验证码⽅法"""# return self.driver.find_element_by_id('verify_code')return self.driver.find_element(self.code[0], self.code[1])def find_login_btn(self):"""定位登录按钮⽅法"""# return self.driver.find_element_by_name('sbtbutton')return self.driver.find_element(self.btn[0], self.btn[1])
7.2 PO ⽂件操作层代码优化
class LoginHandle(object):"""登录操作层"""def __init__(self):self.login_page = LoginPage() # 获取对象层对象def input_username(self, name):"""输⼊⽤户名⽅法"""# 说明: 在执⾏输⼊操作前, 应该先执⾏清空操作self.login_page.find_username().clear()self.login_page.find_username().send_keys(name)def input_password(self, pwd):"""输⼊密码⽅法"""self.login_page.find_password().clear()self.login_page.find_password().send_keys(pwd)def input_verify_code(self, code):"""输⼊验证码⽅法"""self.login_page.find_verify_code().clear()self.login_page.find_verify_code().send_keys(code)def click_login_btn(self):"""点击登录按钮⽅法"""self.login_page.find_login_btn().click()