天天看点

UI自动化之PO模型

PO模型

    • 简述
    • 测试框架发展
    • 设计分层
    • 设计理念
    • po设计实现
    • 示例
    • 总结思考

简述

PageObject(页面即对象),PO模式是UI自动化测试实现中广泛使用的模式,它基本上由应用程序页面元素和类之间的映射组成;它还使用其元素在该页面上定义了用户操作;po模式和工具(selenium)结合使用时,可以在代码维护方面提供具有高业务价值和低成本的自动化测试。

重构,维护成本低

测试代码是代码,因此重构测试自动化代码与重构应用程序代码一样重要。否则,测试将很难维护,最好扔掉所有东西,然后重新开始。通过遵循一些技巧可以避免这种成本,这些技巧可帮助减少代码量,功能文件和步骤定义的维护成本。

在Web应用程序的UI中,您的测试与某些区域交互。Page Object只是将它们建模为测试代码中的对象。这减少了重复代码的数量,并且意味着如果UI更改,则仅需要在一个地方应用此修复程序。

测试框架发展

1,线形脚本

少量代码实现的针对某一个功能的自动化操作步骤;

特点:上手简单,少量代码,无扩展性,无复用性,基本不具备业务价值

2,数据驱动+关键字驱动(selenium二次封装)

测试脚本与测试数据分离,通过给定的数据执行对应的测试步骤,通过编写用例数据与维护用例数据来实现自动化测试,当页面ui发生变动后只需要修改对应的数据文件,而不需要修改测试脚本代码;

特点:框架开发有一定难度,用例编写变得简单,易于维护,具有很高的业务价值,扩展性底;

3,po模型+数据驱动+关键字驱动

在数据驱动+关键字驱动基础上升级,保留其本身的优点,并有效的提高了扩展性,灵活性,要求有一定的代码功底;

设计分层

UI自动化之PO模型

设计架构图

UI自动化之PO模型

设计理念

1,可以将PageObject视为同时面向两个方向;面向测试的开发人员,它们代表特定页面提供的服务。面对开发人员,它们应该是唯一了解页面(或页面一部分)HTML结构的唯一事物。

2,将页面对象上的方法视为提供“服务”是页面最基本的信息。鼓励测试开发人员在PageObject开发中只考虑页面交互的服务,而不是基础的WebDriver的实现。

3, PageObject上的方法应返回其他PageObjects。这意味着我们可以通过我们的应用程序有效地模拟用户的操作流程。也意味着,页面之间的交互流程发生改变(例如,登录页面要求用户在首次登录服务时更改密码,而之前没有这样做),只需更改适当的方法即可。

这种方法的一个后果是,可能有必要对成功和不成功的登录进行建模,或者根据应用程序的状态,单击可能会产生不同的结果。发生这种情况时,通常在PageObject上具有多个方法:

public class LoginPage {
    public HomePage loginAs(String username, String password) {
        // ... 登录成功
        return new HomePage(driver);
    }
    
    public LoginPage loginAsExpectingError(String username, String password) {
        //  ... 登录失败,可能是用户名密码错误
        return new LoginPage(driver);
    }
    
    public String getErrorMessage() {
        // 获取登录失败的页面提示内容并返回
        return message
    }
}
           

4,PageObjects不应该负责对页面状态进行断言。

例如:

public void testLoginAsExpectingError() {
    LoginPage loginPage = new LoginPage(driver);
    loginPage.loginAsExpectingError(username, password)
    loginPage.assertLoginAsExpectingErrorMessage("用户名错误");
}
           

优化写法:

public void testLoginAsExpectingError() {
    LoginPage loginPage = new LoginPage(driver);
    loginPage.loginAsExpectingError(username, password)
    assertFalse(loginPage.getErrorMessage("用户名错误");
}
           

po设计实现

1,页面对象化

一个页面包含多个对象,每个对象都会提供对应的页面服务;

2,目标元素属性化

每一个提供服务的目标元素都会映射为PageObjects中的属性

3,操作函数化

对目标元素的操作,以及页面所能提供的服务进行函数化

4,WebDriver底层实现封装

封装基础的WebDriver元素操作方法,为每一个PageObjects提供直接调用的能力,而无需考虑怎么实现;

示例

public class LoginPage {
    private final WebDriver driver;

    public LoginPage(WebDriver driver) {
        this.driver = driver;

        // 检查我们在正确的页面上。
        if (!"Login".equals(driver.getTitle())) {
            // 或者,我们可以导航到登录页面,也许先注销
            throw new IllegalStateException("这不是登录页面");
        }
    }

    //登录页面包含几个将表示为WebElements的HTML元素。
    //这些元素的定位符仅应定义一次。
    By usernameLocator = By.id("username");
    By passwordLocator = By.id("passwd");
    By loginButtonLocator = By.id("login");

    //登录页面允许用户在用户名字段中输入其用户名
    public LoginPage typeUsername(String username) {
        //这是唯一“知道”如何输入用户名的地方
        driver.findElement(usernameLocator).sendKeys(username);

        //返回当前页面对象,因为此操作不会导航到由另一个PageObject表示的页面
        return this;
    }

    //登录页面允许用户在密码字段中输入密码
    public LoginPage typePassword(String password) {
        //这是唯一“知道”如何输入密码的地方
        driver.findElement(passwordLocator).sendKeys(password);
        return this;
    }

    //登录页面允许用户提交登录表单
    public HomePage submitLogin() {
        //这是提交登录表单并希望目的地为主页的唯一位置。
        driver.findElement(loginButtonLocator).submit();

        //返回代表目标的新页面对象。
        //移至其他地方(例如,法律免责声明),然后更改方法签名
        //对于此方法,将意味着不会编译所有依赖此行为的测试。
        return new HomePage(driver);
    }

    //登录页面允许用户在知道输入了无效的用户名和/或密码的情况下提交登录表单
    public LoginPage submitLoginExpectingFailure() {
        //这是唯一提交登录表单并由于登录失败而期望目的地为登录页面的位置。
        driver.findElement(loginButtonLocator).submit();

        //返回代表目标的新页面对象。 提交带有凭据的登录名后,是否应该将用户导航到主页?
        //预期登录失败,该脚本在尝试实例化LoginPage PageObject时将失败。
        return new LoginPage(driver);
    }

    //从概念上讲,登录页面为用户提供了能够“登录”的服务
    //使用用户名和密码的应用程序。
    public HomePage loginAs(String username, String password) {
        //输入用户名,密码和提交登录名的PageObject方法已经定义,在此不再重复。
        typeUsername(username);
        typePassword(password);
        return submitLogin();
    }
}
           

总结思考

思考

1,关于页面对象是应该自己包含断言,还是只是为测试脚本提供数据来断言,存在意见分歧。

主张在页面对象中包含断言的人说,这有助于避免断言在测试脚本中的重复。

无断言页面对象的拥护者说,包含断言将提供对页面数据的访问与断言逻辑的职责混合在一起,并导致膨胀的页面对象。

比较好的处理方案,页面对象中没有断言。可以通过为常见的断言提供断言库来避免重复-这也可以使提供良好的断言更加容易

2,这里有一个论点,名称“页面对象”具有误导性,因为它使您认为每页应该只有一个页面对象。

PageObject不必代表整个页面。它可能代表一个在网站或页面中多次出现的部分,例如网站导航。基本原则是,测试套件中只有一个地方可以了解特定页面(一部分)的HTML结构。

3, 常见的建议是让页面对象负责创建其他页面对象,以响应诸如导航之类的事情。但是,一些从业者更喜欢页面对象返回一些通用的浏览器上下文,并且测试根据测试流程(尤其是条件流程)控制在该上下文之上构建哪些页面对象。他们的偏好是基于这样一个事实,即测试脚本知道接下来要访问哪些页面,并且不需要在页面对象本身中重复这些知识。

总结

1,公共方法代表页面提供的服务

2,尽量不要暴露页面的内部

3,通常不做断言

4,方法返回其他PageObjects

5,无需代表整个页面

6,相同动作的不同结果被建模为不同的方法

参考文献

selenium官网文档:

  • PageObjects
  • 基类支持
  • Bot Style Tests

其他:

  • 马丁福勒:PageObject核心理念
  • Cucumber+selenium+po模式实现