自动化测试(java) - PO模式了解

文章目录

  • [1. page object 模式简介](#1. page object 模式简介)
    • [1.1 传统 UI 自动化的问题](#1.1 传统 UI 自动化的问题)
    • [1.2 PO 模式的优势](#1.2 PO 模式的优势)
  • [2. page object 设计原则](#2. page object 设计原则)
    • [2.1 PO模式核心思想:](#2.1 PO模式核心思想:)
    • [2.2 字段意义](#2.2 字段意义)
    • [2.3 方法意义](#2.3 方法意义)
    • [2.4 PO模式 使用方法](#2.4 PO模式 使用方法)
  • 总结

✨✨✨学习的道路很枯燥,希望我们能并肩走下来!

编程真是一件很奇妙的东西。你只是浅尝辄止,那么只会觉得枯燥乏味,像对待任务似的应付它。但你如果深入探索,就会发现其中的奇妙,了解许多所不知道的原理。知识的力量让你沉醉,甘愿深陷其中并发现宝藏。



本文开始

1. page object 模式简介

PO模式本质:将冗余的测试过程进行分层处理;

Page Object模式:是将每个页面封装成一个对象,页面元素作为对象属性,页面操作作为对象方法。使用Java的面向对象特性,通过类来组织页面元素和操作。

下面是伪代码示例:

元素定位层:

java 复制代码
// ElementLocators.java - 专门存放元素定位器
public class LoginPageLocators {
    // 所有定位器作为公共静态常量
    public static final By USERNAME_INPUT = By.id("username");
    public static final By PASSWORD_INPUT = By.id("password");
    public static final By LOGIN_BUTTON = By.id("login-btn");
    public static final By ERROR_MESSAGE = By.className("error-msg");
    public static final By REMEMBER_ME_CHECKBOX = By.name("remember");
    
    // 动态定位器(如果需要)
    public static By dynamicLink(String linkText) {
        return By.linkText(linkText);
    }
}

页面操作层:

java 复制代码
// LoginPage.java - 专门处理页面操作
public class LoginPage {
    private final WebDriver driver;
    
    public LoginPage(WebDriver driver) {
        this.driver = driver;
    }
    
    // 业务操作方法
    public HomePage login(String username, String password) {
        enterUsername(username);
        enterPassword(password);
        clickLogin();
        return new HomePage(driver);
    }
    //登录失败
    public void loginWithError(String username, String password) {
        enterUsername(username);
        enterPassword(password);
        clickLogin();
        // 停留在当前页面,因为登录失败
    }
    
    // 原子操作方法
    //输入账号方法
    public void enterUsername(String username) {
        WebElement element = driver.findElement(LoginPageLocators.USERNAME_INPUT);
        element.clear();
        element.sendKeys(username);
    }
    //输入密码方法
    public void enterPassword(String password) {
        driver.findElement(LoginPageLocators.PASSWORD_INPUT)
              .sendKeys(password);
    }
    //点击登录方法
    public void clickLogin() {
        driver.findElement(LoginPageLocators.LOGIN_BUTTON)
              .click();
    }
    //获取错误信息
    public String getErrorMessage() {
        return driver.findElement(LoginPageLocators.ERROR_MESSAGE)
                     .getText();
    }
    
    // 支持链式调用
    public LoginPage withUsername(String username) {
        enterUsername(username);
        return this;
    }
    
    public LoginPage withPassword(String password) {
        enterPassword(password);
        return this;
    }
    
    public HomePage andLogin() {
        clickLogin();
        return new HomePage(driver);
    }
}

测试用例层伪代码:

java 复制代码
// LoginTests.java - 专门写测试用例
public class LoginTests {
    private WebDriver driver;
    private LoginPage loginPage;
    
    @BeforeEach
    public void setup() {
        driver = new ChromeDriver();
        loginPage = new LoginPage(driver);
        driver.get("https://example.com/login");
    }
    
    @AfterEach
    public void teardown() {
        if (driver != null) {
            driver.quit();
        }
    }
    
    @Test
    @DisplayName("用户使用正确凭据可以登录")
    public void userCanLoginWithValidCredentials() {
        // 使用业务方法
        HomePage homePage = loginPage.login("admin", "password123");
        
        // 验证
        assertTrue(homePage.isUserLoggedIn());
        assertEquals("Dashboard", homePage.getPageTitle());
    }
    
    @Test
    @DisplayName("用户使用错误密码登录失败")
    public void userCannotLoginWithInvalidPassword() {
        // 使用链式调用
        loginPage.withUsername("admin")
                 .withPassword("wrong")
                 .loginWithError();
        
        // 验证错误信息
        String errorMsg = loginPage.getErrorMessage();
        assertEquals("Invalid password", errorMsg);
    }
 }   

1.1 传统 UI 自动化的问题

传统UI自动化测试脚本: 直接将测试逻辑、元素定位、数据验证混合在一起,导致:

1)代码重复:相同元素在多处重复定位,无法适应 UI 频繁变化;

如:当一个元素修改,使用该元素的多个重复地方都需要修改;

2)可读性差:业务逻辑和实现细节混杂,无法清晰表达业务用例场景;

解释:不易看出具体操作逻辑;

3)维护困难:页面变化需要修改多处代码,存在大量的样板代码 driver/find/click

4)复用性低:无法在不同测试中复用页面操作

传统自动化测试登录样例:

java 复制代码
/***
 * Tests login feature
 */
public class Login {

  public void testLogin() {
    // fill login data on sign-in page
    driver.findElement(By.name("user_name")).sendKeys("userName");
    driver.findElement(By.name("password")).sendKeys("my supersecret password");
    driver.findElement(By.name("sign-in")).click();

    // verify h1 tag is "Hello userName" after login
    driver.findElement(By.tagName("h1")).isDisplayed();
    assertThat(driver.findElement(By.tagName("h1")).getText(), is("Hello userName"));
  }
}

1.2 PO 模式的优势

PO模式通过封装带来以下核心优势:

1)代码复用:页面操作一次定义,多次使用

2)易于维护:页面变化只需修改对应Page Object

3)可读性强:测试用例反映业务逻辑,而非实现细节

4)团队协作:测试开发分离,并行工作

5)健壮性:统一处理等待、异常等

小结:使用PO模式,降低 UI 变化导致的测试用例脆弱性问题

让用例清晰明朗,与具体实现无关

点击这里→Selenium 官网地址-PO模式介绍

2. page object 设计原则

2.1 PO模式核心思想:

PO模式:

  1. 将测试脚本从"如何操作"转变为"要做什么",让测试代码更关注业务逻辑,而非实现细节。
  2. 当页面UI变化时,只需要修改对应的Page Object类,而不需要修改测试用例,真正实现了关注点分离

2.2 字段意义

  1. 不要暴露页面内部的元素给外部
    解释:页面内部的元素(按钮、输入框)应该封装在页面类内部,外部测试代码不需要知道具体怎么找到这些元素,只需要知道能做什么操作。
java 复制代码
// ✅ 正确做法:封装起来
public class LoginPage {
    private By usernameInput = By.id("username"); // private修饰,不暴露
    
    public void enterUsername(String text) {
        // 内部处理,外部不知道具体怎么找元素
        driver.findElement(usernameInput).sendKeys(text);
    }
}

// 测试代码只调用方法
loginPage.enterUsername("admin"); // 简洁,不关心内部实现
  1. 不需要建模 UI 内的所有元素
    解释:只封装测试需要用到的元素,没用的元素不要写进来。

2.3 方法意义

  1. 用公共方法代表 UI 所提供的功能
    解释:方法就是那些按钮,代表用户能做的操作。
java 复制代码
// ✅ 正确做法:方法名代表业务功能
public class ShoppingCartPage {
    public OrderPage proceedToCheckout() {  // 代表"进行结账"这个功能
        driver.findElement(By.id("checkout")).click();
        return new OrderPage(driver);  // 返回下一页
    }
    
    public ShoppingCartPage updateQuantity(String itemId, int quantity) {
        // 代表"更新数量"功能
        // ...实现代码
        return this;  // 返回当前页,支持链式调用
    }
}

// 使用:读起来像自然语言
cartPage.proceedToCheckout();        // 进行结账
cartPage.updateQuantity("item1", 3); // 更新数量
  1. 方法应该返回其他的 PageObject 或者返回用于断言的数据。
    解释:方法要么带你去下一个页面(返回新页面对象),要么给你一些信息让你检查(返回数据),不要什么都不给
  2. 同样的行为不同的结果可以建模为不同的方法
    解释:同样是"登录"这个行为,成功和失败是不同的场景,应该用不同的方法处理,就像"正常登录"和"忘记密码登录"是两回事。
java 复制代码
// ✅ 正确做法:不同结果用不同方法
public class LoginPage {
    // 成功登录:返回下一页
    public HomePage loginSuccessfully(String user, String pass) {
        enterCredentials(user, pass);
        clickLogin();
        return new HomePage(driver);  // 假设总是成功
    }
    
    // 登录失败:停留在当前页,可以检查错误
    public LoginPage loginWithInvalidCreds(String user, String pass) {
        enterCredentials(user, pass);
        clickLogin();
        return this;  // 返回自己,因为还在登录页
    }
}
  1. 不要在方法内加断言
    解释:页面对象的方法只管"做事情",不要管"检查对错"。断言应该放在测试代码里。

2.4 PO模式 使用方法

  1. 把元素信息和操作细节封装到 PageObject 类中
  2. 根据业务逻辑,在测试用例中链式调用

PO模式中测试用例代码示例:

java 复制代码
public class OptimizedLoginTests {
    private WebDriver driver;
    private PageFactory pages;
    
    @BeforeEach
    public void setup() {
        driver = WebDriverManager.chromedriver().create();
        driver.manage().window().maximize();
        pages = new PageFactory(driver);
        
        // 导航到登录页
        driver.get("https://example.com");
        pages.loginPage().isLoginPageDisplayed(); // 等待页面加载
    }
    
    @Test
    @DisplayName("端到端登录流程测试")
    public void endToEndLoginFlow() {
        // 使用页面工厂
        LoginPage loginPage = pages.loginPage();
        
        // 执行登录
        HomePage homePage = loginPage.performLogin("testuser", "SecurePass123!");
        
        // 验证登录成功
        assertTrue(homePage.isUserMenuVisible(), "用户菜单应该可见");
        assertTrue(homePage.getWelcomeMessage().contains("testuser"), 
                  "欢迎消息应包含用户名");
        
        // 验证导航
        DashboardPage dashboard = homePage.navigateToDashboard();
        assertTrue(dashboard.isDashboardLoaded(), "仪表板应加载成功");
    }
    
    @Test
    @DisplayName("数据驱动登录测试")
    @CsvSource({
        "admin, admin123, true",
        "user, wrongpass, false",
        "'', password, false",
        "admin, '', false"
    })
    public void dataDrivenLoginTest(String username, String password, boolean shouldSucceed) {
        LoginPage loginPage = pages.loginPage();
        loginPage.performLogin(username, password);
        
        LoginPage.LoginState state = loginPage.getLoginState();
        
        if (shouldSucceed) {
            assertEquals(LoginPage.LoginState.LOGGED_IN, state);
        } else {
            assertNotEquals(LoginPage.LoginState.LOGGED_IN, state);
        }
    }
}

总结

✨✨✨各位读友,本篇分享到内容是否更好的帮助你理解,如果对你有帮助给个👍赞鼓励一下吧!!
🎉🎉🎉世上没有绝望的处境,只有对处境绝望的人。
🎉🎉🎉一遇挫折就灰心丧气的人,永远是个失败者。而一向努力奋斗,坚韧不拔的人会走向成功。
感谢每一位一起走到这的伙伴,我们可以一起交流进步!!!一起加油吧!!!

相关推荐
徐先生 @_@|||2 小时前
Java/Maven 对比 Python/PyPI
开发语言·python
编程猪猪侠2 小时前
手写js轮播图效果参考
开发语言·javascript·ecmascript
嘻嘻嘻开心2 小时前
Collection接口
linux·windows·python
IT 行者2 小时前
Spring Security 7.0 新特性详解
java·后端·spring
rebekk2 小时前
什么时候会用到python -m
python
思成不止于此2 小时前
C++ STL中map与set的底层实现原理深度解析
开发语言·c++·set·map·红黑树·底层实现
华仔啊2 小时前
Java 的金额计算用 long 还是 BigDecimal?资深程序员这样选
java·后端
惺忪97982 小时前
C++ 构造函数完全指南
开发语言·c++
小此方2 小时前
Re:从零开始学C++(五)类和对象·第二篇:构造函数与析构函数
开发语言·c++