news 2026/9/18 10:10:58

基于Selenium的银行柜面系统自动化测试框架设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Selenium的银行柜面系统自动化测试框架设计

简介:针对银行核心系统版本迭代快、手工测试重复度高、辅助操作占用大量时间等问题,这份研究文档给出了基于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*****.xls,0000Sit是批次标识,后面跟交易ID,比如0000SitLogin.xls,一个文件对应一个交易的案例集。用例存Excel而不是直接写死在测试代码里,出发点很明确:测试工程师不写代码,只需要维护Excel里的案例数据,测试开发工程师负责把框架和脚本做好。两者角色分离,协作界面就是Excel文件本身的格式约定。案例文件的字段设计大致如下:

字段含义示例
caseId案例标识TC-LOGIN-001
userId柜员号100023
pwd密码test@123
orgId营业机构0101
expectResult期望结果登录成功,进入主界面
actualResult实际结果回写PASS / FAIL

actualResult这一列是预留的回写列。框架从Excel读取数据,TestNG的@DataProvider把每一行案例作为一组测试参数注入测试方法,执行完毕后再通过POI把PASS或FAIL写回对应行。这个回写动作是很多自建框架容易漏掉的步骤,没有它,后续就无法按执行结果筛选失败用例做定向回归。

4.2 POI读取与Java反射构建数据对象

数据读取链路由POI API加Java反射组成,POI负责打开工作簿,反射负责把一行数据组装成CaseBean对象:

public static List<CaseBean> readRows(String filePath, String sheetName) throws Exception { List<CaseBean> 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 { List<CaseBean> 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 name="core-bank-regression" verbose="1"> <test name="login-and-deposit"> <classes> <class name="com.bank.it.test.LoginCase"/> <class name="com.bank.it.test.DepositCase"/> </classes> </test> <test name="voucher-prepare"> <classes> <class name="com.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属性更贴近真实测试节奏,因为晚上批量跑出来的失败集合,本身就是当天最值得关注的风险清单。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 10:08:15

蓝屏代码全解读:从0xc000021a到unexpected store exception的排查指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 10:07:05

Reasonix 能力诊断快速清单:6 大能力的加载顺序与一分钟排障

Reasonix 能力诊断快速清单&#xff1a;6 大能力的加载顺序与一分钟排障 【免费下载链接】DeepSeek-Reasonix DeepSeek-native AI coding agent for your terminal. Engineered around prefix-cache stability — leave it running. 项目地址: https://gitcode.com/GitHub_Tr…

作者头像 李华