前阵子总算把商城系统的自动化测试一期项目收尾,测试报告在评审会上逐页过完,研发和业务才终于不再觉得自动化是“测试写给自己看的自嗨产物”。回头整理这整套实施过程,我最大的感受是:商城系统的自动化测试,真正的难点从来不是某个工具跑通一个小demo,而是把这条业务链路上散落的规则拆成可验证的场景,再让这些场景在版本迭代中稳定运行、持续产出可信的结果。
这篇文章我会完整复盘一遍这个项目——从为什么做、测试场景怎么拆,到接口自动化和UI自动化框架怎么搭、报告怎么出才有人看、上线之后怎么避免烂尾。不管你是刚接手电商业务的测试,还是正打算在公司从零开始搭自动化测试框架,这条路线上的核心决策和坑,我会尽量一次说透。
1. 动手前先想清楚:商城系统自动化测试的价值与边界
1.1 商城链路复杂度比想象中高,自动化为何值得投入
很多人以为商城系统自动化测试无非是“登录、搜索、加购、下单、支付”这一条流程跑通就完事,真做起来才发现完全不是这么回事。商城系统的业务链路是所有业务系统里最长、最复杂的那一档,订单从创建到完成要经历好几个状态节点,每个节点还牵扯库存、优惠、支付渠道、物流等多个外部依赖。
我负责的被测系统是典型的PHP技术栈商城,业务规则散落在各个服务模块里,光订单状态字段就有十几种组合。更麻烦的是,商城的功能迭代速度非常快——营销活动几乎每周都有,商品SPU和SKU各种上下架规则随时调整,如果完全靠手工回归,一轮核心用例跑下来要一个测试人力搭上两三天,而且很容易漏掉状态组合里的隐蔽问题。
自动化测试在这里的价值不只是省人力,而是把“验证”变成常态。夜间跑完核心回归,第二天早上直接看结论,异常用例自动归类,这种节奏会让测试的角色从“到处救火”变成“提前排雷”。不过前提是:场景设计得对、框架搭得稳、报告有人信,这三件事缺一件,自动化最后都会沦为摆设。
1.2 哪些商城功能适合自动化,哪些不适合
在建自动化用例之前,最好先做一轮筛选,不是所有功能都值得自动化。我一开始也犯过“什么都想自动跑”的毛病,后来被维护成本教育了才学乖。
根据商城系统的业务特征,我总结了一套判断标准:
- 适合自动化的场景:核心购物流程、订单状态流转、优惠计算、支付回调处理、库存扣减逻辑,以及每次发版都必须回归的功能。这些场景规则固定、出现频率高、人工重复验证成本大,自动化的投入产出比最高。
- 适合保留人工测试的场景:视觉与交互体验检查、复杂促销活动的临时页面探索、需要真金白银的支付渠道联调、需要大量主观判断的异常界面提示。自动化脚本很难判断“这个弹窗风格是否符合预期”,这类工作交给人工更合适。
- 暂时不适合自动化的场景:一次性活动页面、频繁调整文案和布局的营销落地页。这类页面今天写完明天就改,脚本维护速度永远赶不上页面变化速度。
1.3 先算清ROI,再决定自动化做到什么程度
做自动化最怕老板问“这能省多少人天”,所以动手前我先粗略算了笔账,这也是后来项目能持续推进的关键。
我以主流程回归为例做了估算:手工执行一轮商城核心用例大约需要6个小时,按测试人力成本折算,每轮大约花掉一天。用自动化跑同样的用例,服务器执行15分钟,加上人工分析失败结果的时间约30分钟。假设每月发版4次、每次至少回归2轮,那么自动化每月能节省大约7个人日。
但自动化不是免费午餐,脚本维护本身要花时间。我估过一个经验值:UI层用例每100条每周维护约3到5小时,接口层用例要便宜很多,每100条每周1到2小时就够。这个模型会直接影响技术选型——为什么我坚持接口自动化优先、UI自动化做精选,而不是反过来,后面第2章会展开讲。
2. 测试场景怎么拆:从业务链路到可复用用例
2.1 分层设计:接口测规则、UI测体验、数据做地基
商城系统的自动化测试不能把宝全押在UI这一层。用过Selenium的同学都懂,页面元素稍微动一下class,脚本就红一片。真正稳妥的做法是分层:接口层覆盖业务规则,UI层只跑最关键的用户端到端链路,底层再用一套测试数据准备机制托住所有用例。
我把这套方案称为“接口测规则、UI测体验、数据做地基”。接口自动化用来验证下单、支付回调、库存锁定、优惠计算这些业务逻辑;UI自动化只验证用户真实操作的链路是否通;数据层统一解决“用例执行前需要什么数据、执行后怎么清理”的问题。三件事分开做,各自聚焦,维护成本才有机会压住。
为什么商城系统尤其适合这种分层?因为商城的业务核心是状态机和资金库存,这些逻辑全部体现在接口返回和数据库记录里,用接口来验证最直接、最稳定。而UI上那些成功提示、按钮交互、跳转体验,属于体验层面的验证,数量不必多,选几条核心链路覆盖住就好。
2.2 核心业务场景拆解:从商品浏览到订单关闭
场景拆解是整个项目里最值得花时间的环节,拆得好后面的脚本才有意义。针对商城系统,我按用户主流程和业务异常流两条线索来做拆分。
主流程链路是:浏览商品→加入购物车→确认订单→提交订单→完成支付→订单发货→确认收货→申请售后。这条链路每经过一个节点,订单状态都会变化,每个变化点都是一组断言对象。比如“提交订单后,订单状态应为待付款,库存应被锁定,但优惠券状态应变为已占用”。
异常流和分支流反而是自动化更容易发现bug的地方。商城系统里常见的异常分支包括:商品库存不足时下单、优惠券已过期、购物车里的商品已下架、支付超时后系统自动取消订单、退款申请被驳回后状态是否正确回滚。这些分支在手工回归里最容易漏,因为构造异常数据本身就很费劲,但自动化可以通过接口造数快速完成。
2.3 订单状态机与幂等性校验:最容易忽略的测试死角
给商城系统做自动化测试,最怕的是只照着页面点点点,而没去验证底层规则。我强烈建议把订单状态机和支付幂等性单独做成一套接口测试用例集,这部分的性价比极高。
举个真实例子:支付回调是商城系统的高危环节,第三方支付平台会自动重试回调,如果系统没有做幂等处理,同一笔订单可能被重复入账。要验证这个问题,手工测试很难模拟连续两次回调,但自动化只需要把同一个回调报文连续发两次,再断言订单金额只累加一次、支付记录只有一条,整个过程几秒钟。
订单状态机测试也是同样的逻辑。正常用户在界面上是没法把一笔“已发货”的订单改成“待付款”的,但接口入参一旦被人恶意构造或代码逻辑出现疏漏,就可能产生非法状态流转。自动化可以用接口直接发起非法状态变更请求,验证后端是否做了权限与合法性校验。这类用例是商城系统自动化里最有含金量的部分。
3. 从零搭接口自动化测试框架:Python + Requests + Pytest
3.1 先定一个简洁的框架结构,别贪大
商城系统的接口自动化,我选的是Python技术栈,核心组合是Requests加Pytest加Allure。这套组合足够成熟,社区资料多,团队上手快,而且没有商业授权压力。框架结构我没有搞得太复杂,保持了清晰分层的目录:
. ├── apis # 接口请求封装 │ ├── order_api.py │ ├── cart_api.py │ └── payment_api.py ├── common # 公共组件 │ ├── http_client.py │ ├── db_client.py │ └── log_util.py ├── config # 环境配置 │ ├── config.py │ └── env.yaml ├── data # 测试数据 │ └── order_data.yaml ├── testcases # 测试用例 │ ├── conftest.py │ ├── test_cart.py │ ├── test_order.py │ └── test_payment.py └── reports # 测试报告这个结构和很多公开项目接近,但真正决定框架好不好用的不是目录长啥样,而是几个关键环节的处理——登录态、数据驱动、数据库断言。这三个问题解决不好,用例一多就会崩。
3.2 登录态与Token处理的几个坑
商城系统几乎所有下单、订单查询接口都需要登录态。我们的系统登录后会返回一个token,有效期两小时。最开始的脚本是每个用例都先走一遍登录接口,慢还不说,频繁登录容易被服务端限流。后来我改成用户会话共享的方案:整个测试会话只登录一次,在conftest里把token存到session级别。
import pytest import requests @pytest.fixture(scope="session") def user_token(): resp = requests.post( "https://api.shop.example.com/user/login", json={"username": "test_buyer", "password": "encrypted_password"} ) assert resp.status_code == 200 return resp.json()["data"]["token"] @pytest.fixture(scope="session") def http_client(user_token): session = requests.Session() session.headers.update({"Authorization": f"Bearer {user_token}"}) return session这里有一个必须处理的点:token会过期。如果某次用例执行时间超过了token有效期,后续用例会大面积因为401失败。我在封装里加了一个自动处理机制:当接口返回401时,重新登录一次并更新token,然后重放当前请求。这个逻辑虽然只有十几行代码,但极大提升了长时间用例集运行的稳定性。
3.3 数据驱动:把用例数据和脚本逻辑解耦
商城系统的接口测试用例数量多,参数组合也多,如果一个个写函数,代码量会爆炸。我全部改成数据驱动,用例数据和脚本分离。以购物车加购为例,多组参数直接写在yaml里,用pytest的参数化机制读取。
CartTest_data: - case: "正常加购" params: sku_id: 100234 quantity: 1 except: code: 0 - case: "加购数量超过库存" params: sku_id: 100234 quantity: 99999 except: code: 21006 - case: "未登录加购" params: need_login: false sku_id: 100234 except: code: 10001import yaml import pytest from common.http_client import HttpClient with open("data/cart_data.yaml", encoding="utf-8") as f: cart_datas = yaml.safe_load(f)["CartTest_data"] @pytest.mark.parametrize("cart_case", cart_datas, ids=lambda x: x["case"]) def test_add_cart(http_client, cart_case): param = cart_case["params"] resp = http_client.api_post("/api/cart/add", json=param) assert resp["code"] == cart_case["except"]["code"]这样做的好处有两个:一是新增一条测试场景只需要在yaml里加一组数据,测试代码完全不用改,业务同事也能看懂用例覆盖范围;二是每次接口调整导致预期结果变化时,能快速定位是脚本问题还是接口问题。
3.4 数据库断言:接口返回值不等于真的正确
做商城系统接口测试一个很深切的教训是:接口返回200、code为0不代表业务真的成功。比如订单提交接口,即使接口提示“下单成功”,也可能出现订单表里没有记录、库存没扣、优惠券没锁定的情况。因此我所有核心用例都加了数据库断言。
我用pymysql封装了一个DbClient,专门在用例执行完成后去数据库查关键表的实际状态。以“提交订单”用例为例,接口断言之外还要查订单表状态、订单商品表内容、库存流水表、优惠券使用记录四个地方。只有数据库也符合预期,这条用例才算真正通过。
class DbClient: def __init__(self, env): self.conn = pymysql.connect( host=env["db_host"], port=env["db_port"], user=env["db_user"], password=env["db_password"], database=env["db_name"], charset="utf8mb4" ) def query_one(self, sql): with self.conn.cursor() as cursor: cursor.execute(sql) return cursor.fetchone() def close(self): self.conn.close()数据库断言有一个必须守住的原则:只做查询验证,绝不让自动化测试脚本直接去改生产环境数据。测试环境的订单数据可以清理,但线上数据一旦被自动化影响,性质就变了。我宁可多写几个前置造数接口,也不在用例里直接执行delete或者update去破坏数据。
4. UI自动化怎么落地:从Selenium到Playwright的选型记录
4.1 UI自动化的定位:控制数量,保证质量
商城系统的UI自动化,我一开始犯过冒进的错,一口气写了六七十条Selenium用例,结果每次迭代光维护选择器就花掉大半天。后来被迫做减法:UI自动化只覆盖三类场景——未登录用户能否正常浏览下单、登录用户全链路购买、关键页面核心文案与跳转是否正常。
数量控制在20条以内,但每一条都是高频使用的核心链路。这样做的原因是商城页面改版太频繁,UI层的稳定性天然不如接口层,把UI用例数量做大只会让维护成本吞掉收益。UI自动化的目标不该是“替代所有手工”,而是给接口自动化加上一道最后一公里的保障:用户真实操作到底能不能走通。
4.2 Selenium还是Playwright,我最终的判断逻辑
技术选型当时纠结过一阵。团队原本有Selenium的使用基础,但新项目的诉求是快、稳、好调试。我把两者放在一起对比后发现,Playwright在几个关键点上更占优:
- 元素定位失败率:Playwright的自动等待和actionability检查明显更省心,Selenium则对隐式等待和显式等待的组合要求更高。
- 调试体验:Playwright支持trace viewer,脚本失败后能看完整操作录屏和网络请求,定位问题效率高很多。
- 安装与浏览器管理:Playwright会自己下载匹配版本的浏览器,不太需要操心chromedriver版本不匹配的经典问题。
不过如果你所在团队已经积累了成熟的Selenium自动化资产,也没有必要推倒重来。Selenium结合PO模式同样可以做得很稳。真正决定成败的不是用哪个工具,而是有没有统一的页面对象封装和等待策略规范。
4.3 PO模式封装与等待策略:UI自动化稳定的关键
我在项目里用的是经典Page Object模式。为了控制篇幅不贴完整代码,但核心思路值得展开。每个页面一个类,例如首页、商品详情页、购物车页、结算页,页面上的元素定位和操作动作全部封装在类里,测试用例层只负责调用业务动作,完全不出现driver.find_element这种底层代码。
class CartPage: def __init__(self, page): self.page = page self. checkout_button = page.locator("#checkout_btn") self. item_count = page.locator(".cart-item-count") def goto_checkout(self): self.checkout_button.click() def get_item_count(self): return int(self.item_count.inner_text())等待策略上我有一条铁律:什么都不用sleep。无论Selenium还是Playwright,sleep都是脚本不稳定的头号来源。页面加载快慢、网络抖动都会让固定sleep失效,正确做法是显式等待目标元素处于可操作状态。Playwright的locator操作天然带自动等待,Selenium则建议统一封装WebDriverWait工具方法。
还有一个坑:商城页面很多弹窗和浮层内容是异步加载的,点击按钮前最好先确认目标元素没有被子元素遮挡。Playwright的click会自己检查元素可点,Selenium里就得注意element.send_keys(Keys.ENTER)配合显式等待来绕过偶发的遮挡问题。
5. 测试报告与结果分析:让结论真正推动质量改进
5.1 自动化测试报告不该只有通过率
项目标题既然是“测试报告”,这部分我必须多说几句。做了几年测试后发现,绝大多数自动化报告有一个通病:只写一共多少条用例、通过多少条、通过率百分之多少。这种报告对决策者来说几乎没有信息量。
我最终输出的报告包含四块内容:整体结论、失败原因分类、核心业务场景覆盖度、风险提示。失败原因分类非常重要,我把失败数据按环境问题、测试数据问题、脚本问题、真实缺陷四类归类统计。如果一串失败用例里环境问题占了一大半,那说明不是功能有问题,而是自动化运行环境不稳定,要先把基础设施弄稳,而不是追着开发改bug。
5.2 排查失败用例的三步流程和常见坑
我在项目里总结了一套排查失败用例的固定流程。第一步看日志,先确认请求发出去了没有、后端返回了什么;第二步看数据,订单状态、库存记录、支付单状态在数据库里是否和预设一致;第三步再看是不是真实业务缺陷,如果是就提单,如果只是环境或数据问题就修复后重跑。
实际运行中踩到最多的坑,我整理成了一张速查表:
| 现象 | 常见原因 | 处理方式 |
|---|---|---|
| 大量接口返回401 | token过期未自动刷新 | 检查自动重登机制是否生效 |
| 下单断言失败但库存被扣 | 用例执行时商品被其他用例共享 | 每个用例使用独立商品SKU |
| 支付回调用例执行后脏数据残留 | 回调通知串到自动化测试环境 | 隔离支付回调回调地址与测试环境 |
| 页面元素点击无响应 | 异步弹层尚未加载完成 | 优化等待策略,确认目标元素真正可见可点 |
| 优惠券用例预期不一致 | 优惠券被前序用例用过状态为已核销 | 测试数据准备阶段动态创建新券 |
其中“库存被占用导致断言失败”是商城自动化最常踩的坑。多个用例如果共用同一批SKU,前一个用例下单后锁了库存,后一个用例再下单就会库存不足。解决办法是数据准备时用库存充足的专用商品池,每条用例执行前动态验证一下库存量,不够就自动补库存,用完再清理。
5.3 让报告被团队信任:定时任务与通知机制
报告写得再好看,如果没人及时看也白搭。我通过Jenkins配置了每晚十点的定时任务,自动化用例跑完后自动生成Allure报告,再把汇总结果推送到项目群里。推送内容只保留最关键的几条:本次通过率、新增失败用例数、需要人工处理的失败用例编号。
这么做的好处是让研发每天早晨到岗第一件事就能看到测试结果,有失败直接认领处理。渐渐地团队会形成一种氛围——晚上自动化跑完,早上就像收体检报告一样过一遍,而不是每个版本发布前才紧急补测。让自动化变成日常习惯,这套体系才算真正融入研发流程。
6. 自动化体系的长期维护与传统测试如何融合
6.1 别指望自动化全替代人工,两者是协作关系
商城系统上线自动化之后,手工测试并没有消失,只是角色变了。自动化接管了重复的回归验证,人工测试的精力释放到探索性测试和复杂业务场景设计上。探索性测试发现的新问题,一旦确认是稳定的可复现缺陷,我就把它转写成自动化用例加入回归集,形成“人工发现问题、自动化守护回归”的闭环。
这种分工模式我用了几个迭代后效果不错,团队里比较资深的测试同学专注业务分析与异常场景设计,新同学负责执行探索测试并维护自动化脚本,每个人都在自己擅长的方向上产出。
6.2 防止自动化用例“僵尸化”的日常机制
自动化体系最大的死法不是搭不起来,而是没人维护。一旦用例开始频繁失败又没人处理,团队就会对报告失去信任,最终废弃。为了防止这种情况,我定了几条维护规矩:
每条用例都必须有明确的业务owner,谁写的用例谁负责维护。每次需求迭代时,涉及核心流程改动的开发任务必须同步评估自动化用例影响范围。每周花固定时间处理失败用例,不允许失败用例积压超过一周。
另外我会定期抽查用例的“含金量”,如果一个UI用例已经连续两个月没有发现过缺陷且每次都在纯耗时,我就会评估是否降低执行频率或干脆删除。自动化测试的价值在于风险覆盖,不在于用例数量,那些花费长但从不报风险的用例,本质上是在浪费CI资源。
6.3 关于AI自动化测试工具的落地思考
这一两年AI自动化测试的讨论非常多,我也做过一些尝试。拿测试用例生成来说,大模型确实能帮助快速生成接口层的基础脚本和页面元素定位建议,但直接产出可用的完整用例还做不到。真正让我觉得有实用价值的是AI做失败用例初分类——把接口返回的报错信息和日志丢给大模型,让它先归类一遍是环境问题还是业务问题,能省掉不少人工分析时间。
关于AI自动化测试平台的落地,我的观点是:能用现成轮子就用现成轮子,优先把AI应用在数据分析、失败归因这些“辅助决策”环节上,而不是一上来就幻想着AI自动写全部用例、自动修bug。商城业务规则复杂,人和AI结合,AI负责效率,人负责判断,这是短期内比较现实的路子。
这个项目跑到现在,我的一个核心体会是:商城系统自动化测试最大的价值不是替代谁的工作,而是把反复回归的时间省下来,让测试人员有机会去思考更深层的业务问题和质量风险。如果只给你留一条建议,那一定是第一版不要贪多求全,先覆盖订单主链路和支付回调这类最核心、最容易出大事的场景,把通过率稳到95%以上,再往外扩展。自动化是一笔持续投入的账,宁可每一条用例都可信,也不要一百条三天两头失灵的脚本撑场面。