HarmonyOS开发:测试脚本维护——测试代码重构

举报
Jack20 发表于 2026/06/24 15:50:45 2026/06/24
【摘要】 HarmonyOS开发:测试脚本维护——测试代码重构📌 核心要点:UI测试脚本的可维护性决定了测试体系的寿命,Page Object模式分离定位与操作、测试数据集中管理、重构策略持续优化,让测试代码和业务代码一样经得起时间考验。 背景与动机你写了50个UI测试用例,跑得挺顺。然后产品改了个需求——“登录"按钮改成"立即登录”。你打开测试代码一看,ON.text('登录')出现了30次。改...

HarmonyOS开发:测试脚本维护——测试代码重构

📌 核心要点:UI测试脚本的可维护性决定了测试体系的寿命,Page Object模式分离定位与操作、测试数据集中管理、重构策略持续优化,让测试代码和业务代码一样经得起时间考验。

背景与动机

你写了50个UI测试用例,跑得挺顺。然后产品改了个需求——“登录"按钮改成"立即登录”。

你打开测试代码一看,ON.text('登录')出现了30次。改吧。改完第15次的时候你发现,有个地方是ON.text('登录账号'),这改不改?改完第30次,又发现有个地方是ON.id('login_btn'),这个不用改——但下次改id呢?

这就是测试代码的维护问题。测试代码和业务代码一样,不维护就会腐化。而且测试代码的腐化速度往往更快——因为大家觉得"测试代码不重要",随便写写就行。

但随便写的测试代码,三个月后你自己都看不懂,半年后改一个按钮要改十几个文件。最终的结果就是:测试代码没人维护,测试用例没人跑,整个测试体系形同虚设。

所以你需要像对待业务代码一样对待测试代码——设计模式、重构、代码复用,一个都不能少。

核心原理

测试代码的三个层次

graph TB
    A[测试代码] --> B[测试用例层<br/>业务流程描述]
    A --> C[Page Object层<br/>页面操作封装]
    A --> D[测试数据层<br/>数据与配置管理]
    
    B --> B1["只描述做什么"]
    B --> B2["不关心怎么做"]
    B --> B3["可读性优先"]
    
    C --> C1["封装组件查找"]
    C --> C2["封装页面操作"]
    C --> C3["一处修改全局生效"]
    
    D --> D1["测试数据集中管理"]
    D --> D2["环境配置分离"]
    D --> D3["数据驱动测试"]
    
    classDef root fill:#4A90D9,stroke:#2C5F8A,color:#fff
    classDef test fill:#67C23A,stroke:#3E9B2B,color:#fff
    classDef page fill:#E6A23C,stroke:#B07D2B,color:#fff
    classDef data fill:#F56C6C,stroke:#C94A4A,color:#fff
    
    class A root
    class B,B1,B2,B3 test
    class C,C1,C2,C3 page
    class D,D1,D2,D3 data

Page Object模式

Page Object是UI测试最经典的设计模式。核心思想:每个页面对应一个类,类里封装这个页面的组件查找和操作方法。测试用例只调用Page Object的方法,不直接操作组件。

flowchart LR
    A[测试用例] -->|"调用方法"| B[LoginPage]
    A -->|"调用方法"| C[HomePage]
    A -->|"调用方法"| D[CartPage]
    
    B -->|"查找组件"| E[Driver]
    C -->|"查找组件"| E
    D -->|"查找组件"| E
    
    E -->|"操作设备"| F[被测App]
    
    classDef test fill:#67C23A,stroke:#3E9B2B,color:#fff
    classDef page fill:#E6A23C,stroke:#B07D2B,color:#fff
    classDef driver fill:#4A90D9,stroke:#2C5F8A,color:#fff
    classDef app fill:#F56C6C,stroke:#C94A4A,color:#fff
    
    class A test
    class B,C,D page
    class E driver
    class F app

重构前后对比

维度 重构前 重构后
组件定位 散落在各测试用例中 集中在Page Object中
操作逻辑 每个用例重复编写 Page Object封装复用
改一个按钮文案 改N个文件 改1个文件
测试可读性 代码和逻辑混杂 业务流程清晰
维护成本 高,随用例数线性增长 低,新增用例成本低

代码实战

基础用法:从硬编码到Page Object

先看反面教材——所有逻辑都堆在测试用例里:

// ❌ 重构前:硬编码,难维护
import { Driver, ON } from '@ohos.UiTest';

export default function testLogin_old() {
  const driver = Driver.create();
  
  // 组件定位硬编码
  const usernameInput = driver.findComponent(ON.id('username_input'));
  usernameInput?.inputText('admin');
  
  const passwordInput = driver.findComponent(ON.id('password_input'));
  passwordInput?.inputText('123456');
  
  const loginBtn = driver.findComponent(ON.text('登录'));
  loginBtn?.click();
  
  driver.assertComponentExistence(ON.text('首页'), 5000);
}

改成Page Object模式:

// ✅ 重构后:Page Object封装
import { Driver, ON, Component } from '@ohos.UiTest';

// ===== Page Object: 登录页 =====
class LoginPage {
  private driver: Driver;
  
  // 组件定位集中管理——改一个地方全局生效
  private readonly SELECTORS = {
    usernameInput: ON.id('username_input'),
    passwordInput: ON.id('password_input'),
    loginBtn: ON.text('登录'),         // 改成"立即登录"?只改这里
    loginPage: ON.id('login_page'),
  };
  
  constructor(driver: Driver) {
    this.driver = driver;
  }
  
  // 页面操作封装
  async login(username: string, password: string): Promise<boolean> {
    const usernameInput = this.driver.findComponent(this.SELECTORS.usernameInput);
    usernameInput?.inputText(username);
    
    const passwordInput = this.driver.findComponent(this.SELECTORS.passwordInput);
    passwordInput?.inputText(password);
    
    const loginBtn = this.driver.findComponent(this.SELECTORS.loginBtn);
    loginBtn?.click();
    
    // 等待登录完成
    try {
      this.driver.assertComponentExistence(ON.text('首页'), 5000);
      return true;
    } catch {
      return false;
    }
  }
  
  // 验证页面是否加载
  isLoaded(): boolean {
    return this.driver.findComponent(this.SELECTORS.loginPage) !== null;
  }
  
  // 获取错误提示
  getErrorMessage(): string {
    const errorHint = this.driver.findComponent(ON.id('login_error'));
    return errorHint?.getText() ?? '';
  }
}

// ===== 测试用例:简洁清晰 =====
export default async function testLogin_new() {
  const driver = Driver.create();
  const loginPage = new LoginPage(driver);
  
  // 测试用例只描述业务流程
  const success = await loginPage.login('admin', '123456');
  if (success) {
    console.info('✅ 登录成功');
  } else {
    console.error(`❌ 登录失败: ${loginPage.getErrorMessage()}`);
  }
}

看到了吗?测试用例从"找组件→操作→验证"变成了"调方法→看结果"。代码量没少多少,但维护成本天差地别——改按钮文案只改SELECTORS里的一行。

进阶用法:完整的Page Object体系

// 470_test_script_maintain_advanced.ets
import { Driver, ON, Component } from '@ohos.UiTest';

// ===== 基础Page Object =====
abstract class BasePage {
  protected driver: Driver;
  
  constructor(driver: Driver) {
    this.driver = driver;
  }
  
  // 通用等待方法
  protected async waitForComponent(selector: ON, timeout: number = 5000): Promise<Component> {
    const startTime = Date.now();
    while (Date.now() - startTime < timeout) {
      const component = this.driver.findComponent(selector);
      if (component !== null) return component;
      await this.sleep(500);
    }
    throw new Error(`等待组件超时: ${selector}`);
  }
  
  // 通用点击
  protected async safeClick(selector: ON, timeout?: number): Promise<void> {
    const component = await this.waitForComponent(selector, timeout);
    component.click();
  }
  
  // 通用输入
  protected async safeInput(selector: ON, text: string, timeout?: number): Promise<void> {
    const component = await this.waitForComponent(selector, timeout);
    component.inputText(text);
  }
  
  // 页面是否加载
  abstract isLoaded(): boolean;
  
  // 截图
  screenshot(name: string): void {
    this.driver.captureScreen(`/data/local/tmp/${name}_${Date.now()}.png`);
  }
  
  protected sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// ===== 首页Page Object =====
class HomePage extends BasePage {
  private readonly SELECTORS = {
    homePage: ON.id('home_page'),
    searchBox: ON.id('search_box'),
    tabHome: ON.id('tab_home'),
    tabCart: ON.id('tab_cart'),
    tabProfile: ON.id('tab_profile'),
    productList: ON.id('product_list'),
  };
  
  isLoaded(): boolean {
    return this.driver.findComponent(this.SELECTORS.homePage) !== null;
  }
  
  // 搜索商品
  async searchProduct(keyword: string): Promise<SearchResultPage> {
    await this.safeClick(this.SELECTORS.searchBox);
    await this.safeInput(ON.id('search_input'), keyword);
    this.driver.pressKey(KeyCode.ENTER);
    await this.sleep(1000);
    return new SearchResultPage(this.driver);
  }
  
  // 浏览商品
  async browseProduct(index: number): Promise<ProductDetailPage> {
    const list = await this.waitForComponent(this.SELECTORS.productList);
    const items = list.findComponents(ON.type('ListItem'));
    if (index >= items.length) {
      throw new Error(`商品索引超出范围: ${index} >= ${items.length}`);
    }
    items[index].click();
    await this.sleep(1000);
    return new ProductDetailPage(this.driver);
  }
  
  // 导航到购物车
  async goToCart(): Promise<CartPage> {
    await this.safeClick(this.SELECTORS.tabCart);
    await this.sleep(500);
    return new CartPage(this.driver);
  }
  
  // 导航到个人中心
  async goToProfile(): Promise<ProfilePage> {
    await this.safeClick(this.SELECTORS.tabProfile);
    await this.sleep(500);
    return new ProfilePage(this.driver);
  }
}

// ===== 搜索结果页Page Object =====
class SearchResultPage extends BasePage {
  private readonly SELECTORS = {
    resultList: ON.id('search_result_list'),
    searchInput: ON.id('search_input'),
  };
  
  isLoaded(): boolean {
    return this.driver.findComponent(this.SELECTORS.resultList) !== null;
  }
  
  // 获取搜索结果数量
  getResultCount(): number {
    const list = this.driver.findComponent(this.SELECTORS.resultList);
    if (list === null) return 0;
    return list.findComponents(ON.type('ListItem')).length;
  }
  
  // 选择第N个搜索结果
  async selectResult(index: number): Promise<ProductDetailPage> {
    const list = await this.waitForComponent(this.SELECTORS.resultList);
    const items = list.findComponents(ON.type('ListItem'));
    if (index >= items.length) {
      throw new Error(`搜索结果索引超出范围`);
    }
    items[index].click();
    await this.sleep(1000);
    return new ProductDetailPage(this.driver);
  }
}

// ===== 商品详情页Page Object =====
class ProductDetailPage extends BasePage {
  private readonly SELECTORS = {
    detailPage: ON.id('product_detail_page'),
    productName: ON.id('product_name'),
    productPrice: ON.id('product_price'),
    favoriteBtn: ON.id('favorite_btn'),
    addToCartBtn: ON.id('add_to_cart'),
    buyNowBtn: ON.id('buy_now'),
  };
  
  isLoaded(): boolean {
    return this.driver.findComponent(this.SELECTORS.detailPage) !== null;
  }
  
  // 获取商品名称
  getProductName(): string {
    const name = this.driver.findComponent(this.SELECTORS.productName);
    return name?.getText() ?? '';
  }
  
  // 获取商品价格
  getProductPrice(): string {
    const price = this.driver.findComponent(this.SELECTORS.productPrice);
    return price?.getText() ?? '';
  }
  
  // 收藏
  async favorite(): Promise<void> {
    await this.safeClick(this.SELECTORS.favoriteBtn);
  }
  
  // 加入购物车
  async addToCart(): Promise<void> {
    await this.safeClick(this.SELECTORS.addToCartBtn);
    await this.sleep(1000);
  }
  
  // 立即购买
  async buyNow(): Promise<CheckoutPage> {
    await this.safeClick(this.SELECTORS.buyNowBtn);
    await this.sleep(1000);
    return new CheckoutPage(this.driver);
  }
  
  // 返回
  async goBack(): Promise<void> {
    this.driver.pressKey(KeyCode.BACK);
    await this.sleep(500);
  }
}

// ===== 购物车Page Object =====
class CartPage extends BasePage {
  private readonly SELECTORS = {
    cartPage: ON.id('cart_page'),
    cartItemCheckbox: ON.id('cart_item_checkbox'),
    checkoutBtn: ON.text('结算'),
    emptyCart: ON.id('empty_cart'),
  };
  
  isLoaded(): boolean {
    return this.driver.findComponent(this.SELECTORS.cartPage) !== null;
  }
  
  // 获取购物车商品数量
  getCartItemCount(): number {
    const items = this.driver.findComponents(ON.id('cart_item'));
    return items.length;
  }
  
  // 全选
  async selectAll(): Promise<void> {
    await this.safeClick(this.SELECTORS.cartItemCheckbox);
  }
  
  // 结算
  async checkout(): Promise<CheckoutPage> {
    await this.safeClick(this.SELECTORS.checkoutBtn);
    await this.sleep(1000);
    return new CheckoutPage(this.driver);
  }
  
  // 是否为空
  isEmpty(): boolean {
    return this.driver.findComponent(this.SELECTORS.emptyCart) !== null;
  }
}

// ===== 结算页Page Object =====
class CheckoutPage extends BasePage {
  private readonly SELECTORS = {
    checkoutPage: ON.id('checkout_page'),
    confirmOrderBtn: ON.id('confirm_order'),
  };
  
  isLoaded(): boolean {
    return this.driver.findComponent(this.SELECTORS.checkoutPage) !== null;
  }
  
  // 确认订单
  async confirmOrder(): Promise<boolean> {
    await this.safeClick(this.SELECTORS.confirmOrderBtn);
    try {
      this.driver.assertComponentExistence(ON.text('订单提交成功'), 10000);
      return true;
    } catch {
      return false;
    }
  }
}

// ===== 个人中心Page Object =====
class ProfilePage extends BasePage {
  private readonly SELECTORS = {
    profilePage: ON.id('profile_page'),
    orderEntry: ON.text('我的订单'),
    settingsEntry: ON.text('设置'),
  };
  
  isLoaded(): boolean {
    return this.driver.findComponent(this.SELECTORS.profilePage) !== null;
  }
  
  // 进入订单列表
  async goToOrders(): Promise<void> {
    await this.safeClick(this.SELECTORS.orderEntry);
    await this.sleep(500);
  }
  
  // 进入设置
  async goToSettings(): Promise<void> {
    await this.safeClick(this.SELECTORS.settingsEntry);
    await this.sleep(500);
  }
}

完整示例:数据驱动测试 + 重构策略

// 470_test_script_maintain_full.ets
import { Driver, ON } from '@ohos.UiTest';

// ===== 测试数据管理 =====
class TestDataManager {
  private static instance: TestDataManager;
  private data: Map<string, Map<string, string>>;
  
  private constructor() {
    this.data = new Map();
    this.loadTestData();
  }
  
  static getInstance(): TestDataManager {
    if (!TestDataManager.instance) {
      TestDataManager.instance = new TestDataManager();
    }
    return TestDataManager.instance;
  }
  
  // 加载测试数据(实际项目从JSON文件读取)
  private loadTestData(): void {
    // 登录测试数据
    const loginData = new Map<string, string>();
    loginData.set('valid_username', 'admin');
    loginData.set('valid_password', 'Admin123456');
    loginData.set('invalid_username', 'wrong_user');
    loginData.set('invalid_password', 'wrong_pass');
    loginData.set('empty_username', '');
    loginData.set('empty_password', '');
    this.data.set('login', loginData);
    
    // 搜索测试数据
    const searchData = new Map<string, string>();
    searchData.set('keyword_valid', '蓝牙耳机');
    searchData.set('keyword_empty', '');
    searchData.set('keyword_special', '@#$%');
    searchData.set('keyword_long', '这是一个非常长的搜索关键词用来测试输入框的边界情况');
    this.data.set('search', searchData);
    
    // 环境配置
    const envData = new Map<string, string>();
    envData.set('wait_timeout', '5000');
    envData.set('long_timeout', '15000');
    envData.set('retry_count', '2');
    this.data.set('env', envData);
  }
  
  // 获取测试数据
  getData(category: string, key: string): string {
    return this.data.get(category)?.get(key) ?? '';
  }
  
  // 获取环境配置
  getConfig(key: string): string {
    return this.data.get('env')?.get(key) ?? '';
  }
  
  // 生成唯一数据(避免测试间冲突)
  generateUnique(prefix: string): string {
    return `${prefix}_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
  }
}

// ===== 数据驱动测试框架 =====
class DataDrivenTestRunner {
  private driver: Driver;
  private dataManager: TestDataManager;
  
  constructor() {
    this.driver = Driver.create();
    this.dataManager = TestDataManager.getInstance();
  }
  
  // 数据驱动测试:用多组数据跑同一个测试逻辑
  async runWithData(
    testFn: (driver: Driver, data: Record<string, string>) => Promise<boolean>,
    dataSet: Array<Record<string, string>>,
    testName: string
  ): Promise<void> {
    console.info(`\n🧪 数据驱动测试: ${testName}`);
    
    for (let i = 0; i < dataSet.length; i++) {
      const data = dataSet[i];
      const caseName = data._name ?? `用例${i + 1}`;
      
      try {
        const passed = await testFn(this.driver, data);
        const status = passed ? '✅' : '❌';
        console.info(`  ${status} ${caseName}`);
      } catch (error) {
        console.error(`${caseName}: ${(error as Error).message}`);
      }
    }
  }
  
  getDriver(): Driver {
    return this.driver;
  }
  
  getDataManager(): TestDataManager {
    return this.dataManager;
  }
}

// ===== 重构后的测试用例 =====

// 测试1:登录流程(数据驱动)
async function testLoginFlow(): Promise<void> {
  const runner = new DataDrivenTestRunner();
  const driver = runner.getDriver();
  const loginPage = new LoginPage(driver);
  
  // 定义多组测试数据
  const loginDataSets = [
    { _name: '正常登录', username: 'admin', password: 'Admin123456', expectSuccess: 'true' },
    { _name: '空用户名', username: '', password: 'Admin123456', expectSuccess: 'false' },
    { _name: '空密码', username: 'admin', password: '', expectSuccess: 'false' },
    { _name: '错误密码', username: 'admin', password: 'wrong', expectSuccess: 'false' },
  ];
  
  await runner.runWithData(async (drv, data) => {
    // 确保在登录页
    if (!loginPage.isLoaded()) {
      // 导航到登录页...
    }
    
    const success = await loginPage.login(data.username, data.password);
    const expected = data.expectSuccess === 'true';
    
    if (success !== expected) {
      if (expected) {
        console.error(`登录失败: ${loginPage.getErrorMessage()}`);
      }
      return false;
    }
    return true;
  }, loginDataSets, '登录流程');
}

// 测试2:购物流程(链式调用,可读性极高)
async function testShoppingFlow(): Promise<void> {
  const driver = Driver.create();
  
  // 从首页开始
  const homePage = new HomePage(driver);
  if (!homePage.isLoaded()) {
    throw new Error('首页未加载');
  }
  
  // 搜索 → 选择商品 → 加入购物车 → 结算
  const searchResult = await homePage.searchProduct('蓝牙耳机');
  const productDetail = await searchResult.selectResult(0);
  
  const productName = productDetail.getProductName();
  console.info(`正在购买: ${productName}`);
  
  await productDetail.addToCart();
  
  // 返回首页,进入购物车
  await productDetail.goBack();
  const cartPage = await homePage.goToCart();
  
  if (cartPage.isEmpty()) {
    throw new Error('购物车为空,加入购物车可能失败');
  }
  
  await cartPage.selectAll();
  const checkoutPage = await cartPage.checkout();
  
  const orderSuccess = await checkoutPage.confirmOrder();
  if (!orderSuccess) {
    throw new Error('订单提交失败');
  }
  
  console.info('🎉 购物流程测试完成');
}

// 测试3:搜索功能(数据驱动 + 多关键词)
async function testSearchFunction(): Promise<void> {
  const runner = new DataDrivenTestRunner();
  const driver = runner.getDriver();
  
  const searchDatasets = [
    { _name: '正常关键词', keyword: '蓝牙耳机', expectHasResult: 'true' },
    { _name: '空关键词', keyword: '', expectHasResult: 'false' },
    { _name: '特殊字符', keyword: '@#$%', expectHasResult: 'false' },
    { _name: '超长关键词', keyword: '这是一个非常长的搜索关键词用来测试输入框的边界情况', expectHasResult: 'false' },
  ];
  
  await runner.runWithData(async (drv, data) => {
    const homePage = new HomePage(drv);
    if (!homePage.isLoaded()) return false;
    
    const searchResult = await homePage.searchProduct(data.keyword);
    const hasResult = searchResult.getResultCount() > 0;
    const expected = data.expectHasResult === 'true';
    
    return hasResult === expected;
  }, searchDatasets, '搜索功能');
}

// 执行所有测试
export default async function runAllRefactoredTests() {
  console.info('🧪 ===== 开始重构后的测试 =====');
  
  await testLoginFlow();
  await testShoppingFlow();
  await testSearchFunction();
  
  console.info('\n🧪 ===== 所有测试完成 =====');
}

踩坑与注意事项

坑1:Page Object过度封装

Page Object不是越细越好。如果一个方法只被一个测试用例用到,封装它反而增加了维护成本——你改方法签名的时候要同时改调用方。

原则:被2个以上测试用例使用的操作才封装成方法;只被1个用例使用的操作直接写在测试用例里。

坑2:Page Object之间的导航耦合

homePage.searchProduct()返回SearchResultPage——这种链式调用很优雅,但有个问题:如果搜索流程变了(比如搜索不再跳转新页面,而是在当前页显示结果),你需要改searchProduct的返回值类型,所有调用方都要改。

解决方案:Page Object的导航方法返回下一个Page Object是合理的,但不要在Page Object里做太多业务判断。如果流程可能变化,用更灵活的返回方式。

坑3:测试数据的硬编码

测试数据直接写在代码里,改数据就要改代码。如果测试数据经常变化(比如接口字段调整),维护成本很高。

解决方案:测试数据集中管理,从JSON文件读取,代码和数据分离。

// 测试数据文件 test_data.json
{
  "login": {
    "valid": { "username": "admin", "password": "Admin123456" },
    "invalid": { "username": "wrong", "password": "wrong" }
  }
}

// 代码中读取
const testData = JSON.parse(fileIo.readTextSync('./test_data.json'));

坑4:BasePage的"上帝类"倾向

BasePage很容易变成什么都往里塞的"上帝类"。今天加个等待方法,明天加个截图方法,后天加个日志方法……BasePage越来越臃肿。

解决方案:BasePage只放最通用的方法(等待、点击、输入)。其他能力(截图、日志、性能采集)用独立的工具类,通过组合而非继承来使用。

坑5:忘记处理测试失败后的状态清理

测试失败后,App可能停在某个中间状态。下一个测试用例假设App在首页,但实际在详情页——于是找不到组件,也失败了。这种"级联失败"会让整个测试套件崩溃。

解决方案:每个测试用例开始前重置到已知状态。

// 每个测试用例的beforeEach
async function resetToHomePage(driver: Driver): Promise<void> {
  // 连按返回键直到回到首页
  for (let i = 0; i < 5; i++) {
    const home = driver.findComponent(ON.id('home_page'));
    if (home !== null) break;
    driver.pressKey(KeyCode.BACK);
    await sleep(300);
  }
  // 确保在首页Tab
  const homeTab = driver.findComponent(ON.id('tab_home'));
  homeTab?.click();
}

HarmonyOS 6适配说明

HarmonyOS 6对测试脚本维护做了以下增强:

  1. 测试脚手架生成:DevEco Studio内置Page Object代码生成器,根据UI组件树自动生成Page Object骨架代码
  2. 测试数据管理器:框架内置TestDataManager类,支持从JSON/YAML文件加载测试数据
  3. 测试用例模板:提供数据驱动测试、参数化测试等模板,一键生成测试用例框架
  4. 智能等待策略WaitStrategy类封装多种等待策略(固定等待、条件等待、指数退避等待),替代手写sleep
  5. 测试代码质量检查:DevEco Studio新增测试代码质量检查,检测硬编码、重复代码等问题

迁移注意:BasePage模式在HarmonyOS 6中仍然适用,但建议使用框架内置的TestPage基类替代自定义BasePage,减少维护成本。

总结

测试代码的维护性不是"锦上添花",是"生死攸关"。维护性差的测试代码,三个月后就是一堆没人敢碰的遗留代码。维护性好的测试代码,可以持续为项目保驾护航。

维度 评价
学习难度 ⭐⭐⭐ Page Object概念简单,设计合理的抽象层次需要经验
使用频率 ⭐⭐⭐⭐⭐ 每个测试项目都需要
重要程度 ⭐⭐⭐⭐⭐ 决定测试体系的寿命

核心原则:测试代码和业务代码同等重要。你不会在业务代码里到处硬编码、不封装、不重构,那测试代码也不应该。把Page Object模式、数据管理、重构策略用起来,让测试代码经得起时间的考验。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。