接口自动化测试从零搭:31 条用例跑 4 秒,抓出 6 个权限缺陷

我是写业务代码的。做这套东西之前,我没写过测试用例,一条都没有。

有一段时间权限相关的 bug 出得特别密。今天某个角色能调不该调的接口,明天跨租户能看到别人家的数据。每次都是线上有人反馈才知道,修完也不敢说修干净了,因为换个角色、换个接口,同样的坑还会再来一遍。

真正让我下决心的是,我发现我没有任何手段回答这样一个问题:改了这行代码,原来能用的地方还活着吗?

这篇文章是那之后两个月的事。103 条手写用例,最后跑成 31 条自动用例,一次 4 秒,挖出 6 个缺陷,现在每周一自动跑一遍。

一、先写的是一份文档

一开始我也去看了 pytest,看了几篇教程,写了个 test_login() 跑通了,然后就卡住了------不知道下一步该写什么。

后来我想明白了:卡住不是因为不会写代码,是因为我没想清楚要验证什么。

所以我调了个方向,先花一天半手写了一份用例文档,103 条,每条 9 个字段:

复制代码
ID / 场景 / 角色 / 前置条件 / 步骤 / 预期结果 / 实际结果 / 状态 / 备注

写完这份文档,后面写自动化几乎就是「翻译」了。现在回头看,这一步省掉的是后面反复改结构的时间------直接上代码的话,很容易一路写成「把接口调一遍看看通不通」,那是冒烟,不是测试。

文档按 14 个模块分:

模块 条数 模块 条数
租户隔离 11 牛只 10
诊断 10 权限 9
投喂 9 库存 8
认证 6 菜单 6
成本 6 设备 6
审批 6 数据隔离 6
集团 4 端到端 6

写的时候我特意加了三样东西:术语的大白话对照表、账号和数据准备清单、缺陷断言的速查表。

当时是因为自己对测试这一套完全没概念,不得不边查边记。结果这份「小白笔记」后来帮了别人------产品拿去对着看,提了不少我漏掉的场景。

二、从一份文档到 4 个文件

落地是四个文件:

文件 职责
config.js 环境配置:base URL、数据库、租户编码、各角色账号
lib.js 8 个基础能力
tcs/index.js 用例本身
run.js 执行器:跑用例、出报告

lib.js 里就这点东西,每加一个都是被坑出来的:

login · loginAndSelectTenant · rawFetch · dbQuery · assert · runCase · invalidateToken · CFG

用例本身写起来很短:

js 复制代码
TCS.push({
  id: 'TC-PERM-003',
  module: 'PERM',
  title: '场长直调新增牛只接口被拒(无 cattle:add)',
  expected: '返回 403,不产生数据',
  async run() {
    const t = await login('director');
    const r = await rawFetch('/cattle', { method: 'POST', token: t, body: {...} });
    assert(r.status === 403, '越权未被拦截: ' + r.text.slice(0, 120));
    return 'code=' + r.status;
  }
});

第一批 24 条,跑完 4 秒。

4 秒这个数字当时挺意外的。后来想通了:接口测试就是发 HTTP 请求、断言响应,没有浏览器、没有渲染、没有等待。跑得这么便宜,才有可能天天跑------这是它后来能挂到定时任务上的前提。

三、几条规矩,有的是踩了坑才明白的

只做只读的

tcs/index.js 第一行我写了个注释:

范围:只读安全子集 + 403 权限验证(不做写操作,避免污染数据)

这条不是设计出来的,是吃了亏才加的。

最早我想一次做全,增删改都测。跑了几轮之后,环境里的垃圾数据越堆越多,列表接口的断言开始莫名其妙地挂,账号也被锁过。那套东西就没人敢跑了。

改成只读之后,跑一百次环境还是干净的。写操作用例当然也能写,但得配套自造数据、自己断言、自己清场,那是后面的事,不是第二天就该干的事。

缺账号就跳过,别报失败

不是每个环境都有全套测试账号。缺的时候我让用例直接抛 SKIP:

js 复制代码
if (!CFG.accounts.multiTenant) throw new Error('SKIP: 当前环境无多租户账号');

这个事的道理很简单:第一次跑出来 10 个红的,全是环境问题;第二次你就不认真看报告了;等到真有 bug 那天,你也不会看。

编号对着需求文档写

TC-AUTH-001、TC-PERM-003,编号跟需求文档对得上。

刚开始是强迫症,后来发现有用:某条用例挂了,我能顺着编号翻回去看当初的需求是怎么写的、我为什么这么断言。不然过两个月,自己写的用例自己看不懂。

每条用例都重新登录

这条纯粹是被逼的,原因在下面坑二。

四、折腾最久的是环境

写用例的时间大概三天,调环境的时间比这还长。下面这五个,前四个都跟业务逻辑没关系。

有两个账号,永远登不进去

测试账号里有两个,用户名 21 个字符,而系统的 USERNAME_MAX_LENGTH = 20。

它们永远登录失败,报「用户名不在指定范围」。

这地方我绕了很久。因为它看起来像权限问题,像密码问题,像租户没绑,唯独不像「名字太长」------谁会想到登录不了是因为用户名超长?而且报错信息也没直接说。

后来是拿一个能登的账号和它逐字对比,才发现长度差了 1。

测试数据也是数据,系统的约束一样管着你。

选完租户的 token,不能再选租户

系统是多租户的,登录后要选租户,选完换一个新 token。而那个新 token 是完整的租户态,再调 select-tenant 会出问题。

第一版框架没管这个,结果连跑两条需要选租户的用例就挂。表现很迷惑:单跑都过,连着跑就失败。

解法就是笨办法,每条用例开始前重新登录一次,不复用 token。代价是 4 秒变 5 秒。我认了,因为排查「为什么连着跑会挂」花的时间,比这 1 秒贵太多。

登出不清缓存,后面全 401

logout 之后如果不清服务端的 token 缓存,后面所有用例全部 401。

而且会连锁:框架一旦复用 session,第一条挂了,后面全跟着挂,看起来像「所有接口都坏了」。

第一次遇到时我以为权限配置被谁动了,查了半天配置。

后来加了 invalidateToken(),登出后显式清缓存。

反复用错密码,把真账号锁了

这个坑我觉得最阴。

系统有登录失败锁定:连续输错密码锁 10 分钟。计数在 Redis 里(pwd_err_cnt:<username>),重设密码也不清这个计数。

我写「错误密码应该被拒」这条用例时,用的是真实的测试账号。跑了几十次之后,那个账号被锁了,后面一串用例全登不上。

改法很朴素:这条用例改用不存在的用户名。反正验证的是「密码错被拒」,用个假账号一样能验,还不会在真账号上留痕迹。

由此得出一条通用经验:验证失败路径的用例,输入尽量用不会留下痕迹的。

用例的预期,是照两周前的数据写的

这条最尴尬。

我写权限用例时,角色和菜单的对应关系是查一份数据库快照来的。后来发现那份快照是两周前的,绑定关系早改了。

于是好几条用例的预期是错的。比如我断言「场长看不到在线诊断」,实际上当时他能看到------因为那本身就是个 bug,而快照那会儿这个 bug 还没出现。

用例报红,我以为是系统坏了,查了半天才发现是我自己的预期过时了。

后来改成连库实时查,不再用静态快照。

这件事改了我一个想法:用例的预期值也是要维护的东西。系统变了、需求变了,预期不跟着改,测试就会从帮你变成骗你。

五、它挖出来什么

本地和测试服务器都跑过,确认了 6 个:

编号 缺陷 性质
P0 某角色越权读诊断列表 越权,测试服务器上复现,能读到别人的数据
BUG-1 设备模块接口契约不一致 前后端路径/方法对不上
BUG-2 耳标批量查询契约不一致 同上
BUG-3 场长问诊列表不可用 有菜单、接口没权限
BUG-4 财务成本列表不可用 权限收太死,正常功能也挂了
--- 耳标操作权限缺失 按钮有、接口没权限

BUG-3 和 BUG-4 挺有意思,是一对反的:

  • BUG-3:菜单给了,接口权限没给,点进去报错
  • BUG-4:接口权限收得太紧,正经角色也用不了

所以权限这块只测「越权被拒」是不够的,还得测「该能用的能不能用」。收太松和收太紧,都是线上事故。

六个缺陷修完之后,每个都补了一条回归用例:

vbnet 复制代码
TC-MOD-004  契约缺陷修复回归
TC-MOD-005  场长问诊列表可用(BUG-3 修复回归)
TC-MOD-006  财务成本列表可用 + 越权解除(BUG-4 修复回归)

这是我从这件事里学到的最有用的一条:bug 修完不算完,变成用例才算完。不然同样的问题过半年还会再来一次,而你已经忘了当初怎么修的。

最后全套 31 条,双环境全绿,29 PASS + 2 SKIP。

六、让它自己跑起来

写完没人跑,等于没写。这话我在别的项目上吃过亏。

所以最后一件事是挂定时任务:

每周一 12:00 自动执行 AT_ENV=remote node run.js,只读巡检 31 条用例,生成报告,对着 6 个已修复缺陷看有没有回归。有 FAIL 才提示人工介入,不自动改代码、不自动改库。

两个地方我犹豫过:

一是只读。自动跑的东西如果会写数据,半夜跑挂了没人知道,第二天环境全是脏的。所以这条线不敢越。

二是要不要自动修复。听着很美,但一个能改东西的测试,本身就成了新的风险源。我最后只让它报告。

七、如果也是零基础

三个事,按重要性排:

先写文档,别急着上框架。 我一开始也去装 pytest 了,写了一个用例就不知道该写什么。文档逼你想清楚要验什么,代码只是执行。

第一版只做只读和越权验证。 贪多会把环境搞脏,搞脏之后就没人跑了,这套东西就死了。

修完的 bug 补一条回归用例。 这一条决定了你做的是一次性劳动还是资产。

至于语言,我用 Node 没什么讲究。项目前端就是 JS,_at 目录里本来就都是 node 脚本,顺手。后来还写了个小工具,扫前端 api/*.js 的 url 和 method,跟后端所有 Controller 的注解比对,找契约不一致------用 node 读前端源码确实方便些。

换成 pytest 或 Java 也一样,那三条跟语言没关系。


(本文用「某养殖 SaaS 平台」代指实际项目,接口路径、账号、IP 均已替换,不含任何真实客户信息。)

相关推荐
掘金小豆1 小时前
Spring AI 实战:从一次可运行的模型调用到 RAG 体系
后端·程序员·ai编程
前端小万1 小时前
探索副业五个月,最终以放弃告终
前端·后端·面试
用户87359865635871 小时前
Bias-Variance Tradeoff
后端
今天早睡1 小时前
TypedDict 还是 Pydantic?
后端
木易 士心1 小时前
Java 后端驱动远程手机自动化:ADB + Python + 内网穿透实战全解
java·后端·智能手机·自动化
薛定谔的悦1 小时前
从论文公式到 BMS 固件:FreeRTOS 与 EKF SOC 的工程结合
后端
用户1494484813201 小时前
从入口拦截到最终对账:我如何设计一套高并发秒杀系统
后端·架构
Jeromefromhk1 小时前
把服务器当代码管
后端
闽林致远1 小时前
C++ 插件化后台服务架构实践
后端