1. 认识 PageObject
1.1 PO首次提出
PO的思想最早是2013年由IT大佬Martin Flower提出的:
https://martinfowler.com/bliki/PageObject.html
没错,就是他
--- 没错,就是他 ---
在他的文章里有这样一张经典样图,图片中展示了测试代码中直接操作HTML元素和使用PO模式将page对象封装成一个HTML页面,通过特定方法来操作元素的对比;如下图:
1.2 PO原则解读
我们知道,PO主要就是应用在UI自动化测试上(Web端和App端均适用),因此2015年,Selenium官方给出了PO的设计原则说明:https://github.com/SeleniumHQ/selenium/wiki/PageObjects
对官方的原则进行解读,我们可以得到如下的信息:
1.2.1 方法意义
- 用公共方法代表UI所提供的功能
如企业微信的通讯录页面,其中有“添加成员
”、“批量导入,导出
”、“设置所在部门
”、“删除
”等功能,这些功能都可以封装成通讯录这个UI界面所提供的方法;当然,部分数据较多或者较为复杂,复用性也比较高的话,例如添加成员,也可以单独抽离出来做一个page。
**
**
- 方法应该返回其他的PageObject或者返回用于断言的数据
我们既然以页面为对象进行业务操作,那么一个方法结束后必然要有返回值:
要么返回一个页面
,这个页面可以是当前页(因为可能还要在这个页面进行其他操作),可以是其他页面(我们操作某个方法后很可能会跳转到另一个页面进行下一步操作);
要么返回需要断言的值
,测试用例总归有预期结果的对吧,那么最后肯定要有方法返回一个值,用来给我们做断言,来判断用例执行是否符合预期结果。
不要返回null或者写一个void没有返回值的方法
, 这样的方法没有意义,既不能为下一步操作创造条件,也不能为用例的断言提供结果。
**
**
- 同样的行为不同的结果可以建模为不同的方法
这个就比较好理解了,拿最简答的登录场景来说:
同样的行为
: 无论输入的账号密码正确与否,都是按照输入账号密码
,点击登录
这样的行为去操作
不同的结果
:账号密码错误和正确得到的登录响应一定是不同的。
建模为不同的方法
:对于登录页来说,就可以根据登录信息正确与否建模出正确登录
、账号错误登录
、密码错误登录
等方法了
**
**
- 不要在方法内加断言
对一个测试用例的执行结果进行判断一定是在测试用例里的,方法只是提供给我们业务上需要的操作,因此断言不要加在方法里
,而是应该写在用例里
1.2.2 字段意义
- 不要暴露页面内部的元素给外部
我们使用PO的目的就是为了提高测试用例的可读性
和可维护性
,只要我们人能操作的事,通过page对象封装好的客户端都可以做到;就类似于一个接口,我们只关心请求操作后接口的返回值是什么,而不需要关心接口内部到底是如何工作的
- 不需要建模UI内的所有元素
一个UI页面可能会包含很多的元素,但是我们只要根据实际业务需求,将我们用的上的元素进行建模即可
1.3 PO的做法和优点
1.3.1 PO的做法总结
-
以页面为单位独立建模
-
隐藏实现细节
-
本质是面向接口编程
1.3.2 基于POM的用例组织结构
-
page :完成对页面的封装
-
driver :完成对Web、Android、Ios、接口的驱动
-
testcase :调用各类page完成业务流程并进行断言
-
data :配置文件和数据驱动
-
utils :其他便捷的功能封装(可选)
1.3.3 PO的优点
-
减少例如find click这类样板代码的重复
-
测试用例的可读性提高,只关心业务流程
-
测试用例可维护性提高,UI页面频繁被修改了,我们只需要去修改对应PO即可,用例无需修改
** 2、PO封装演示**
说的再多,不如动手,下面以QQ邮箱登录为例,演示PO模式在UI自动化中的应用
2.1 登录场景预设
2.1 登录场景预设
登录页面提供login功能——LoginPage类+login方法
登录页面内有多少元素并不关心,隐藏内部细节
登录成功和失败会返回不同的页面
loginSuccess——MainPage(进入主页面)
loginFail——LoginPage(停留在登录页)
通过方法返回值判断登录是否符合预期
2.2 代码实现
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();
@BeforeAll static 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)整体结构展示:
2.3 运行效果
** 3、补充说明**
3.1 用例设计
-
case尽量保持独立
-
suite体系管理用例的顺序
-
不要把大量的业务校验逻辑放到UI自动化测试里, UI主要校验的是用户交付,操作流程,样式、数据、兼容性。
-
与接口测试合理的分工 #### 3.2 补充说明 以上仅仅是为了演示PO而举的一个简单的demo,实际上还有很大的优化空间:
-
常用元素操作方法可以进一步封装的更完善
-
可封装常用的操作util类,例如滑动
-
特定元素的等待采用显示等待
-
登录用例可以利用参数化来以数据驱动的方式完成,使用例代码更简洁易懂
-
PO
代码和testcase
代码可以分开,test
下只放case
代码
等等~后续需要大家一起继续完善。
** _
来霍格沃兹测试开发学社,学习更多软件测试与测试开发的进阶技术,知识点涵盖web自动化测试 app自动化测试、接口自动化测试、测试框架、性能测试、安全测试、持续集成/持续交付/DevOps,测试左移、测试右移、精准测试、测试平台开发、测试管理等内容,课程技术涵盖bash、pytest、junit、selenium、appium、postman、requests、httprunner、jmeter、jenkins、docker、k8s、elk、sonarqube、jacoco、jvm-sandbox等相关技术,全面提升测试开发工程师的技术实力