深入理解PO模型

无论是手工测试还是自动化测试,最核心的任务就是编写测试用例、执行测试用例、输出测试报告以及维护测试用例。因此,如何提高自动化测试效率就等同于如何提高自动化编写、执行、维护测试用例的效率。当下最流行的PO模型和关键字驱动模型都是为了解决此问题,本文主要介绍一下PO模型

PO模型介绍

全称PageObject,也叫做POM模型,它是一种设计思想,不是一种规范,是为了解决自动化测试过程中随代码量的增加导致代码冗余,难以维护、难以扩展等事件的方案。在PO模型下,每个页面都对应到一个类中,每一个类都维护着该页面中的元素集和操作这些元素的方法,使用此模型的目的是使架构解耦合,让程序松耦合

案例演示

以登录为例,文件no_PO_Object.py,使用PO模型对脚本代码进行优化

# no_PO_Object.py
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
import pytest

login = [["","654321","账号不能为空"],["test","123456","用户名不正确。"],
         ["admin","","密码不能为空"],["admin","654321","用户名或密码不正确"],["admin","123456","用户中心"]]

class TestUser(object):
    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()
        
    def teardown_class(self):
        self.driver.quit()

    @pytest.mark.parametrize("username,pwd,expected",login)
    def test_user_login(self,username,pwd,expected):
        self.driver.get("http://192.166.66.22:8080/user/login")

        # 清空输入框后输入用户名
        self.driver.find_element(By.NAME, "user").clear()
        self.driver.find_element(By.NAME, "user").send_keys(username)
        # 清空输入框后输入密码
        self.driver.find_element(By.NAME, "pwd").clear()
        self.driver.find_element(By.NAME, "pwd").send_keys(pwd)
        # 点击【登录】
        self.driver.find_element(By.CLASS_NAME, "btn").click()
        if username == "admin" and pwd == "123456":
            # 等待页面加载
            WebDriverWait(self.driver, 3).until(EC.title_is(expected))
            # 验证是否登录成功,根据当前页面标题进行判断
            assert self.driver.title == expected
        else:
            # 等待页面加载
            WebDriverWait(self.driver, 3).until(EC.alert_is_present())
            alert = self.driver.switch_to.alert
            # 验证报错信息是否正确
            assert alert.text == expected
            alert.accept()

将代码中容易变动的信息都拿出来,单独写个类中loginPage.py,若前端属性值、元素或定位方式发生改变,只需要在此模块进行更改

# loginPage.py
from selenium.webdriver.common.by import By
class loginPage():
    # 定义一个构造方法,初始化变量,在实例化时传入一个driver参数
    def __init__(self,driver):
        self.driver = driver

    # 抽出定位和元素的属性值
    user = (By.NAME, "user")
    pwd = (By.NAME, "pwd")
    btn = (By.CLASS_NAME, "btn")
    url = "http://192.166.66.22:8080/user/login"

    # 加载项目地址
    def user_loginURL(self):
        self.getURL(self.url)

    # 元素操作
    # 清空输入框后输入用户名
    def user_input(self,username):
        self.driver.find_element(*loginPage.user).clear()
        self.driver.find_element(*loginPage.user).send_keys(username)
    # 清空输入框后输入密码
    def pwd_input(self,pwd):
        self.driver.find_element(*loginPage.pwd).clear()
        self.driver.find_element(*loginPage.pwd).send_keys(pwd)
    # 点击登录按钮
    def btn_click(self):
        self.driver.find_element(*loginPage.btn).click()
        
    # 绑定业务函数
    # 对于有关联关系的操作可以进行业务绑定,比如登录
    def login(self,username,pwd):
        self.user_loginURL()
        self.user_input(username)
        self.pwd_input(pwd)
        self.btn_click()

测试用例loginCase.py的类中直接调用loginPage.py中的方法

# loginCase.py
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
import pytest
from PO.BasePage.loginPage import loginPage

login = [["","654321","账号不能为空"],["test","123456","用户名不正确。"],
         ["admin","","密码不能为空"],["admin","654321","用户名或密码不正确"],["admin","123456","用户中心"]]

class TestUser(object):
    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()
    
    def teardown_class(self):
        self.driver.quit()

    @pytest.mark.parametrize("username,pwd,expected",login)
    def test_user_login(self,username,pwd,expected):

        # 把用例层面实例的driver传到loginPage中
        self.login = loginPage(self.driver)

        self.login.loginURL()			# 访问项目地址
        self.login.user_input(username)	# 输入用户名
        self.login.pwd_input(pwd)		# 输入密码
        self.login.btn_click()			# 点击登录
        # 上面loginPage.py中的业务绑定函数,此处可直接调用,省去调用上面4步单独的元素调用
        # self.login.login(username,pwd)

        if username == "admin" and pwd == "123456":
            # 等待页面加载
            WebDriverWait(self.driver, 3).until(EC.title_is(expected))
            # 验证是否登录成功,根据当前页面标题进行判断
            assert self.driver.title == expected
        else:
            # 等待页面加载
            WebDriverWait(self.driver, 3).until(EC.alert_is_present())
            alert = self.driver.switch_to.alert
            # 验证报错信息是否正确
            assert alert.text == expected
            alert.accept()

至此代码就实现了下图中的转变方式,no_PO_Object.py转为loginPage.py+loginCase.py

请添加图片描述

通过上面的修改可以看到,在loginPage.py中出现重复的定位元素操作,若页面元素较多,或进行多个页面操作时,会出现更多的重复定位操作,所以创建basePage.py,对页面操作进一步优化,抽取页面中的公共方法

# basePage.py
class Browser_init():
    def __init__(self,driver):
        self.driver = driver

    # 加载项目地址
    def getURL(self,url):
        self.driver.get(url)

    # 元素定位
    def locate_ele(self,locator):
        ele = self.driver.find_element(*locator)
        return ele

    # 元素文本清空
    def locate_clear(self,locator):
        self.locate_ele(locator).clear()

    # 元素文本输入
    def locate_input(self,locator,text):
        self.locate_ele(locator).send_keys(text)

    # 元素点击
    def locate_click(self,locator):
        self.locate_ele(locator).click()

loginPage.py中的代码修改如下

# loginPage.py
from selenium.webdriver.common.by import By
from PO.BasePage.basePage import Browser_init

class loginPage(Browser_init):
    
    # 抽出定位和元素的属性值
    user = (By.NAME, "user")
    pwd = (By.NAME, "pwd")
    btn = (By.CLASS_NAME, "btn")
    url = "http://192.166.66.22:8080/user/login"

    # 加载项目地址
    def user_loginURL(self):
        self.getURL(self.url)

    # 元素操作
    # 清空输入框后输入用户名
    def user_input(self,username):
        self.locate_clear(loginPage.user)
        self.locate_input(loginPage.user,username)
    # 清空输入框后输入密码
    def pwd_input(self,pwd):
        self.locate_clear(loginPage.pwd)
        self.locate_input(loginPage.pwd,pwd)

    # 点击登录按钮
    def btn_click(self):
        self.locate_click(loginPage.btn)

    # 绑定业务函数
    def login(self,username,pwd):
        self.user_loginURL()
        self.user_input(username)
        self.pwd_input(pwd)
        self.btn_click()

loginCase.py的代码如下

# loginCase.py
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
import pytest
from PO.BasePage.loginPage import loginPage

login = [["","654321","账号不能为空"],["test","123456","用户名不正确。"],
         ["admin","","密码不能为空"],["admin","654321","用户名或密码不正确"],["admin","123456","用户中心"]]

class TestUser(object):
    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()
        
    def teardown_class(self):
        self.driver.quit()

    @pytest.mark.parametrize("username,pwd,expected",login)
    def test_user_login(self,username,pwd,expected):

        # 把用例层面实例的driver传到loginPage中
        self.login = loginPage(self.driver)

        self.login.login(username,pwd)

        if username == "admin" and pwd == "123456":
            # 等待页面加载
            WebDriverWait(self.driver, 3).until(EC.title_is(expected))
            # 验证是否登录成功,根据当前页面标题进行判断
            assert self.driver.title == expected
        else:
            # 等待页面加载
            WebDriverWait(self.driver, 3).until(EC.alert_is_present())
            alert = self.driver.switch_to.alert
            # 验证报错信息是否正确
            assert alert.text == expected
            alert.accept()

最终,源代码一分为三,可理解为基础层、页面层和业务层

  • 基础层:BasePage,封装了基础的Selenium的原生方法,如定位元素、输入输出、元素点击等一些控件操作
  • 页面层:loginPage,存放一些封装好的功能用例模块,继承基础层中的操作
  • 业务层:loginCase,真正的测试用例的操作,用例的业务逻辑以及数据驱动,调用页面层中的方法

请添加图片描述

三者关系如图所示

请添加图片描述

通过以上对代码的拆分,可以看出PO模型的优缺点:

优点:

  • 提升代码可维护性,PO模型是一种业务流程与页面元素操作分离的模式,使得测试代码更加清晰整洁,利于代码的可维护性
  • 提高代码可读性,因为PO模型对代码进行了分层,而且也会集中管理一个页面内的公共方法,利于用例编写及代码可读性
  • 提升代码复用性,页面对象和用例之间的相互分离,使得测试人员可集中管理元素对象

缺点:

  • 由于根据项目流程进行了模块化处理, 造成项目结构复杂化

当然,对于以上代码仍可以进行细分,所以再次提醒,PO模型是一种思想,不是一种规范,每个人写出来的方式可能都不一样,没有对错之分,对于PO模型仁者见仁,智者见智

猜你喜欢

转载自blog.csdn.net/Q0717168/article/details/120942175
今日推荐