ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

银行核心系统Web自动化测试:Pytest+Selenium架构与数据断言实战

银行核心系统Web自动化测试:Pytest+Selenium架构与数据断言实战 简介这是一份面向银行核心系统测试人员、自动化测试工程师及金融科技从业者的专业技术文档聚焦基于Web的银行核心系统自动化测试研究与应用。文档以华夏银行实践为背景剖析版本更新频繁带来的测试数据预埋、环境准备、案例执行等重复性负担进而引入Selenium与POMPage Object Model设计模式详细阐述Bean、Inter、Page Objects、Implement等框架分层结构及测试开发与测试工程师的协作分工。内容还覆盖典型应用场景、优化成效与未来趋势对理解自动化测试落地路径、设计可扩展测试框架具有直接参考价值。文档还比较了UFT与Selenium在版权费用、扩展能力、跨平台支持等方面的差异为工具选型提供依据。资源为1个PDF文件压缩包大小约2.35MB便于下载后随时查阅。该资源已有355人学习适合正在规划或优化银行核心系统自动化测试体系的读者借鉴。1. 银行核心系统的自动化测试难点从来不在「自动化」上线一个基于 Web 的银行核心系统自动化测试项目第一轮全量回归的结果往往是这样的登录、开卡、转账、销户这些主流程都录进了脚本一百条用例跑完界面通过的只有四十条其余全挂在等待超时、弹窗未关闭、数据残留这类问题上重跑一遍挂掉的又换了一拨。这不是被测系统不稳定而是脚本把「自动化」想简单了。银行核心系统的 Web 页面背后连着账户、账务、额度和账务引擎每个测试动作都会产生真实的数据效应脚本必须回答三个问题数据从哪来、执行后怎么校验、失败后怎么恢复。下面按这个顺序展开先讲测试对象怎么拆、框架怎么选再给出一套可复现的 Pytest 加 Selenium 骨架然后落到参数化、数据管理和断言设计最后收在稳定性和回归提速上。适合正在做核心系统、柜面系统、网银渠道测试的开发或测试工程师也适合从功能测试转自动化、想搭第一套能扛住回归的框架的同学。2. 银行核心系统 Web 自动化测试的对象拆解与分层策略2.1 测试对象不止是页面渠道层、交易层、数据层各测什么银行核心系统对外提供的能力通常分两种形态面向客户的网银渠道和面向柜员的柜面系统两者都是典型的 Web 工程。但自动化测试不能只盯着页面元素。一笔转账用例在页面上表现为「输入账号、金额、点确认」背后其实涉及账户状态检查、借贷记账、流水登记、限额校验四类逻辑。把测试对象拆成三层来看渠道层Web 页面验证元素呈现、交互流程、提示文案、页面跳转回答「界面是否符合验收标准」交易层接口与应用服务验证报文格式、金额精度、账户合法性、交易幂等性这些校验在 UI 上不一定暴露数据层数据库与账务验证余额变动、分户账、总账、交易流水的一致性这是核心系统自动化区别于普通 Web 测试的关键。三层不是并列关系而是递进关系UI 层通过只能说明页面没报错数据层校验通过才能说明交易真的成功。实践中见过不少项目花大力气做 UI 断言真实业务验收时发现数据库里挂了两笔重复交易——脚本双击了确认按钮幂等校验在接口层却没覆盖。所以分层的第一原则是UI 验证流程接口验证规则数据库验证结果。2.2 框架选型UI 自动化、接口自动化与数据库校验的分工当前核心系统自动化测试的主流选择是 Python 加 Pytest 加 Selenium 这套组合。Selenium 承担 Web 页面操作Pytest 提供参数化、fixture 和报告能力配合 pymysql 或 cx_Oracle 直连数据库做数据断言。选它的原因不是功能最强而是生态完整、资料多、招人容易。银行项目里 Java 系TestNG 加 WebDriver也常见两者没有本质差别选团队最熟的一门语言即可。测试层常用手段验证内容维护成本稳定性UISelenium 4 WebDriver页面流程、交互、提示高低受前端改动影响接口requests pytest交易规则、报文、金额精度中高数据pymysql / cx_Oracle SQL余额、流水、总分核对中高需要强调的是不必追求全链路 UI 自动化。核心系统的开户、结息、冲正这类操作通常有批量任务参与UI 层看到「成功」不代表批处理之后的最终状态正确这些场景用接口加数据校验比 UI 可靠得多。近年生成式编程工具Claude、Codex 辅助生成的脚本确实能加速 Page Object 这类样板代码的编写但核心系统的金额精度和账务断言逻辑仍需逐条人工确认AI 生成的代码直接进回归集是踩坑重灾区。2.3 测试环境的三个硬性要求可重置、能造数、依赖挡板化银行核心系统对测试环境的要求比一般 Web 项目严苛最常踩坑的有三点。第一环境必须能重置数据。核心系统每个工作日跑批处理结息、计提会改写余额和账户状态。测试环境需要提供数据库还原或快照恢复能力否则用例执行到第二个工作日时上一轮的结息数据会让断言全部失效。常见做法是维护一套可定时还原的数据库快照回归前统一还原到基线。第二要有独立的账务初始化工具。造数不能只靠页面操作开户、发卡这类前置流程本身就要十几个步骤。国内银行项目的通行做法是直接调用核心系统的初始化接口或执行注入 SQL 预置账户把「准备数据」和「执行用例」解耦用例里只依赖余额已知的账户不关心账户是怎么来的。第三外部依赖要挡板化。核心系统联调对象多支付清算、征信查询这类外部系统在测试环境不稳定自动化结果会持续飘红。对高频外部依赖做挡板回放固定响应回归结果才可解释、可定位。提示环境隔离做得越干净调脚本的成本越低。用例失败先问「环境是否基线状态」再怀疑脚本。3. 用 Pytest Selenium 搭建核心系统自动化测试骨架3.1 工程目录与 Page Object 的合理粒度一个能长期维护的核心系统自动化工程目录建议按业务域组织而不是按页面组织corebank_auto/ ├── conftest.py # 全局 fixturedriver、db、数据准备 ├── config/ │ ├── env.yaml # 环境地址、数据库连接、账号配置 │ └── user.yaml # 测试柜员与客户信息 ├── pages/ # Page Object按业务域划分 │ ├── login_page.py │ ├── transfer_page.py │ └── account_query_page.py ├── datas/ │ ├── init_account.sql # 预置测试账户 │ └── clean_account.sql # 用例后清理 ├── tests/ │ ├── test_login.py │ ├── test_transfer.py │ └── test_account_query.py └── utils/ ├── dbhelper.py # 数据库查询封装 └── excel_reader.py # 批量参数读取Page Object 的粒度以「业务操作」为单位而不是以「页面元素」为单位。以转账页为例暴露给用例层的是transfer(pay_account, payee_account, amount)方法而不是set_amount_input()这种逐元素方法。这样页面改版时只需要改 Page 内部实现用例层代码不动。核心系统页面信息密度高、校验项多元素定位优先用稳定的 id 或 name银行项目里前端框架升级频繁xpath 里带样式类名的定位方式最容易崩能少用就少用。3.2 session 级 fixture 管理登录态登录是绝大多数用例的前置策略上既要快又要隔离。每次用例都登录执行时间翻倍整个会话只登录一次多用例共享一个操作员会话虽然省时间但核心系统风控模块有时会锁定长时间不操作的会话还会因为共享页面状态导致用例相互干扰。常见做法是 session 级 fixture 做登录function 级 fixture 在需要时切换操作员import pytest from selenium import webdriver from selenium.webdriver.chrome.options import Options pytest.fixture(scopesession) def driver(): options Options() # 去掉自动化控制条页面行为更接近真实用户 options.add_experimental_option(excludeSwitches, [enable-automation]) # 流水线上无头运行本地调试时注释掉 options.add_argument(--headlessnew) options.add_argument(--window-size1920,1080) d webdriver.Chrome(optionsoptions) d.implicitly_wait(5) # 全局兜底等待具体场景用显式等待 yield d d.quit() pytest.fixture(scopesession) def logged_in(driver): driver.get(ENV[portal_url]) LoginPage(driver).login(USER[teller_01], USER[password]) return driver参数说明scopesession让整个测试会话只启动一次浏览器、完成一次登录比 function 级快得多implicitly_wait(5)是全局兜底只用于元素查找的默认等待不能替代对业务结果的显式等待--headlessnew是 Chrome 的新无头模式渲染比旧模式更接近真实浏览器核心系统页面大量使用弹层和日期控件旧无头模式偶尔渲染异常新模式下明显减少。ENV和USER是 config 模块读取的字典实际项目通过 yaml 加载环境地址、柜员号不能硬编码进测试类。核心系统登录通常有验证码自动化环境要提前和开发约定关闭验证码开关或使用万能码。这一点必须在环境准备阶段确认不要在脚本里做识别验证码这种事稳定性极差且毫无必要。3.3 一个完整的转账用例页面操作加数据库断言下面是一个覆盖「数据准备、页面操作、数据库校验」的转账用例import pytest from decimal import Decimal from utils.dbhelper import fetch_one def test_transfer_success(logged_in, db_conn, prepared_accounts): # prepared_accounts 返回 (转出账号, 转入账号, 双方初始余额) acct_from, acct_to, bal_from, bal_to prepared_accounts amount Decimal(1000.00) # 1. 页面操作用例层看不到 find_element TransferPage(logged_in).transfer( pay_accountacct_from, payee_accountacct_to, amount1000.00 ) # 2. 页面层断言只验证成功提示 assert 交易成功 in logged_in.page_source # 3. 数据层断言逐字段核对双方余额 row_from fetch_one(db_conn, SELECT balance FROM deposit_acct WHERE acct_no%s, acct_from) row_to fetch_one(db_conn, SELECT balance FROM deposit_acct WHERE acct_no%s, acct_to) assert row_from[balance] bal_from - amount assert row_to[balance] bal_to amount # 4. 流水层断言借贷双方各一条记账记录 biz_id TransferPage(logged_in).get_biz_id() row_flow fetch_one(db_conn, SELECT COUNT(*) AS cnt, SUM(amount) AS total FROM trans_flow WHERE biz_id%s, biz_id) assert row_flow[cnt] 2 and row_flow[total] str(amount * 2)逻辑说明页面断言只做成功提示这一层真正的成败判定交给数据库。prepared_accounts是一个用例级 fixture在用例执行前通过初始化接口预置两个余额已知的账户同时返回初始余额供断言使用脚本里不硬编码余额避免数据漂移。金额断言用Decimal而不是浮点数核心系统金额精度精确到分浮点数的二进制存储会在 0.1 加 0.2 这类场景产生精度误差账务领域一点都不能差。流水断言的 2 条记录对应借贷记账法下一笔转账在本方、对方各生成一条明细SUM(amount)用来做平账检查。fetch_one是 dbhelper 对游标查询的封装返回第一行字典get_biz_id从成功提示页的交易流水号元素取值各家核心系统这个字段的位置和命名不同统一封装在 Page 类里即可。3.4 数据库连接与数据准备的 fixture 封装数据库校验是核心技术系统自动化区别于常规 Web 测试的关键连接要单独管理pytest.fixture(scopesession) def db_conn(): import pymysql conn pymysql.connect( hostENV[db_host], port3306, userENV[db_user], passwordENV[db_pwd], databaseENV[db_name], charsetutf8mb4, cursorclasspymysql.cursors.DictCursor, autocommitTrue ) yield conn conn.close()参数说明autocommitTrue是刻意的——测试环境需要断言立刻看到写入结果走事务的话未提交数据在另一个连接里不可见反而制造脏读假象。DictCursor让查询结果以字段名访问代码可读性比元组好。核心系统用的数据库如果是 Oracle把pymysql.connect换成cx_Oracle.connectSQL 占位符从%s改成:param风格。数据库连接建议用只读账号写操作只通过初始化接口执行防止测试脚本误改基线数据。提示跑用例前先确认数据库账号能查询核心账务表。很多测试库对生产同构权限开得严SELECT 之外的操作一律拒绝这反而安全脚本不要试图绕过。4. 银行核心系统自动化测试的参数化设计与数据管理4.1 用 parametrize 覆盖金额校验分支核心系统用例的特点是同一操作大量分支。同一笔转账金额上限、下限、小数点位数、币种、节假日判断都影响结果为每个分支写一个函数会让用例文件膨胀到无法维护。参数化是标准解法import pytest pytest.mark.parametrize( amount, expect_code, expect_db, [ (0.01, 0000, success), # 最小金额分位 (9999999.99, 0000, success), # 限额内最大金额 (0, E1001, no_trans), # 零金额应拒绝 (-100, E1002, no_trans), # 负数应拒绝 (100.001, E1003, no_trans), # 超精度应拒绝 ], ids[min_ok, max_ok, zero_reject, neg_reject, precision_reject] ) def test_transfer_amount_rule(logged_in, db_conn, prepared_accounts, amount, expect_code, expect_db): acct_from, acct_to, bal_from, bal_to prepared_accounts result TransferPage(logged_in).transfer(acct_from, acct_to, amount) assert result.code expect_code if expect_db success: row fetch_one(db_conn, SELECT balance FROM deposit_acct WHERE acct_no%s, acct_from) assert row[balance] bal_from - Decimal(amount) else: row fetch_one(db_conn, SELECT COUNT(*) AS cnt FROM trans_flow WHERE acct_no%s, acct_from) assert row[cnt] 0参数说明expect_code对应核心系统返回的交易响应码各家银行编码体系不同脚本里必须使用被测环境实际返回的码值不要靠猜。ids参数为每条用例生成可读名称pytest 默认用参数值做用例名金额参数一多报告就分不清哪条是哪条。金额参数用字符串传而不是数字是为了保证Decimal构造精度——浮点参数会在进入断言前就丢了精度。expect_db做分支断言预期成功的才查余额预期拒绝的只核对该账号没有新增流水减少无谓的数据库查询。这张参数表同时承担了需求文档的角色金额校验规则以用例形式沉淀在代码里业务变更先改表再改逻辑比维护一份游离的用例文档可靠。4.2 测试数据的生成标识与清理策略核心系统最忌讳「跑一次留一堆」的测试数据。设计数据时让每个用例持有自己的标识既方便断言也方便清理。常见做法是统一前缀加时间戳import random import time def gen_acct_no(prefixAUTO): # 测试账户号AUTO 业务域编码 时间戳 随机序列 biz_code 01 # 01表示活期02表示定期按项目约定扩展 ts time.strftime(%H%M%S) return f{prefix}{biz_code}{ts}{random.randint(1000, 9999)}数据清理不能只靠 DELETE。核心系统的表之间有外键关联流水表引用账户表直接删主表会连带删掉不该动的东西。更稳的策略是按业务标识做逻辑作废把测试账户状态置为无效、余额归零、流水保留。清理 SQL 要限定本次用例的前缀范围避免误伤其他团队的测试数据。还有一种情况无法用清理解决结息、日切这类批处理产生的数据遍布全表这类用例要固定在日切窗口后执行执行前先还原环境快照不要试图在用例里逐条擦数据。4.3 三层断言设计页面、交易、总分核对断言不到位是核心系统自动化返工最常见的原因。断言按三层设计逐层递进页面层提示文案、跳转结果只证明页面执行完毕交易层响应码、流水表记录条数证明交易被受理并记账总分核对层分户账余额汇总与总账一致证明账务是平的。实际项目里可以定一条硬指标每个业务用例至少包含一条数据库断言纯页面断言的用例不允许进回归集。这样做的代价是编写成本上升换来的是失败定位时间从小时级降到分钟级。一个常见的误用是拿driver.page_source里是否包含「成功」二字做唯一断言页面改版时这类断言首当其冲失效率飙升替换方案是断言交易流水号已生成并入库页面文案只当辅助信息。总分核对层可以单独抽用例做。执行完一批交易后用一条汇总 SQL 比较分户账余额合计与总账科目余额差值非零立即报警。这个核对语句不要每次用例都跑作为日终回归的一个独立用例效率更高。5. Web 自动化测试的稳定性治理与回归提速5.1 重试只对「环境类失败」开放核心系统回归最大的成本来自不稳定失败同一个用例这次过、下次挂结果没人敢信。治理思路是先区分两类失败。业务失败——断言不通过、响应码不对——不能重试余额和流水已经变了重跑只会多产生一笔交易数据越跑越脏。环境类失败——元素超时、连接重置——可以有限重试pytest-rerunfailures 的常用配置是重试 2 次、间隔 1 秒pip install pytest-rerunfailures pytest tests/ --reruns 2 --reruns-delay 1注意插件对所有失败一视同仁直接全局开重试会掩盖真实缺陷最好把重试限定在显式的环境异常上。失败现场的收集比重试更关键在 conftest 里挂一个截图钩子失败即落盘文件名带用例名和时间戳当天就能定位pytest.hookimpl(tryfirstTrue, hookwrapperTrue) def pytest_runtest_makereport(item, call): outcome yield rep outcome.get_result() if rep.when call and rep.failed: driver item.funcargs.get(driver) if driver: driver.save_screenshot( freports/{item.name}_{int(time.time())}.png)5.2 并行加速先过数据隔离关提速最直接的手段是 pytest-xdist 并行但核心系统场景先自查两条每个用例是否用独立账户数据固定账号的用例并行后必挂是否每个 worker 独立创建浏览器实例fixture 里不要有全局单例。满足之后再从 4 个 worker 起步观察数据库连接数和交易响应时间逐步上调。流水线里给回归单独建 job与编译部署解耦避免发布节奏被半小时起的回归拖住。并行改造后有一个值得固定下来的验证动作同一套用例集串行、并行各跑一遍对比失败集合。并行独有的失败几乎都是数据冲突两边都挂的才需要查脚本这个对比能快速给稳定性问题定性。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进