PO模式
PO模式学习思路
采用版本迭代的方式来学习,便于对不同版本的优缺点进行对比和理解
1、V1:不使用任何涉及模式和单元测试框架
2、V2:使用UnitTest管理用例
3、V3:使用方法封装的思想,对代码进行优化
4、V4:使用PO模式的分层思想对代码进行拆分
5、V5:对PO分层之后的代码继续优化
6、V6:PO模式深入封装,把共同操作提取封装到父类中,子类直接调用父类的方法
1、V1版本
不使用任何设计模式和单元测试框架。
每个文件里编写一个用例,完全的面向过程的编程方式。
1.1 存在的问题
1、一条测试用例对应一个文件,用例较多时不方便管理维护
2、代码高度冗余
1.2 示例代码
登录功能-账号不存
from selenium import webdriver
# 创建浏览器驱动对象,并完成初始化操作
driver = webdriver.Firefox()
driver.maximize_window()
driver.implicitly_wait(10)
driver.get("http://localhost")
"""
登录功能-账号不存在
"""
# 点击首页的‘登录’链接,进入登录页面
driver.find_element_by_link_text("登录").click()
# 输入用户名
driver.find_element_by_id("username").send_keys("13099999999")
# 输入密码
driver.find_element_by_id("password").send_keys("123456")
# 输入验证码
driver.find_element_by_id("verify_code").send_keys("8888")
# 点击‘登录’按钮
driver.find_element_by_name("sbtbutton").click()
# 获取提示信息
msg = driver.find_element_by_class_name("layui-layer-content").text
print("msg=", msg)
# 关闭驱动对象
driver.quit()
登录功能-密码错误
from selenium import webdriver
# 创建浏览器驱动对象,并完成初始化操作
driver = webdriver.Firefox()
driver.maximize_window()
driver.implicitly_wait(10)
driver.get("http://localhost")
"""
登录功能-密码错误
"""
# 点击首页的‘登录’链接,进入登录页面
driver.find_element_by_link_text("登录").click()
# 输入用户名
driver.find_element_by_id("username").send_keys("13012345678")
# 输入密码
driver.find_element_by_id("password").send_keys("error")
# 输入验证码
driver.find_element_by_id("verify_code").send_keys("8888")
# 点击‘登录’按钮
driver.find_element_by_name("sbtbutton").click()
# 获取提示信息
msg = driver.find_element_by_class_name("layui-layer-content").text
print("msg=", msg)
# 关闭驱动对象
driver.quit()
2、V2版本
使用UnitTest管理用例,并断言用例的执行结果
2.1 引入UnitTest的好处
1、方便组织、管理多个测试用例
2、提供了丰富的断言方法
3、方便生成测试报告
4、减少了代码的冗余
2.2 存在的问题
代码冗余
2.3 示例代码
import unittest
from selenium import webdriver
class TestLogin(unittest.TestCase):
"""
对登录模块的功能进行测试
"""
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Firefox()
cls.driver.maximize_window()
cls.driver.implicitly_wait(10)
cls.driver.get("http://localhost")
@classmethod
def tearDownClass(cls):
cls.driver.quit()
def setUp(self):
# 打开首页
self.driver.get("http://localhost")
# 点击首页的‘登录’链接,进入登录页面
self.driver.find_element_by_link_text("登录").click()
# 账号不存在
def test_login_username_is_error(self):
# 输入用户名
self.driver.find_element_by_id("username").send_keys("13099999999")
# 输入密码
self.driver.find_element_by_id("password").send_keys("123456")
# 输入验证码
self.driver.find_element_by_id("verify_code").send_keys("8888")
# 点击‘登录’
self.driver.find_element_by_name("sbtbutton").click()
# 断言提示信息
msg = self.driver.find_element_by_class_name("layui-layer-content").text
print("msg=", msg)
self.assertIn("账号不存在", msg)
# 密码错误
def test_login_password_is_error(self):
# 输入用户名
self.driver.find_element_by_id("username").send_keys("13012345678")
# 输入密码
self.driver.find_element_by_id("password").send_keys("error")
# 输入验证码
self.driver.find_element_by_id("verify_code").send_keys("8888")
# 点击‘登录’
self.driver.find_element_by_name("sbtbutton").click()
# 断言提示信息
msg = self.driver.find_element_by_class_name("layui-layer-content").text
print("msg=", msg)
self.assertIn("密码错误", msg)
3、V3版本
3.1 方法封装
方法封装:是将一些有共性的或多次被使用的代码提取到一个方法中,供其他地方调用。
封装的好处:
1、避免代码冗余
2、容易维护
3、隐藏代码实现的细节
目的:用最少的代码实现最多的功能
V3版本:使用方法封装的思想,对代码进行优化
1、定义获取驱动对象的工具类
2、封装“获取弹出框的提示信息等”
4、PO模式介绍
4.1 存在的问题
在做UI自动化时定位元素特别依赖页面,一旦页面发生变更就不得不跟着去修改定位元素的代码。
举例:假设要对一个元素进行点击操作,而且会经常对该元素进行操作,那么你就可能会编写多
处如下代码
driver.find_element_by_id("login-btn").click()
存在的问题:
1、如果开发人员修改了这个元素的id,这时候你就不得不修改所有队形的代码
2、存在大量的冗余代码
思考;
如何来解决这个问题?
4.2 PO模式
PO是Page Object的缩写,PO模式是自动化测试项目开发实践的最佳设计模式之一。
核心思想是通过对界面元素的封装减少冗余代码,同时在后期维护中,若元素定位发生变化,
只需要调整页面元素封装的代码,提高测试用例的可维护性、可读性。
4.2.1 PO模式层级
PO模式可以把一个页面分为三层,对象库层、操作层、业务层。
1、对象库层:封装定位元素的方法
2、操作层:封装对元素的操作
3、业务层:将一个或多个操作组合起来完成一个业务功能。比如登录:需要输入账号、密码、点击登录三个操作。
4.2.2 引入PO模式的好处
引入PO模式前:
1、存在大量的冗余代码
2、业务流程不清晰
3、后期维护成本大
引入PO模式后:
1、减少代码冗余
2、业务代码和测试代码被分开,降低耦合性
3、维护成本低
4.3 PO模式实践
V4版本:采用PO模式的分层思想对代码进行拆分
PO分层封装:
1、base(基层):page页面的一些公共的方法;
#Base类
# 初始化方法
# 查找元素方法
# 点击元素方法
# 输入方法
# 获取文本方法
# 截图方法
注意:
1、以上方法封装的时候,解包只需一处,在查找元素解包;
2、driver为虚拟,谁调用base时,谁传入,无需关注从哪里来;
3、loc:真正使用loc的方法只有在查找元素方法使用;
2、page(页面对象):一个页面封装成一个对象
应用:继承base;
实现:
1、模块名:page+实际操作模块名称 如:page_login.py
2. 页面对象名:以大驼峰方法将模块名抄进来,有下划线去掉下划线
3. 方法:涉及元素,将每个元素操作单独封装一个操作方法;
4. 组装:根据需求组装以上操作步骤;
3、scripts(业务层):导包调用 page页面
实现:
1. 模块:test+实际操作模块名称 如:test_login.py
2. 测试业务名称:以大驼峰方法将模块名抄进来,有下划线去掉下划线
3. 方法:
1. 初始化方法 setUp() 注:在unittest框架中不能使用def __init__()初始化方法;
# 实例化 页面对象
# 前置操作:如:打开等等
2. 结束方法 teardown
# 关闭驱动
3. 测试方法
# 根据要操作的业务来实现
扩展:
loc变量:类型为元组,*loc为解包;
4.3.1 示例代码
目录
"""base.py 基类”“”
from time import strftime
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
class Base:
# 1、初始化方法
def __init__(self):
# self.driver = driver
# 获取driver
self.driver = webdriver.Firefox()
# 最大化浏览器
self.driver.maximize_window()
# 打开URL
self.driver.get("https://10.66.210.198:8443")
# 2和3有个共同的方法,查找元素方法
def base_find_element(self, loc, timeout=30, poll=0.5):
# 使用显示等待
"""
:param loc: 元素的配置信息,格式为元组 如:login_link = By.PartialLink, "登录"
:param timeout: 默认超时时间为30s,可以通过传入参数进行修改
:param poll: 默认访问频率0.5s
:return: 返回查找到的元素
"""
return WebDriverWait(self.driver,
timeout=timeout,
poll_frequency=poll).until(lambda x: x.find_element(*loc))
# 2、点击元素
def base_click(self, loc):
# 获取元素并进行点击
self.base_find_element(loc).click()
# 3、输入方法
def base_input(self, loc, value):
# 获取元素
el = self.base_find_element(loc)
# 清空
el.clear()
# 输入
el.send_keys(value)
# 4、获取文本信息
def base_get_msg(self, loc):
msg = self.base_find_element(loc).text
# 5、截图
def base_get_image(self):
self.driver.get_screenshot_as_file("{}.png".format(strftime("%Y_%m_%d_%H_%M_%S")))
”“”loc变量 __init__.py“”“
from selenium.webdriver.common.by import By
"""以下为登录页面元素配置信息"""
# 用户名
login_username = By.CSS_SELECTOR, "#username"
# 密码
login_pwd = By.ID, "pass"
# 登录按钮
login_btn = By.CSS_SELECTOR, ".submit"
# 获取异常文本信息
login_error_msg = By.CSS_SELECTOR, ".error-info"
# print(type(login_btn)) <class 'tuple'>
“”“page.py 页面对象层”“”
from base.base import Base
import page
class PageLogin(Base):
# 输入 用户名 封装
def page_input_username(self, username):
self.base_input(page.login_username, username)
# 输入 密码 封装
def page_input_pwd(self, pwd):
self.base_input(page.login_pwd, pwd)
# 点击 登录 封装
def page_click_login_btn(self):
self.base_click(page.login_btn)
# 获取登录异常信息
def page_error_login_msg(self):
return self.base_get_msg(page.login_error_msg)
# 截图
def page_get_screenshot(self):
self.base_get_image()
# 组装 封装
def page_login(self, username, pwd):
self.page_input_username(username)
self.page_input_pwd(pwd)
self.page_click_login_btn()
”“”test_login.py 业务层“”“
import unittest
from time import sleep
from page.page_login import PageLogin
from base.base import Base
from parameterized import parameterized
def get_data():
return [("admin", "123@123", "密码解密失败,请重新输入!!")]
# 新建测试类 并继承
class TestLogin(unittest.TestCase):
# setUP
def setUp(self):
self.login = PageLogin()
# tearDwon
def tearDown(self):
# 退出浏览器驱动
sleep(5)
self.login.driver.quit()
# 登录测试方法
@parameterized.expand(get_data())
def test_login(self, username, pwd, expect_result):
# 调用登录方法
self.login.page_login(username, pwd)
# 获取登录提示信息
msg = self.login.page_error_login_msg()
print(msg)
try:
# 断言
self.assertEqual(msg, expect_result)
sleep(5)
except AssertionError:
# 截图
sleep(5)
self.login.page_get_screenshot()