软件测试|PO设计模式在 UI 自动化中的实践
PO的思想最早是2013年由IT大佬Martin Flower提出的:
https://martinfowler.com/bliki/PageObject.html
没错,就是他
— 没错,就是他 —
在他的文章里有这样一张经典样图,图片中展示了测试代码中直接操作HTML元素和使用PO模式将page对象封装成一个HTML页面,通过特定方法来操作元素的对比;如下图:
我们知道,PO主要就是应用在UI自动化测试上(Web端和App端均适用),因此2015年,Selenium官方给出了PO的设计原则说明:https://github.com/SeleniumHQ/selenium/wiki/PageObjects
对官方的原则进行解读,我们可以得到如下的信息:
-
用公共方法代表UI所提供的功能
如企业微信的通讯录页面,其中有“添加成员”、“批量导入,导出”、“设置所在部门”、“删除”等功能,这些功能都可以封装成通讯录这个UI界面所提供的方法;当然,部分数据较多或者较为复杂,复用性也比较高的话,例如添加成员,也可以单独抽离出来做一个page。 -
方法应该返回其他的PageObject或者返回用于断言的数据
我们既然以页面为对象进行业务操作,那么一个方法结束后必然要有返回值:
要么返回一个页面,这个页面可以是当前页(因为可能还要在这个页面进行其他操作),可以是其他页面(我们操作某个方法后很可能会跳转到另一个页面进行下一步操作);
要么返回需要断言的值,测试用例总归有预期结果的对吧,那么最后肯定要有方法返回一个值,用来给我们做断言,来判断用例执行是否符合预期结果。
不要返回null或者写一个void没有返回值的方法,这样的方法没有意义,既不能为下一步操作创造条件,也不能为用例的断言提供结果。
-
同样的行为不同的结果可以建模为不同的方法
这个就比较好理解了,拿最简答的登录场景来说:
同样的行为: 无论输入的账号密码正确与否,都是按照输入账号密码,点击登录这样的行为去操作
不同的结果:账号密码错误和正确得到的登录响应一定是不同的。
建模为不同的方法:对于登录页来说,就可以根据登录信息正确与否建模出正确登录、账号错误登录、密码错误登录等方法了 -
不要在方法内加断言
对一个测试用例的执行结果进行判断一定是在测试用例里的,方法只是提供给我们业务上需要的操作,因此断言不要加在方法里,而是应该写在用例里 -
不要暴露页面内部的元素给外部
我们使用PO的目的就是为了提高测试用例的可读性和可维护性,只要我们人能操作的事,通过page对象封装好的客户端都可以做到;就类似于一个接口,我们只关心请求操作后接口的返回值是什么,而不需要关心接口内部到底是如何工作的 -
不需要建模UI内的所有元素
一个UI页面可能会包含很多的元素,但是我们只要根据实际业务需求,将我们用的上的元素进行建模即可 -
以页面为单位独立建模
-
隐藏实现细节
-
本质是面向接口编程
-
page :完成对页面的封装
-
driver :完成对Web、Android、Ios、接口的驱动
-
testcase :调用各类page完成业务流程并进行断言
-
data :配置文件和数据驱动
-
utils :其他便捷的功能封装(可选)
1.3.3 PO的优点
- 减少例如find click这类样板代码的重复
- 测试用例的可读性提高,只关心业务流程
- 测试用例可维护性提高,UI页面频繁被修改了,我们只需要去修改对应PO即可,用例无需修改
说的再多,不如动手,下面以QQ邮箱登录为例,演示PO模式在UI自动化中的应用
2.1 登录场景预设
登录页面提供login功能——LoginPage类+login方法
登录页面内有多少元素并不关心,隐藏内部细节
登录成功和失败会返回不同的页面
loginSuccess——MainPage(进入主页面)
loginFail——LoginPage(停留在登录页)
通过方法返回值判断登录是否符合预期
1)创建基础类BasePage,初始化driver,并封装常用的元素操作方法,如click、sendKeys等
package poshow.page;import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;import java.util.List;public class BasePage {public static WebDriver driver;public WebElement findElement(By by){return driver.findElement(by);}public List<WebElement> finElements(By by){return driver.findElements(by);}public void click(By by){findElement(by).click();}public void sendKeys(By by,String context){findElement(by).sendKeys(context);}public String getText(By by){return findElement(by).getText();}
}
2)创建MainPage类,用于登录成功后的返回页面,由于这里并未演示登录后的操作,所以类中无具体方法实现,仅作为loginSuccess后的返回对象
package poshow.page;public class MainPage extends BasePage{
}
3)创建LoginPage类,继承BasePage类。定义所需元素定位方式并根据操作动作(输入账号、输入密码、点击登录)将其封装成具体的业务操作方法,例如登录成功,用户名错误登录、密码错误登录等,输入的测试数据作为方法的入参传入(username,password)
package poshow.page;import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;
import java.util.concurrent.TimeUnit;public class LoginPage extends BasePage{//定位器By usernameInput = By.name("u"); //获取用户名输入框By passwordInput = By.id("p"); //获取密码输入框By submitLogin = By.cssSelector("#login_button"); //获取登录按钮By ErrM = By.id("err_m"); //获取错误提示信息public void openUrl(){String url = "https://mail.qq.com/";driver = new ChromeDriver();driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);driver.get(url);driver.manage().window().maximize();driver.switchTo().frame("login_frame");}private void sleepWait(){try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}//业务方法/*登录方法*/private void login(String username,String password){findElement(usernameInput).clear();findElement(passwordInput).clear();sendKeys(usernameInput,username);sendKeys(passwordInput,password);click(submitLogin);}/*成功登录*/public MainPage loginSuccess(String username,String password){login(username,password);return new MainPage();}/*密码错误登录message:你输入的帐号或密码不正确,请重新输入。*/public String loginWithErrPassword(String username,String password ){login(username,password);sleepWait();return getText(ErrM);}/*账号为空登录你还没有输入帐号!*/public String loginWithErrUsername(String username,String password){login(username,password);sleepWait();return getText(ErrM);}/*密码为空登录*/public String loginWithoutPassword(String username,String password){login(username,password);sleepWait();return getText(ErrM);}
}
4)最后创建LoginTest测试类,编写测试用例;用例的编写更接近于人的行为,人想要登录邮箱,只需要依靠用户名和密码完成登录的行为即可,无需关注具体的输入框和登录按钮是如何定位,如何进行输入点击的。并在用例中加入断言进行判断。
package poshow.testcase;import org.junit.jupiter.api.*;
import poshow.page.LoginPage;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class LoginTest {LoginPage loginPage = new LoginPage();@BeforeAllstatic void openUrl(){new LoginPage().openUrl();}@Test@DisplayName("密码错误登录")@Order(1)void loginWithErrPassword(){String username = "376057520";String password = "123456";String expectedErrM = "你输入的帐号或密码不正确,请重新输入。";String errM = loginPage.loginWithErrPassword(username, password);assertThat(errM,equalTo(expectedErrM));}@Test@DisplayName("账号错误登录")@Order(2)void loginWithErrUsername(){String username = "111";String password = "123456";String expectedErrM = "请输入正确的帐号!";String errM = loginPage.loginWithErrUsername(username, password);assertThat(errM,equalTo(expectedErrM));}@Test@DisplayName("空密码登录")@Order(3)void loginWithoutPassword(){String username = "376057520";String password = "";String expectedErrM = "你还没有输入密码!";String errM = loginPage.loginWithoutPassword(username, password);assertThat(errM,equalTo(expectedErrM));}@Test@DisplayName("正确登录")@Order(4)void logSuccess(){String username = "376057520";String password = "xxx";loginPage.loginSuccess(username,password);}}
5)整体结构展示:
- case尽量保持独立
- suite体系管理用例的顺序
- 不要把大量的业务校验逻辑放到UI自动化测试里, UI主要校验的是用户交付,操作流程,样式、数据、兼容性。
- 与接口测试合理的分工 #### 3.2 补充说明 以上仅仅是为了演示PO而举的一个简单的demo,实际上还有很大的优化空间:
- 常用元素操作方法可以进一步封装的更完善
- 可封装常用的操作util类,例如滑动
- 特定元素的等待采用显示等待
- 登录用例可以利用参数化来以数据驱动的方式完成,使用例代码更简洁易懂
- PO代码和testcase代码可以分开,test下只放case代码
最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!

相关文章:
软件测试|PO设计模式在 UI 自动化中的实践
PO的思想最早是2013年由IT大佬Martin Flower提出的:https://martinfowler.com/bliki/PageObject.html 没错,就是他 — 没错,就是他 — 在他的文章里有这样一张经典样图,图片中展示了测试代码中直接操作HTML元素和使用PO模式将page对象封装成…...
如何上传自己的Jar到Maven中央仓库
在项目开发过程中,我们常常会使用 Maven 从仓库拉取开源的第三方 Jar 包。本文将带领大家将自己写好的代码或开源项目发布到 Maven中央仓库中,让其他人可以直接依赖你的 Jar 包,而不需要先下载你的代码后 install 到本地。 注册帐号 点击以…...
智能井盖传感器功能,万宾科技产品介绍
在国家治理方面,对社会的治理是一个重要的领域,一定要在推进社会治理现代化过程中提高市政府的管理和工作能力,推动社会拥有稳定有序的发展。在管理过程中对全市井盖进行统一化管理,可能是市政府比较头疼的难题,如果想…...
洛谷P4185 离线+并查集
好题,发现没有强制在线,可以离线操作 排序之后带集合点数的并查集就好了 #include<bits/stdc.h> using namespace std; const int N 1e510; int n,m; int p[N],sz[N];int find(int x){if(x!p[x])p[x] find(p[x]);return p[x]; } struct Node{in…...
遇到java.security.AccessControlException:access denied怎么办?
今天工作中遇到了如下报错,记录一下解决方案。 目录 问题 分析 结论 问题 这个问题出现在openjdk8启动网页端Java应用。 Java Exception:java.security.AccessControlException:access denied("java.net.SocketPermission""22.188.130.11:9000…...
c++对接CAT1400
最近工作中遇到需要对接1400协议,网上搜索不到c/c++的实现,所以记录一下自己的实现。 第一步注册: 1400是在http摘要认证的基础上做的,所以要去了解http摘要认证的流程 说明: 1.视图库通过用户分配,手动分配username,password给三方对接程序 2.三方对接程序第一次请求由…...
Linux基础【Linux知识贩卖机】
偶尔的停顿和修整,对于人生是非常必要的。 --随记 文章目录 Linux目录目录结构磁盘分区相关命令 相对路径和绝对路径 文件权限用户分类umask创建文件权限计算方法粘滞位 总结 Linux目录 目录结构 Linux 操作系统采用了一种层次化的目录结构,常被称为标…...
CSS 边框、轮廓线
一、CSS边框: CSS边框属性允许指定一个元素边框的样式和颜色。 1)、边框样式:border-style属性用来定义边框的样式,border-style值: 2)、边框宽度:border-width属性用于指定边框宽度。指定变宽…...
Transformer架构 完整的处理流程
Transformer 是由多层的 Encoder 和 Decoder 构成的。每一层的 Encoder 和 Decoder 都包含了多头自注意力机制(Multi-head Self Attention)、前馈神经网络(Feed Forward)和添加及归一化(Add & Norm)。特…...
git and svn 行尾风格配置强制为lf
git CLI配置: // 提交时转换为LF,检出时转换为CRLF git config --global core.autocrlf true // 提交时转换为LF,检出时不转换 git config --global core.autocrlf input // 提交检出均不转换 git config --global core.autocrlf f…...
达梦数据库答案
1、 创建数据库实例,到/dm8/data下,数据库名:DEMO,实例名DEMOSERVER(10分) [dmdbadmServer ~]$ cd /dm8/tool [dmdbadmServer tool]$ ./dbca.sh1、 簇大小32,页大小16,登录密码&…...
基于SSM的楼房销售系统设计与实现
末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:采用JSP技术开发 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目&#x…...
Blender做一个小凳子学习笔记
文章目录 创建椅座椅子腿靠背渲染 本文是这个B站视频的学习笔记:【Blender】爆肝两个月!拜托三连了!这绝对是全B站最用心的(没有之一)Blender 3D建模零基础入门 创建椅座 首先,需要了解其左上角和右上角的…...
Maven简介
一、Maven模型 二、模型实现 三、对应代码项目介绍...
后端工程化 | SpringBoot 知识点
文章目录 [SpringBoot] 后端工程化1 需求2 开发流程3 RequestController 类(操作类)3.1 简单参数(形参名和请求参数名一致)3.2 简单参数(形参名和请求参数名不一致)3.3 复杂实体参数3.4 数组参数3.5 集合参…...
Oracle(15)Managing Users
目录 一、基础知识 1、Users and Security 用户和安全 2、Database Schema 3、Checklist for Creating Users创建用户步骤 二、基础操作 1、创建一个用户 2、OS Authentication 操作系统身份验证 3、Dropping a User 删除用户 4、Getting User Information 获取用户信…...
自动化测试(Java+eclipse)教程
webdriver环境配置 1.下载chromedriver到本地(一定要选择和自己浏览器相对应的版本chromedriver下载地址) 2.加入到环境变量path中 webdriver工作原理 创建web自动化测试脚本 1.Maven项目创建 File->New->project->(搜索maven)选择maven pr…...
ThreadFactory 实例创建方式
匿名内部类 private final Executor executor;{ThreadFactory threadFactory new ThreadFactory() {Overridepublic Thread newThread(Runnable r) {Thread t new Thread(r);t.setDaemon(true);return t;}};executor Executors.newFixedThreadPool(shops.size(), threadFac…...
【自动化测试】Pytest框架 —— 跳过测试和失败重试
1、Pytest跳过测试用例 自动化测试执行过程中,我们常常出现这种情况:因为功能阻塞,未实现或者环境有问题等等原因,一些用例执行不了, 如果我们注释掉或删除掉这些测试用例,后面可能还要进行恢复操作&#…...
python 时间加法 输出t分钟后的时间
题目: 现在时间是a点b分,请问t分钟后,是几点几分? 输入: 第一行包含一个整数a 第二行包含一个整数b 第三行包含一个整数t 其中,0≤a≤23,0≤b≤59,0≤t,t分钟后还…...
css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
怎么让Comfyui导出的图像不包含工作流信息,
为了数据安全,让Comfyui导出的图像不包含工作流信息,导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo(推荐) 在 save_images 方法中,删除或注释掉所有与 metadata …...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...
在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例
目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码:冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...
算术操作符与类型转换:从基础到精通
目录 前言:从基础到实践——探索运算符与类型转换的奥秘 算术操作符超级详解 算术操作符:、-、*、/、% 赋值操作符:和复合赋值 单⽬操作符:、--、、- 前言:从基础到实践——探索运算符与类型转换的奥秘 在先前的文…...
