爆肝3周,开源一套通用测试Skills框架:支持Web/App/接口统一技能调用

举报
霍格沃兹测试开发学社 发表于 2026/06/08 14:54:03 2026/06/08
【摘要】 目录一、测试团队越做越累,不是人不够,是技能太散二、本质不是缺框架,是缺“统一调用层”三、核心机制拆解:Skill抽象 + 注册中心 + 动态调度四、典型案例对比:同一个场景,三种终端,一套写法五、工程落地启示:你的测试资产不该绑定在某种工具上六、问你的团队一个问题一、测试团队越做越累,不是人不够,是技能太散上个月,我帮一个中型电商团队做技术评审。他们有三个测试小组:Web、App、接口。W...
目录

一、测试团队越做越累,不是人不够,是技能太散
二、本质不是缺框架,是缺“统一调用层”
三、核心机制拆解:Skill抽象 + 注册中心 + 动态调度
四、典型案例对比:同一个场景,三种终端,一套写法
五、工程落地启示:你的测试资产不该绑定在某种工具上
六、问你的团队一个问题


一、测试团队越做越累,不是人不够,是技能太散

上个月,我帮一个中型电商团队做技术评审。
他们有三个测试小组:Web、App、接口。

Web组用Playwright。
App组用Appium。
接口组用Requests + Pytest。

三个组,三套代码仓库,三种定位器写法,三种等待策略。

新人进来,要先学三套东西。
一个跨端场景(比如从Web下单,App确认收货),要三个组各写一遍,再用消息队列串起来。

他们问我:是不是该裁掉一组人,或者统一用某个商业平台?

我说:问题不在人,在你们的技能没有统一抽象。

每一端都在做类似的事:
点击、输入、获取文本、等待条件、发送请求、断言响应。

但每个框架都用自己的方式表达这些“技能”。
Web的“点击”是page.click(locator)
App的“点击”是element.click()
接口的“请求”是requests.post(url, data)

本质上,它们都是“执行一个动作并验证结果”。
但你们的代码里,每一层都在重复实现调度、重试、日志、断言。

这不是技术债,这是架构债。

我用了三周时间,把过去几年在多个项目里积累的经验抽出来,做了一个通用测试Skills框架。

核心目标很简单:
一套技能描述,同时驱动Web、App、接口。
统一调用方式,统一技能注册,统一结果断言。

代码已经开源。下面讲清楚它怎么工作。


二、本质不是缺框架,是缺“统一调用层”

很多人一听到“统一框架”,第一反应是再做一套超级框架,把所有底层都包进去。

那是错误的思路。

正确的思路是:不要在底层统一,要在“技能调用层”统一。

什么是技能?
技能是一个可命名的、有输入输出、有执行逻辑的最小测试单元。

比如:

  • click(selector) 是一个技能
  • input_text(selector, text) 是一个技能
  • http_get(url) 是一个技能
  • wait_for_element(selector, timeout) 是一个技能
  • assert_text_contains(text) 是一个技能

Web端需要这些技能,App端也需要,接口端需要的只是其中一部分。

关键在于:
技能的调用方不关心技能背后是Playwright、Appium还是Requests。
它只关心技能的名字和参数。

这就好比你在写业务代码时调用一个函数,你不管这个函数是用Go写的还是Python写的。

所以我们需要的不是统一的执行引擎,而是统一的技能注册表 + 动态调度器。

我的框架干的就是这件事。


三、核心机制拆解:Skill抽象 + 注册中心 + 动态调度

先看架构图。


拆解三个核心机制。

机制一:Skill定义规范 - 让每个技能自描述

一个Skill的最小定义:

@register_skill("click")
def skill_click(selector: str, timeout: int = 5, **context):
    """点击指定元素,支持Web/App统一selector"""
    driver = context["driver"]  # 由调度器注入
    # driver可能是Playwright的Page,也可能是Appium的WebElement
    driver.click(selector, timeout=timeout)

但这样还不够。每个底层驱动的API不同。
所以真正的Skill实现是一个适配器:

class ClickSkill(BaseSkill):
    name = "click"
    parameters = {"selector": str, "timeout": int}
    
    def execute(self, params, context):
        driver = context["driver"]
        if driver.__class__.__name__.startswith("Playwright"):
            driver.locator(params["selector"]).click(timeout=params["timeout"])
        elif driver.__class__.__name__.startswith("Appium"):
            driver.find_element(AppiumBy.XPATH, params["selector"]).click()
        # 接口层不支持click,调用会报错

关键点: 技能内部知道当前driver是什么类型,自己做适配。
调用方完全不用管。

机制二:注册中心 - 技能的市场

所有技能启动时注册到中心。
注册表维护一个字典:skill_name -> SkillClass

调度器收到调用请求后,去注册表找技能,实例化,调用execute

好处:
新增技能不需要修改调度器代码。
团队可以共享技能库,比如login_with_retrywait_for_toast

机制三:动态调度 - 一套DSL跑通所有端

调度器接受两种输入:

  1. YAML/JSON序列:适合关键字驱动
  2. Python链式调用:适合代码风格

一个YAML用例示例:

name: 跨端下单流程
skills:
-name:navigate
    params:{url:"https://xxx.com"}
-name:click
    params:{selector:"#add-to-cart"}
-name:wait_for_element
    params:{selector:".cart-badge",timeout:5}
-name:http_post
    params:{url:"/api/checkout",data:{"item_id":123}}
-name:assert_status_code
    params:{expected:200}

这个用例可以在Web环境跑(navigate, click),也可以在纯接口环境跑(http_post, assert_status_code)。
调度器会根据当前注册的技能集合,跳过不可用的技能(如click在接口环境自动跳过并报警)。

核心设计哲学:
技能是原子能力,用例是技能的有序组合。
底层驱动可以换,技能可以增删,但用例结构不变。


四、典型案例对比:同一个场景,三种终端,一套写法

拿“登录并校验”这个场景举例。

传统方式:三套代码

Web端:

page.goto("/login")
page.fill("#username""test")
page.fill("#password""pass")
page.click("button")
page.wait_for_selector(".welcome")
assert page.text_content(".welcome") == "欢迎"

App端(类似,但API不同):

driver.find_element(By.ID, "username").send_keys("test")
driver.find_element(By.ID, "password").send_keys("pass")
driver.find_element(By.ID, "login_btn").click()
wait = WebDriverWait(driver, 5)
wait.until(EC.visibility_of_element_located((By.CLASS_NAME, "welcome")))
assert driver.find_element(By.CLASS_NAME, "welcome").text == "欢迎"

接口端:

resp = requests.post("/login", json={"user""test""pwd""pass"})
assert resp.status_code == 200
assert "欢迎" in resp.text

Skills框架方式:一套用例

skills:
  -name:navigate
    params:{url:"/login"}
-name:input_text
    params:{selector:"#username",text:"test"}
-name:input_text
    params:{selector:"#password",text:"pass"}
-name:click
    params:{selector:"button"}
-name:wait_for_element
    params:{selector:".welcome",timeout:5}
-name:assert_text
    params:{selector:".welcome",expected:"欢迎"}

把这个YAML丢给调度器,设置driver_type=web,跑Web。
设置driver_type=app,跑App(只要selector能被Appium解析)。
设置driver_type=api,框架会自动将input_textclick转换为HTTP请求(如果实现了对应映射)。

实际上,接口环境不需要填表单,所以我们会为接口场景单独写一个更简洁的技能序列。但关键在于:
测试人员不需要记住三套API,只需要记住技能名字和参数。

可以被截图传播的观点句:

测试框架的复杂度,应该由框架本身承担,而不是让每个用例编写者重复学习。

技能统一,才是真正的资产复用。否则你只是在不同的端里重复造轮子。


五、工程落地启示:你的测试资产不该绑定在某种工具上

这个框架开源后,我已经在三个团队落地。
总结三条最直接的经验。

启示一:把现有测试脚本拆成“技能库”和“用例层”

不要一次性重写所有用例。
先从最常用的10个操作开始,注册成技能。
然后让用例通过技能调用来重构。
三个月后,你的用例文件会减少70%的重复代码。

启示二:技能可以跨项目共享

我们在框架里内置了一个远程技能仓库。
团队A写的captcha_solver技能,团队B可以直接拉下来用。
不需要复制代码,不需要知道内部实现。

这对中大型团队的价值极大:
你不再需要每个项目都配一个“自动化专家”。

启示三:新人培训周期从两周压缩到两天

新人只需要学会技能列表和YAML写法。
不需要先学Playwright API,再学Appium,再学Requests。
他们可以在第一天就写出能跑的用例,第二天理解技能背后的原理。

对在校生来说:
你现在不需要纠结“学Web自动化还是App自动化”。
你应该学的是“如何抽象测试技能”。这个能力在任何端都通用。

对初级工程师来说:
试着把你平时写的Playwright脚本,重构为技能+用例的形式。
你会发现自己开始从“写代码的人”变成“设计框架的人”。

对中级工程师来说:
这个框架展示了如何用“注册中心+适配器”模式解耦测试工具。
你可以把它推广到你的团队,或者自己实现一个更轻量的版本。


六、问你的团队一个问题

去你团队的自动化代码仓库里,随便找一个跨端的场景(比如用户从注册到下单)。

统计一下:
为了支持Web、App、接口三种环境,你的代码里重复实现了多少遍“等待元素出现”“输入文本”“点击按钮”?

然后问自己:
如果明天要换掉其中一个底层框架(比如从Playwright换成Cypress),你要改多少处代码?

如果答案是“超过10处”,
那么这个框架就值得你花一天时间研究。


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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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