乐趣区

关于软件测试:软件测试PageObjectPO设计模式在-UI-自动化中的实践总结

PO 的思维最早是 2013 年由 IT 大佬 Martin Flower 提出的:https://martinfowler.com/blik… 没错,就是他 — 没错,就是他 —

在他的文章里有这样一张经典样图, 图片中展现了测试代码中间接操作 HTML 元素和应用 PO 模式将 page 对象封装成一个 HTML 页面,通过特定办法来操作元素的比照;如下图:

咱们晓得,PO 次要就是利用在 UI 自动化测试上(Web 端和 App 端均实用),因而 2015 年,Selenium 官网给出了 PO 的设计准则阐明:https://github.com/SeleniumHQ…

对官网的准则进行解读,咱们能够失去如下的信息:

用公共办法代表 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();

@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)整体构造展现:

case 尽量放弃独立

suite 体系治理用例的程序

不要把大量的业务校验逻辑放到 UI 自动化测试里,UI 次要校验的是用户交付,操作流程,款式、数据、兼容性。

与接口测试正当的分工 #### 3.2 补充阐明 以上仅仅是为了演示 PO 而举的一个简略的 demo,实际上还有很大的优化空间:

罕用元素操作方法能够进一步封装的更欠缺

可封装罕用的操作 util 类,例如滑动

特定元素的期待采纳显示期待

登录用例能够利用参数化来以数据驱动的形式实现,应用例代码更简洁易懂

PO 代码和 testcase 代码能够离开,test 下只放 case 代

退出移动版