
简介针对银行核心系统版本迭代快、手工测试重复度高、辅助操作占用大量时间等问题这份研究文档给出了基于Web的自动化测试完整方案。资源为单个PDF文件容量仅2.35MB内容来自华夏银行课题组的实际项目实践适合银行测试人员、金融科技开发者以及自动化测试初学者参考。文中对比了商业与开源自动化工具的优劣势并围绕Selenium与POM模式设计了测试框架涵盖Bean、Inter、Page Objects、Implement等核心模块明确了测试开发与测试工程师的分工借助数据驱动方式完成用例执行、结果分析与异常复测可覆盖登录、开立账户、存款转账等高频典型场景。读者可以从中获得工具选型思路、框架搭建逻辑和优化成效用于改进自己的测试流程。目前已有355人学习该资源。1. 自动化测试切入柜面系统重复操作才是主战场自动化测试做得久的人都有体会银行核心系统这类业务最消耗人的往往不是设计用例而是一遍遍准备客户号、存款账号、贷款账号、现金存款、凭证领用以及执行同一笔交易几百次并截屏留证。华夏银行课题组把完整测试流程拆开后发现只有设计测试案例和分析测试结果这两件事对软件质量有直接影响其余操作都是辅助测试环节却占用了测试人员大半时间。于是他们选择了基于Web的Selenium方案把重复操作交给自动化定位是保证已投产功能可运行、辅助手工测试快速验证新功能。下文把工具选型、POM分层、数据驱动链路和场景落地逐层拆开讲给准备自建自动化测试框架的团队留一份可落地的工程参考。2. Selenium与UFT选型开源协议与Web适配度的取舍2.1 两大工具的实际差异与选型理由选型是在两个当时排名最靠前的工具之间展开的HP UFT与Selenium。UFT是老牌商业工具版权费用高覆盖Web、Mobile、Desktop三种终端且有完整的官方技术支持渠道Selenium则是开源项目采用Apache 2.0协议只覆盖Web端通过浏览器驱动加编程语言绑定的方式工作。对银行这类采购流程敏感、又希望把测试资产握在自己手里的行业来说Selenium第一个加分项是license成本为零第二个加分项是语言生态——支持Java、Python、C#、JavaScript、Ruby等主流语言测试团队可以直接复用Java开发体系后续再扩展接口自动化测试框架时也不需要另起一套工具栈。对比项HP UFTSelenium版权费用商业工具license昂贵开源免费Apache 2.0可测产品Web、Mobile、Desktop仅Web可通过插件扩展脚本语言以VBScript为主Java、Python、C#、JavaScript等跨平台仅支持Windows支持大部分平台CI集成Jenkins、HP Quality CenterJenkins、Cruise Control等录制回放支持多终端录制回放Selenium IDE支持Firefox和Chrome录制回放表格背后还有一层长期成本考量UFT的脚本资产一旦沉淀在VBScript里后续团队要么持续支付UFT授权要么做一次语言迁移Selenium写出来的测试代码就是普通Java工程走Git管理、走Jenkins构建都没有额外门槛测试资产的自研和沉淀路径明显更顺。华夏银行课题组最终也是围绕版权费用、扩展能力、自研能力这三项确认了Selenium方案。2.2 柜面Web系统为什么天然适配Selenium选型成立还需要被测对象配合。课题组选择问题相对集中的核心柜面系统做验证分析出的交易特征有四条交易功能和交易ID明确、交易跳转逻辑清晰、页面元素操作简单且重复性大、元素定位信息简洁。这四点与Selenium的优劣势恰好匹配。交易ID明确意味着测试数据可以按ID维度组织一个交易ID对应一组固定的前置数据和案例集合跳转逻辑清晰意味着页面对象之间是线性关系POM模式容易建模元素操作重复性强、定位信息简洁意味着大多数控件可以靠稳定的id或name属性定位脚本不容易因为页面微调而大面积失败。同时还需要划清自动化边界。UI布局、视觉感官、音视频同步、模糊判断、异常结果判断、新增功能快速验证这些方向Web层面的自动化工具并不擅长课题组也明确把它们保留给手工测试。这个边界直接决定了框架定位自动化回归管的是“已投产功能还能正常运行”手工测试管的是“新功能是否达到预期”。两条线并行比指望自动化全量替代手工更现实。另外还有一个常被忽略的工程细节Selenium对浏览器版本和驱动版本敏感ChromeDriver版本与浏览器不匹配时用例会全部起不来所以框架初始化环境时要把浏览器和驱动版本固定写死在配置里而不是让执行机器各自装。3. POM框架的包结构拆解Bean到Implement的分层逻辑3.1 四包分层的设计意图很多POM示例只拆两层——页面对象层和测试用例层但华夏银行课题组给的是更工程化的分层Bean、Inter、Page Objects、Implement四个包管页面抽象Only Test包管测试方法Util和MySQL归入通用工具库。初次接触会觉得页面相关代码分四个包有点重但放到柜面系统的场景里是合理的。Bean存放交易数据对象Excel一行案例对应一个Bean实例Inter负责定义页面元素的定位契约和交易动作的接口签名Page Objects把页面上的真实控件操作组织成业务动作Implement实现Inter接口把接口方法绑定到具体页面路径和元素属性。这种分层的主要收益是变更隔离。页面元素属性变了只改对应Implement页面交互步骤变了只改Page Objects交易数据字段变了只改Bean。三者互不牵连。如果接手这套工程不建议把Inter和Implement合并成一个类柜面系统交易类型多同一套登录逻辑在不同机构版本里元素路径可能不同Inter定义契约、Implement按版本出多个实现类测试方法里的调用逻辑保持不动扩展成本最低。Util包里放的是Excel读写、截图、日志这些横切能力MySQL存储则用来沉淀预埋的测试数据和执行结果趋势Excel保存案例定义MySQL保存长期数据两份数据各司其职。3.2 页面对象与测试脚本的职责分离先从Inter这层看接口定义它只声明页面有什么能力// Inter定义登录页面的定位契约不写具体元素路径 public interface ILoginPage { By userIdInput(); By pwdInput(); By loginButton(); void login(String userId, String pwd); }接口层不出现任何具体的XPath或id值只暴露页面提供的能力。真正的定位信息放在Implement层// Implement实现登录页面这里才出现具体定位信息 public class LoginPageImpl implements ILoginPage { private WebDriver driver; public LoginPageImpl(WebDriver driver) { this.driver driver; } Override public By userIdInput() { return By.id(userId); // 柜面系统页面元素带固定id优先使用id定位 } Override public By pwdInput() { return By.name(password); } Override public By loginButton() { return By.xpath(//button[contains(text(),登录)]); } Override public void login(String userId, String pwd) { driver.findElement(userIdInput()).clear(); driver.findElement(userIdInput()).sendKeys(userId); driver.findElement(pwdInput()).clear(); driver.findElement(pwdInput()).sendKeys(pwd); driver.findElement(loginButton()).click(); } }这段代码把元素定位和操作细节全部留在页面实现里测试方法完全不需要知道登录按钮长什么样。后续即使登录页从普通按钮改成动态加载控件只要login方法签名不变调用方的测试代码一行都不用动。Only Test包的测试方法只做业务编排和断言// Only Test只定义测试逻辑与断言页面细节完全隔离 public class LoginCase { Test(dataProvider caseData) public void testLogin(CaseBean bean) { WebDriver driver DriverFactory.getDriver(); ILoginPage loginPage new LoginPageImpl(driver); loginPage.login(bean.getUserId(), bean.getPwd()); // 断言策略放在测试方法里页面对象只负责操作 Assert.assertTrue(driver.getPageSource().contains(业务主界面)); } }这里把断言放在测试方法而不是页面对象里是为了让页面对象专注操作、测试方法专注验证。注意CaseBean从哪来——它就是Excel里那一行测试案例的数据载体也是下一章数据驱动链路的入口。4. 数据驱动链路POI读取、TestNG注解与Java反射4.1 用例文件的组织方式与字段设计这套框架的用例文件是Excel格式命名形如0000Sit*****.xls0000Sit是批次标识后面跟交易ID比如0000SitLogin.xls一个文件对应一个交易的案例集。用例存Excel而不是直接写死在测试代码里出发点很明确测试工程师不写代码只需要维护Excel里的案例数据测试开发工程师负责把框架和脚本做好。两者角色分离协作界面就是Excel文件本身的格式约定。案例文件的字段设计大致如下字段含义示例caseId案例标识TC-LOGIN-001userId柜员号100023pwd密码test123orgId营业机构0101expectResult期望结果登录成功进入主界面actualResult实际结果回写PASS / FAILactualResult这一列是预留的回写列。框架从Excel读取数据TestNG的DataProvider把每一行案例作为一组测试参数注入测试方法执行完毕后再通过POI把PASS或FAIL写回对应行。这个回写动作是很多自建框架容易漏掉的步骤没有它后续就无法按执行结果筛选失败用例做定向回归。4.2 POI读取与Java反射构建数据对象数据读取链路由POI API加Java反射组成POI负责打开工作簿反射负责把一行数据组装成CaseBean对象public static ListCaseBean readRows(String filePath, String sheetName) throws Exception { ListCaseBean result new ArrayList(); Workbook wb WorkbookFactory.create(new FileInputStream(filePath)); Sheet sheet wb.getSheet(sheetName); Row headRow sheet.getRow(0); for (int r 1; r sheet.getLastRowNum(); r) { Row row sheet.getRow(r); if (row null) { continue; } CaseBean bean new CaseBean(); for (Cell cell : row) { String fieldName toCamel(headRow.getCell(cell.getColumnIndex()).getStringCellValue()); String value cell.toString(); // 由表头拼setter方法名如user_id - setUserId String setter set fieldName.substring(0, 1).toUpperCase() fieldName.substring(1); CaseBean.class.getMethod(setter, String.class).invoke(bean, value); } result.add(bean); } wb.close(); return result; }这里的核心技巧是反射拼setter表头文本经过toCamel转成驼峰例如user_id转成userId再拼接成setUserId方法名。这样做的好处是Excel新增列时只需要在CaseBean里加字段读取方法不用改。测试工程师在Excel里加一列“业务类型”框架层面无感适配。注意value统一按字符串处理遇到日期或数值列需要单独做类型转换单元格为空时要跳过否则拼出的setter调用会传null需要在Bean的setter里做空值处理。4.3 TestNG数据供给与执行结果回写TestNG侧的工作是把读取到的案例列表转成数据供给源DataProvider(name caseData) public Object[][] provideCaseData() throws Exception { ListCaseBean cases ExcelReader.readRows(0000SitLogin.xls, login); Object[][] params new Object[cases.size()][1]; for (int i 0; i cases.size(); i) { params[i][0] cases.get(i); } return params; } Test(dataProvider caseData) public void runCase(CaseBean caseData) { String status PASS; try { new LoginPageImpl(driver).login(caseData.getUserId(), caseData.getPwd()); Assert.assertTrue(driver.getPageSource().contains(caseData.getExpectResult())); } catch (Throwable t) { status FAIL; // 记录失败状态不吞异常 throw t; } finally { ExcelWriter.writeResult(0000SitLogin.xls, caseData.getCaseId(), status); } }dataProvider返回的二维数组第一维是案例数量第二维是每个测试方法的参数列表这里固定为1因为每个测试方法只接收一个CaseBean。finally块里回写结果保证失败时也有记录抛出异常则是为了TestNG能统计失败案例并生成测试报告。整条链路到这里就闭合了Excel进、Java对象出、TestNG驱动执行、结果再进Excel。实际操作时要注意POI的XSSFWorkbook写回时不能被另一个进程同时打开同一份文件否则会抛文件锁异常常见做法是执行前复制一份临时副本回写的是副本原文件留作归档。5. 典型交易场景落地与执行效率验证5.1 高频交易工具化的包装方式框架跑通之后要回答一个问题自动化到底用在哪些场景最值得。课题组的做法是把通用性强、使用频率高的交易包装成通用工具测试人员直接调用不关心内部实现。柜面登录、开立账户、存款转账、凭证领用这一类交易都具备“交易ID明确、跳转固定、操作重复性大”的特征非常适合工具化。交易场景前置数据页面对象封装断言要点柜面登录柜员号、密码、营业机构LoginPageImpl登录后进入主界面开立账户客户号、证件类型、证件号码OpenAccountPageImpl回显账号不为空存款转账存款账号、贷款账号、转账金额DepositPageImpl账户余额刷新正确凭证领用凭证类型、起始号码、终止号码VoucherPageImpl凭证状态变为已领用这个表格里的前置数据不是随便写的它反映了银行核心系统测试的依赖关系存款转账必须先有客户号开立账户必须先有证件信息凭证领用必须在已有机构下操作。测试数据的预埋工作由框架统一处理客户号、存款账号、贷款账号、现金存款这些基础数据可以提前批量生成被测环境初始化时由脚本自动铺底取代手工一件件录入。开发阶段测试人员还能拿这套框架同步调试测试代码协助开发完成内部验证。5.2 58.3%覆盖率是怎么算出来的课题组在“会计业务柜面交易授权优化”项目中验证了这套框架自动化测试覆盖率达到58.3%。这个覆盖率不是代码行覆盖率而是回归案例覆盖率也就是自动执行的测试案例数除以该批次全部回归案例数。超过一半的回归案例由自动化执行剩下约四成是新增功能验证、异常路径判断和人工复核类案例。对第一次引入自动化的银行核心系统项目来说这个比例已经能明显压降人力成本。回归案例怎么组织工程上依赖testng.xml控制执行顺序suite namecore-bank-regression verbose1 test namelogin-and-deposit classes class namecom.bank.it.test.LoginCase/ class namecom.bank.it.test.DepositCase/ /classes /test test namevoucher-prepare classes class namecom.bank.it.test.VoucherCase/ /classes /test /suite实际项目中凭证领用这类案例往往依赖前面交易产生的数据这种依赖关系放在suite层用dependsOnGroups维护而不是写在测试方法里这样每个测试方法保持独立任意一个案例都能单独执行调试。执行的效率提升不只是执行本身测试完成后的接口文档、测试截图文档、测试报告也由框架自动生成过去需要人工截屏留证的工作直接归零。整体收益最明显的是数据预埋阶段从小时级降到分钟级这也是当初把它作为自动化测试切入点的原因。6. 回归用例的维护技巧与失败定位6.1 元素定位与失败截图的维护要点自动化测试上线一段时间后真正的成本会从写脚本转向维护脚本。柜面系统页面属性相对固定但每次版本迭代还是会有元素调整维护动作要前置。元素定位优先级建议按id、name、固定class、相对XPath的顺序来不要用绝对XPath路径例如/html/body/div[3]/form/div[2]/button这种页面结构只要多包一层div就会断。落到框架层面就是不同页面类里统一封装定位常量页面调整时只改对应的实现类测试方法不受影响。断言失败之后第一件事是区分脚本问题还是系统缺陷。可以用统一截图加日志的方式降低排查成本public static void saveScreenshot(WebDriver driver, String caseId) { File screenshot ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE); FileUtils.copyFile(screenshot, new File(target/report/ caseId .png)); }截图文件名用caseId关联Excel里的案例编号失败案例当天就能按编号找到截图和日志。截图页面状态正常但断言没过大概率是系统缺陷需要提缺陷单页面还停留在加载中或者元素找不到则检查定位方式和等待条件。等待逻辑建议用WebDriverWait加expectedConditions做显式等待等元素可见或可点击再操作不要用固定sleep批量回归场景下固定sleep会把总执行时间拖长几倍。6.2 用回写结果驱动次日回归的优先级第4章里讲到的actualResult回写在维护阶段会体现出真正的价值。执行完成后Excel里带着每个案例的执行状态把它作为次日回归排序的依据全量回归放在夜间跑第二天回到工位先过滤出上次执行FAILED的案例重新执行一遍确认是环境抖动还是真实缺陷再决定是否提缺陷单没有失败历史的大批量案例放后面。这个习惯比把执行顺序全部交给TestNG的priority属性更贴近真实测试节奏因为晚上批量跑出来的失败集合本身就是当天最值得关注的风险清单。本文还有配套的精品资源点击获取