PO模式项目实战思路分析

PO模式学习思路

采用版本迭代的方式来学习, 便于对不同版本的优缺点进行对比和理解
        V1: 不使用任何设计模式和单元测试框架(线性模型)
        V2: 采用测试框架,使用UnitTest管理用例
        V3: 业务代码和页面对象进行分离,使用方法封装的思想, 对代码进行优化
        V4: 实际中的PO模式编写,PO模式深入封装, 把共同操作提取封装到父类中,
                 子类直接调用父类的方法

选择的测试用例

对项目的登录模块进行自动化测试

账号不存在
1. 点击首页的‘登录’链接, 进入登录页面
2. 输入一个不存在的用户名
3. 输入密码
4. 输入验证码
5. 点击登录按钮
6. 获取错误提示信息

密码错误
1. 点击首页的‘登录’链接, 进入登录页面
2. 输入用户名
3. 输入一个错误的密码
4. 输入验证码
5. 点击登录按钮
6. 获取错误提示信息

V1版本

不使用任何设计模式和单元测试框架,线性模型。
每个文件里编写一个用例, 完全的面向过程的编程方式。

存在的问题

  • 一条测试用例对应一个文件, 用例较多时不方便管理维护
  • 代码高度冗余
  • 无法批量执行

代码结构

文件夹V1

文件:test_login_username_not_exist.py

# 导包
from selenium import webdriver
from selenium.webdriver.common.by import By
# 获取driver对象
driver = webdriver.Chrome()
# 最大化浏览器
driver.maximize_window()
# 隐式等待
driver.implicitly_wait(30)
# 打开url
driver.get("http://localhost")
# 点击登录连接
driver.find_element(By.PARTIAL_LINK_TEXT, "登录").click()
# 输入用户名
driver.find_element(By.CSS_SELECTOR, "#username").send_keys("13011112222")
# 输入密码
driver.find_element(By.CSS_SELECTOR, "#password").send_keys("123456")
# 输入验证码
driver.find_element(By.CSS_SELECTOR, "#verify_code").send_keys("8888")
# 点击登录按钮
driver.find_element(By.CSS_SELECTOR, ".J-login-submit").click()
# 获取错误提示信息
msg = driver.find_element(By.CSS_SELECTOR, ".layui-layer-content").text
print("msg:", msg)
# 断言
assert msg == "账号不存在!"
# 点击提示框确定按钮
driver.find_element(By.CSS_SELECTOR, ".layui-layer-btn0").click()
# 关闭
driver.quit()

文件:test_login_password_err.py

# 导包
from selenium import webdriver
from selenium.webdriver.common.by import By
# 获取driver对象
driver = webdriver.Chrome()
# 最大化浏览器
driver.maximize_window()
# 隐式等待
driver.implicitly_wait(30)
# 打开url
driver.get("http://localhost")
# 点击登录连接
driver.find_element(By.PARTIAL_LINK_TEXT, "登录").click()
# 输入用户名
driver.find_element(By.CSS_SELECTOR, "#username").send_keys("2296288678@qq.com")
# 输入密码
driver.find_element(By.CSS_SELECTOR, "#password").send_keys("123123")
# 输入验证码
driver.find_element(By.CSS_SELECTOR, "#verify_code").send_keys("8888")
# 点击登录按钮
driver.find_element(By.CSS_SELECTOR, ".J-login-submit").click()
# 获取错误提示信息
msg = driver.find_element(By.CSS_SELECTOR, ".layui-layer-content").text
print("msg:", msg)
# 断言
assert msg == "密码错误!"
# 点击提示框确定按钮
driver.find_element(By.CSS_SELECTOR, ".layui-layer-btn0").click()
# 关闭
driver.quit()

 V2版本

使用UnitTest管理用例, 并断言用例的执行结果

引入UnitTest的好处

  • 方便组织、 管理多个测试用例,可以批量运行
  • 提供了丰富的断言方法
  • 方便生成测试报告
  • 减少了代码冗余

存在的问题

  • 代码冗余
  • 业务代码和页面对象都在同一个文件,维护成本高

代码结构

文件夹:V2

文件:test_login_setUpClass.py

# 导包
import unittest
from time import sleep

from selenium import webdriver
from selenium.webdriver.common.by import By


# 新建测试类 并继承
class TestLogin(unittest.TestCase):

    driver = None
    # 初始化 setUp
    @classmethod
    def setUpClass(cls):
        # 获取driver对象
        cls.driver = webdriver.Chrome()
        # 最大化浏览器
        cls.driver.maximize_window()
        # 隐式等待
        cls.driver.implicitly_wait(30)
        # 打开url
        cls.driver.get("http://localhost")
        # 点击登录连接
        cls.driver.find_element(By.PARTIAL_LINK_TEXT, "登录").click()

    # 结束 tearDown
    @classmethod
    def tearDownClass(cls):
        # 关闭浏览器
        cls.driver.quit()

    # 新建测试方法 用户名不存在
    def test_login_username_not_exist(self):
        driver = self.driver
        # 输入用户名
        username = driver.find_element(By.CSS_SELECTOR, "#username")
        username.clear()
        username.send_keys("13011112222")
        # 输入密码
        pwd = driver.find_element(By.CSS_SELECTOR, "#password")
        pwd.clear()
        pwd.send_keys("123456")
        # 输入验证码
        code = driver.find_element(By.CSS_SELECTOR, "#verify_code")
        code.clear()
        code.send_keys("8888")
        # sleep(2)
        # 点击登录按钮
        driver.find_element(By.CSS_SELECTOR, ".J-login-submit").click()
        # sleep(2)
        # 获取错误提示信息
        msg = driver.find_element(By.CSS_SELECTOR, ".layui-layer-content").text
        print("msg:", msg)
        try:
            # 断言
            self.assertEqual(msg, "账号不存在!")
            # 点击提示框确定按钮
            driver.find_element(By.CSS_SELECTOR, ".layui-layer-btn0").click()
        except AssertionError:
            # 截图
            driver.get_screenshot_as_file("../image/fail.png")

    # 新建测试方法 密码错误
    def test_login_password_err(self):
        driver = self.driver
        # 输入用户名
        username = driver.find_element(By.CSS_SELECTOR, "#username")
        username.clear()
        username.send_keys("2296288678@qq.com")
        # 输入密码
        pwd = driver.find_element(By.CSS_SELECTOR, "#password")
        pwd.clear()
        pwd.send_keys("123123")
        # 输入验证码
        code = driver.find_element(By.CSS_SELECTOR, "#verify_code")
        code.clear()
        code.send_keys("8888")
        # sleep(2)
        # 点击登录按钮
        driver.find_element(By.CSS_SELECTOR, ".J-login-submit").click()
        # sleep(2)
        # 获取错误提示信息
        msg = driver.find_element(By.CSS_SELECTOR, ".layui-layer-content").text
        print("msg:", msg)
        try:
            # 断言
            self.assertEqual(msg, "密码错误!")
            # 点击提示框确定按钮
            driver.find_element(By.CSS_SELECTOR, ".layui-layer-btn0").click()
        except AssertionError:
            # 截图
            driver.get_screenshot_as_file("../image/fail.png")

V3版本

使用方法封装的思想, 对代码进行优化。

  • 定义获取驱动对象的工具类
  • 封装“获取弹出框的提示消息”

方法封装

方法封装: 是将一些有共性的或多次被使用的代码提取到一个方法中, 供其他地方调用。

封装的好处:

  • 避免代码冗余
  • 容易维护
  • 隐藏代码实现的细节

目的: 用最少的代码实现最多的功能

PO介绍

不用PO的问题

在做UI自动化时定位元素特别依赖页面, 一旦页面发生变更就不得不跟着去修改定位元素的代码。

举例: 假设要对一个元素进行点击操作, 而且会经常对该元素进行操作, 那么你就可能会编写多处如下代码
        driver.find_element_by_id("login-btn").click()

存在的问题:

  • 如果开发人员修改了这个元素的id, 这时候你就不得不修改所有对应的代码
  • 存在大量冗余代码
PO模式

含义:

PO是Page(页面) Object(对象)的缩写

核心思想是通过对界面元素的封装减少冗余代码

在后期维护中, 若元素定位发生变化, 只需要调整页面元素封装的代码, 提高测试用例的可维护性、 可读性

关键操作:

  • 一个页面封装一个Class
  • 每一步操作步骤单独封装一个方法
  •  模块名:page_实意单词,例如page_login
  • 类名:使用大驼峰将模块名称抄进来,有下滑线去掉下划线
  • 方法名: page_XXX
  • 根据业务需求是否要组装操作步骤,单独组装一个操作方法
引入PO模式的好处

引入PO模式前

  • 存在大量冗余代码
  • 业务流程不清晰
  • 后期维护成本大

引入PO模式后

  • 减少冗余代码
  • 业务代码和测试代码被分开, 降低耦合性
  • 维护成本低

代码结构

文件夹:V3

        文件夹page:

        文件:page_login.py

"""
页面对象层:
    页面对象编写技巧:
        类名:使用大驼峰将模块名称抄进来,有下滑线去掉下划线
        方法:根据业务需求每个操作步骤单独封装一个方法
            方法名 page_XXX

"""
from selenium import webdriver
from selenium.webdriver.common.by import By


class PageLogin:
    def __init__(self):
        # 获取driver对象
        self.driver = webdriver.Chrome()
        # 最大化浏览器
        self.driver.maximize_window()
        # 隐式等待
        self.driver.implicitly_wait(30)
        # 打开url
        self.driver.get("http://localhost")

    # 点击登录 连接
    def page_click_login_link(self):
        self.driver.find_element(By.PARTIAL_LINK_TEXT, "登录").click()

    # 输入用户名
    def page_input_username(self, username):
        self.driver.find_element(By.CSS_SELECTOR, "#username").send_keys(username)

    # 输入密码
    def page_input_pwd(self, pwd):
        self.driver.find_element(By.CSS_SELECTOR, "#password").send_keys(pwd)


    # 输入验证码
    def page_input_verify_code(self, code):
        self.driver.find_element(By.CSS_SELECTOR, "#verify_code").send_keys(code)

    # 点击登录按钮
    def page_click_login_btn(self):
        self.driver.find_element(By.CSS_SELECTOR, ".J-login-submit").click()

    # 获取异常提示信息
    def page_get_text(self):
        return self.driver.find_element(By.CSS_SELECTOR, ".layui-layer-content").text

    # 点击提示框确定按钮
    def page_click_err_btn_ok(self):
        self.driver.find_element(By.CSS_SELECTOR, ".layui-layer-btn0").click()

    # 组装登录业务方法  给业务层调用
    def page_login(self, usrename, pwd, code):
        self.page_click_login_link()
        self.page_input_username(usrename)
        self.page_input_pwd(pwd)
        self.page_input_verify_code(code)
        self.page_click_login_btn()

        文件夹:scripts

        文件:test_login.py

# 导包
import unittest

from parameterized import parameterized

from v3.page.page_login import PageLogin


# 新建测试类
class TestLogin(unittest.TestCase):
    # 初始化方法
    def setUp(self):
        # 获取登录页面对象
        self.login = PageLogin()

    # 结束方法
    def tearDown(self):
        # 关闭驱动对象
        self.login.driver.quit()

    # 新建测试方法
    @parameterized.expand([("13822223333","123456","8888","账号不存在!"), ("13800001111","123123","8888","密码错误!")])
    def test_login(self, username, pwd, code, expect):
        # 调用测试登录方法
        self.login.page_login(username, pwd, code)
        # 获取登录后的信息
        msg = self.login.page_get_text()
        try:
            # 断言
            self.assertEqual(msg, expect)
            # 点击确定
            self.login.page_click_err_btn_ok()
        except AssertionError:
            # 截图
            pass

V4版本

采用PO模式的分层思想对代码进行拆分

PO模式可以把一个页面分为三层, 对象库层、 操作层、 业务层。

  • 对象库层(base): 封装定位元素的方法
  • 操作层(page): 封装对元素的操作
  • 业务层scrips/case: 将一个或多个操作组合起来完成一个业务功能。 比如登录: 需要输入帐号、 密码、 点击登录三个操作

代码结构

文件夹:V4

base(基类):page页面一些公共的方法

文件夹:base

文件:base.py

from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait

# base(基类):page页面一些公共的方法
class Base:
    # 初始化方法
    def __init__(self,driver):
        # driver为虚拟,谁调用base时,谁传入,无需关注从哪里来 
        self.driver = driver
        # 临时代替driver
        # self.driver = webdriver.Chrome
        self.driver.maximize_window()
        self.driver.get("http://localhost")

    # 查找元素方法 (提供:点击、输入、获取文本)使用
    def base_find_element(self, loc, timeout=30, poll=0.5):
        return WebDriverWait(self.driver, timeout=timeout, poll_frequency=poll).until(lambda x: x.find_element(*loc))

    # 点击元素方法
    def base_click(self, loc):
        self.base_find_element(loc).click()

    # 输入方法
    def base_input(self, loc, value):
        el = self.base_find_element(loc)
        # 清空
        el.clear()
        # 输入
        el.send_keys(value)

    # 获取文本方法
    def base_get_text(self, loc):
        # 注意:一定要返回元素的文本信息
        return self.base_find_element(loc).text

    # 截图方法
    def base_get_image(self):
        self.driver.get_screenshot_as_file("../iamge/fail.png")

# 以上方法封装的时候,解包只需一次,在查找元素解包

page(页面对象):一个页面封装成一个对象--继承base

  • page包下面的__init__.py存放基本的配置数据,
  • page_login.py直接导包即可以通过page.来使用
  • 或者导包之后通过继承的的方法在类里面直接通过self.调用 

文件夹:page

 文件:__init__.py

from selenium.webdriver.common.by import By

"""以下为登录页面元素配置信息"""
# 登录链接
login_link = By.PARTIAL_LINK_TEXT, "登录"
# 用户名
login_username = By.ID, "username"
# 密码
login_pwd = By.ID, "password"
# 验证码
login_verify_code = By.ID, "verify_code"
# 登录按钮
login_btn = By.CSS_SELECTOR, ".J-login-submit"
# 获取异常文本信息
login_err_info = By.CSS_SELECTOR, ".layui-layer-content"
# 点击异常提示框 按钮
login_err_btn_ok = By.CSS_SELECTOR, ".layui-layer-btn0"

"""以下为订单页面配置数据"""

文件:page_login.py

from v4 import page
from v4.base.base import Base


class PageLogin(Base):
    # 点击登录链接
    def page_click_login_link(self):
        self.base_click(page.login_link)

    # 输入用户名
    def page_input_username(self, username):
        self.base_input(page.login_username, username)

    # 输入密码
    def page_input_password(self, pwd):
        self.base_input(page.login_pwd, pwd)

    # 输入验证
    def page_input_verify_code(self, code):
        self.base_input(page.login_verify_code, code)

    # 点击登录按钮
    def page_click_login_btn(self):
        self.base_click(page.login_btn)

    # 获取异常提示信息
    def page_get_error_info(self):
        return self.base_get_text(page.login_err_info)

    # 点击异常信息框 确定
    def page_click_err_btn_ok(self):
        self.base_click(page.login_err_btn_ok)

    # 截图
    def page_get_img(self):
        self.base_get_image()

    # 组合业务方法
    def page_login(self, username, pwd, code):
        self.page_input_username(username)
        self.page_input_password(pwd)
        self.page_input_verify_code(code)
        self.page_click_login_btn()

3、scrips(业务层):导包调用page页面 

初始化方法    setUp()  注:在unittest框架中不能使用def __init__()初始化方法;

  • 实例化页面对象
  • 前置操作  如:打开等等

结束方法    teardown()

  • 关闭驱动

测试方法

  • 根据要操作的业务来实现 

文件夹: scripts

文件:test_login.py

# 导包
import unittest
from v4.page.page_login import PageLogin
from parameterized import parameterized


def get_data():
    return [("13822223333", "123456", "8888", "账号不存在!"),
            ("13800001111", "123123", "8888", "密码错误!")]


# 新建测试类 并 继承
class TestLogin(unittest.TestCase):
    login = None
    # setUp

    @classmethod
    def setUpClass(cls):
        # 实例化 获取页面对象 PageLogin
        cls.login = PageLogin()
        # 点击登录连接
        cls.login.page_click_login_link()

    # tearDown
    @classmethod
    def tearDownClass(cls):
        # 关闭 driver驱动对象
        cls.login.driver.quit()

    # 登录测试方法
    @parameterized.expand(get_data())
    def test_login(self, username, pwd, code, expect_result):
        # 调用登录方法
        self.login.page_login(username, pwd, code)
        # 获取登录提示信息
        msg = self.login.page_get_error_info()
        try:
            # 断言
            self.assertEqual(msg, expect_result)
        except AssertionError:
            # 截图
            self.login.page_get_img()

拓展:元组解包

loc变量为元组,*loc为解包

from selenium.webdriver.common.by import By
driver = None
# loc = (By.CSS_SELECTOR, ".telA")
loc = By.CSS_SELECTOR, ".telA"
# driver.find_element(By.CSS_SELECTOR, ".telA")
print(type(loc))
print("未解包之前:", loc)
print("解包之后:", *loc)

运行结果:解包之后,单引号被去掉了 

  

# 查找元素方法
    def base_find_element(self, loc, timeout=30, poll=0.5):
        # 使用显示等待
        """
        :param loc: 元素的配置信息,格式为元组 如:login_link = By.PartialLink, "登录"
        :param timeout: 默认超时时间为30,可以通过传入参数进行修改
        :param poll: 默认访问频率 0.5秒
        :return: 查找到的元素
        """
        return WebDriverWait(self.driver,
                             timeout=timeout,
                             poll_frequency=poll).until(lambda x: x.find_element(*loc))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值