别再写重复的认证、签名脚本了!封装成Skills,一行调用完成复杂接口测试

举报
霍格沃兹测试开发 发表于 2026/06/01 11:38:09 2026/06/01
【摘要】 刚入职那会儿,我接手了一套接口自动化代码。打开登录模块,里面赫然写着二十行RSA签名生成。再看订单模块,同样的二十行,连变量名都没改。再看支付模块,又一份。不是复制粘贴的问题。是当时写脚本的人,根本不知道怎么复用。三年过去了,情况好转了吗?没有。我去过七八个测试团队,每个团队的代码仓库里,都躺着至少三四份不同的认证、签名、加密实现。有的用HMAC-SHA256,有的用MD5加盐,有的用JWT...

刚入职那会儿,我接手了一套接口自动化代码。打开登录模块,里面赫然写着二十行RSA签名生成。再看订单模块,同样的二十行,连变量名都没改。再看支付模块,又一份。

不是复制粘贴的问题。是当时写脚本的人,根本不知道怎么复用。

三年过去了,情况好转了吗?

没有。我去过七八个测试团队,每个团队的代码仓库里,都躺着至少三四份不同的认证、签名、加密实现。有的用HMAC-SHA256,有的用MD5加盐,有的用JWT,有的用自定义的非对称签名。每个接口都要带上这些逻辑,每个脚本都要重新写一遍。

更麻烦的是,认证逻辑一变,比如密钥轮换、算法升级,所有脚本全部要改。改漏一个,那个脚本就永远红在那里。

上个月,我把项目里所有认证、签名相关逻辑抽成了Skills。现在任何接口测试,只需要一行声明:@use_skill("auth_rsa")。签名参数自动注入,请求自动发送,响应自动验签。接口测试脚本从一百五十行降到了二十行。

这篇文章不讲大道理。直接拆解怎么把认证、签名这类“横切关注点”封装成可复用的Skill。

一、每个接口测试脚本里,都有一段不敢动的签名代码

随便打开一个接口测试项目,大概率能看到这样的结构:

def test_create_order():
    # 二十行签名生成
    timestamp = str(int(time.time()))
    nonce = str(random.randint(100000, 999999))
    body = json.dumps({"product_id": 123, "quantity": 2})
    sign_string = f"{timestamp}{nonce}{body}"
    signature = hmac.new(secret_key, sign_string.encode(), hashlib.sha256).hexdigest()
    
    # 两行业务请求
    headers = {"X-Timestamp": timestamp, "X-Nonce": nonce, "X-Signature": signature}
    resp = requests.post("/api/order", data=body, headers=headers)
    
    # 二十行响应验签(有时还有)
    # ...

看明白问题了吗?

真正描述业务意图的代码只有两行:post("/api/order", data=body)。其他四十行全是认证、签名、验签的杂音。而且这段杂音在每一个测试函数里重复出现。

更难受的是,开发改了签名算法,从HMAC-SHA256换成SM3。你就要在三十个测试文件里,分别找到那段签名代码,一个一个改。改完还不一定全跑通。

很多人干脆不动。新接口直接复制旧接口的签名代码,改一改变量名就用。于是代码库里出现了三代签名算法并存的奇观。

本质问题是什么?

我们把本应属于“基础设施”的认证能力,硬编码进了业务脚本里。 就像每个业务函数里都写一遍数据库连接池,没人会这么干。但在接口测试里,大家默认就是这么干的。

观点句1:

认证和签名不是业务逻辑,是测试基础设施。基础设施应该被声明式调用,而不是命令式嵌入。

二、认证不是业务,它是基础设施

搞清楚一个概念:什么是业务逻辑,什么是基础设施。

业务逻辑是“下单时要扣减库存”“支付成功后要发送通知”。认证签名是“每个请求都要带上的安全凭证”。前者每个接口都不同,后者整个系统都相同。

基础设施的特点是:可以被统一管理、统一变更、统一升级。数据库连接池是这样,日志是这样,认证也应该是这样。

但在大多数接口测试框架里,认证却被当作业务逻辑处理。每个测试函数都自己负责生成签名、构造认证头、验签。原因有两个。

第一,历史惯性。最早写第一个测试脚本的人,图省事把签名代码直接写在函数里。后来的人看到这个模式,照搬。

第二,工具限制。很多测试框架(比如requests原生用法)没有提供“全局请求拦截器”的概念。你想统一处理认证,只能自己封装一个session类。不是做不到,是需要额外设计。

Skills-first模式提供了一个新的抽象:把认证能力封装成一个独立的Skill,然后通过声明的方式附加到任意接口测试上。

Skill在这里承担的角色是请求拦截器。它在请求发出前执行,可以修改请求头、请求体、甚至URL。同时它也可以在响应返回后执行,做验签、解密等后处理。

关键点:Skill不关心具体调用它的接口是什么。它只知道自己需要从当前上下文中拿到哪些数据(时间戳、随机数、请求体等),然后计算出签名,注入到headers里。

这种设计带来的效果是:接口测试脚本回归到只描述业务本身。认证的事情交给Skill。

观点句2:

一个好的认证Skill,应该让测试人员完全忘记认证的存在。

三、Skill作为请求拦截器:注入、签名、发送

下面拆解一个认证Skill的完整技术实现。

核心架构如下:

image.png

Skill的定义规范

一个认证Skill需要实现两个接口:

class AuthSkill:
    def pre_request(self, context):
        # context包含:method, url, headers, body, params
        # 返回修改后的headers和body
        timestamp = str(int(time.time()))
        nonce = self._gen_nonce()
        body_str = json.dumps(context.body) if context.body else""
        sign = self._sign(f"{timestamp}{nonce}{body_str}")
        context.headers["X-Timestamp"] = timestamp
        context.headers["X-Nonce"] = nonce
        context.headers["X-Signature"] = sign
        return context
    
    def post_response(self, context):
        # context包含:response对象
        # 可选:验签、解密响应体
        signature_from_server = context.response.headers.get("X-Signature")
        ifnot self._verify(context.response.text, signature_from_server):
            raise AuthFailedError("响应签名验证失败")
        return context

在测试脚本中的调用方式

测试脚本只需要声明依赖哪个Skill,以及传入必要的业务参数:

@use_skill("hmac_sha256_auth")
def test_create_order(product_id, quantity):
    resp = api.post("/order", json={"product_id": product_id, "quantity": quantity})
    assert resp.status_code == 200

@use_skill装饰器做的事情:

  1. 在函数执行前,实例化指定的AuthSkill
  2. 将原始请求拦截,传入Skill的pre_request
  3. 发送修改后的请求
  4. 将响应传入Skill的post_response
  5. 返回最终响应给测试函数

支持多Skill链式组合

有些系统需要多层认证。比如先做JWT鉴权,再对请求体做签名。这时候可以把多个Skill串起来:

@use_skill("jwt_auth")
@use_skill("payload_signature")
def test_pay():
    # ...

执行顺序:先最内层的Skill,然后向外层。每个Skill都可以修改请求和响应。

这种设计解决了两个实际问题:

  • 认证逻辑变化时,只改Skill实现,所有测试脚本零改动
  • 新接口接入认证,只需要加一行装饰器,不需要复制任何代码

观点句3:

把认证封装成Skill之后,签名算法的升级从“改三十个文件”变成了“改一个类”。

四、三个真实场景的代码对比

选三个典型的认证场景,看传统写法 vs Skill写法的差异。

场景一:HMAC-SHA256签名(最常见)

传统写法(每个接口):

timestamp = str(int(time.time()))
nonce = str(random.randint(100000, 999999))
body = json.dumps({"amount": 100})
sign_str = f"{timestamp}{nonce}{body}"
sign = hmac.new(secret, sign_str.encode(), hashlib.sha256).hexdigest()
headers = {"X-Timestamp": timestamp, "X-Nonce": nonce, "X-Signature": sign}
resp = requests.post("/api/pay", data=body, headers=headers)

Skill写法:

@use_skill("hmac_sha256")
def test_pay():
    resp = api.post("/pay", json={"amount": 100})
    assert resp.status_code == 200

代码行数:传统25行,Skill方式3行(不含Skill定义)。

场景二:OAuth 2.0 Client Credentials

传统写法(先拿token,再带token请求):

def test_get_user():
    # 获取token
    auth_resp = requests.post("/oauth/token", data={
        "grant_type": "client_credentials",
        "client_id": "xxx",
        "client_secret": "yyy"
    })
    token = auth_resp.json()["access_token"]
    # 业务请求
    headers = {"Authorization": f"Bearer {token}"}
    resp = requests.get("/api/user", headers=headers)
    # token过期还要处理刷新逻辑...

Skill写法:

@use_skill("oauth2_client")
def test_get_user():
    resp = api.get("/user")
    assert resp.status_code == 200

Skill内部处理了token的获取、缓存、自动刷新、过期重试。

场景三:国密SM2/SM3签名(金融常见)

传统写法需要引入复杂的加密库,十几行甚至几十行代码。Skill封装后,业务脚本完全无感知。

一个可传播的对比数据

我们统计了单个项目中认证相关代码的重复率。重构前,15个接口测试文件,认证代码总行数超过600行。重构后,认证Skill一个文件120行,每个测试文件减少40行重复代码。总量从600行降到120 + 15*10 = 270行。净减少55%。

更重要的是,后来开发把签名算法从HMAC-SHA256升级到SM3,改动只发生在Skill内部,测试团队零改动。

五、封装认证Skill的三个层级

不是所有认证逻辑都需要做成通用Skill。我见过有人过度设计,把简单的Basic Auth也封装成Skill,反而增加了理解成本。

根据复杂度和复用频率,可以分三个层级落地。

第一层:函数级复用(最低成本)

如果认证逻辑很简单(比如固定的API Key),而且只在两三个测试文件中使用,没必要做Skill。写一个公共函数即可。

def add_auth_headers(headers):
    headers["X-API-Key"] = "fixed_key"
    return headers

这不算Skill,但已经比到处复制强。

第二层:请求级拦截器(大多数场景适用)

认证逻辑涉及动态参数(timestamp、nonce、签名计算),并且要被五个以上接口使用。这时候值得封装成真正的Skill。

核心是设计好Skill的输入输出。一个好的认证Skill应该做到:

  • 不依赖全局变量(密钥通过配置注入)
  • 不硬编码算法(支持策略模式切换不同签名方式)
  • 能够独立测试

第三层:多Skill组合(企业级复杂场景)

有些系统同时使用多种认证机制。比如内部服务之间用mTLS,对外部请求用JWT,对敏感操作再加一层签名。

这时候需要支持Skill链。每个Skill只做一件事,执行引擎按顺序调用。调试时也可以单独关闭某个Skill来定位问题。

避坑指南

踩过两个坑,提醒一下。

第一个坑:Skill里不要做重操作。比如每次请求都去读密钥文件或调远程服务获取token。应该做缓存,token过期再刷新。

第二个坑:不要为了统一而统一。有的接口不需要认证(比如健康检查),有的接口用不同的认证方式。Skill框架要支持“跳过认证”和“覆盖认证”。我们实现了一个@use_skill(None)用来显式关闭继承的认证。

六、你的代码库里,还有多少“不敢删”的重复代码?

写这篇文章的时候,我随手打开了一个旧项目的测试目录。grep -r "hmac" . | wc -l,结果是47。47处HMAC签名代码,分布在14个文件里。

我把其中一个文件里的签名代码删掉,换成一行@use_skill,跑测试。全绿。

然后我批量替换了剩下的13个文件。总共花了四十分钟。这四十分钟省下的,是未来每一次签名算法变更时的一整天。

现在轮到你了。

打开你正在维护的接口测试项目,搜索signsignaturehmacjwttoken这些关键词。看看有多少个文件在重复做同一件事。

然后问自己一个问题:

如果明天后端通知你,认证算法要换,从A换成B。你需要改动多少个文件?

如果你的答案不是“1个”,那么你的测试框架里,已经有了技术债务。

至于怎么把这1个写得足够通用、足够健壮、足够让团队里其他人直接用——那是另一个话题了。评论区聊聊,你遇到的最离谱的重复代码是什么样的?

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。