-
去年双十一大促封网前夜,一位在拼多多做测试的朋友发了条朋友圈:“终于不用再通宵等回归结果了。”底下炸出一堆同行。因为做过大促的人都知道,全量回归跑三天是常态。业务那边催命一样问“能不能上了”,你只能说“还在跑用例”。后来我找他深聊了一次,信息量很大。他们一条核心业务线,从3万条回归用例压到几千条,再通过智能调度,把整个回归周期从3天压缩到了2小时以内。不是靠堆机器,是靠换思路。目录大促前的回归噩梦,为什么突然消失了回归的瓶颈不是执行速度,是决策速度拆开这个AI大脑,里面就三件事一个真实场景:订单状态机改动,AI怎么挑用例想落地,先别急着上模型测试工程师的新分工:写用例,还是写规则大促前的回归噩梦,为什么突然消失了过去拼多多的大促回归流程,和大多数厂一样:全量拉一遍自动化用例集,夜间跑,白天修脚本,晚上再跑。三万个用例,分布式执行最快也要几十个小时,中间还要处理环境抖动、数据冲突、脚本失效。真正的痛苦不是时间长。是你跑完一轮,发现失败的用例里80%是环境问题,剩下20%才可能是真缺陷。排查又耗掉半天,大促窗口已经快关了。现在的做法是:代码一提交,AI自动分析变更影响面,从全量用例库里精准圈定一个最小必要集,按风险排序,然后在弹性容器集群里并发执行。高风险用例先跑,低风险的并行跑,结果实时推送。全量回归是测试团队的体力遮羞布,AI把它扯下来了。回归的瓶颈不是执行速度,是决策速度以前大家总想着怎么把用例跑得更快。搞分布式、搞并发、搞执行机扩容。但很少有人问一个问题:这些用例,真的都需要跑吗?拼多多那个团队做过统计,历次大促前回归发现的缺陷,91%集中在不到15%的用例覆盖范围内。剩下85%的用例,连续十几次回归零缺陷,纯属“陪跑”。回归测试的核心瓶颈,从来不是执行速度,而是**“该跑哪些”的决策速度**。人工决策的问题很明显:靠经验拍脑袋,要么怕漏测不敢减,要么减了不该减的。一个改动到底影响了哪些模块、哪些接口、哪些历史风险点,靠人脑已经算不过来了。他们做的事,本质就是把“变更影响分析”这个决策过程,从人脑移交给了模型。拆开这个AI大脑,里面就三件事这个AI系统不神秘,拆开来看就三个核心引擎。第一,变更影响分析引擎。每次代码提交,系统通过AST解析和运行时调用链数据,自动生成一张“变更影响拓扑图”。改了一个下单接口的入参校验逻辑,拓扑图会告诉你:这个接口被哪些服务调用,这些服务又关联哪些前端页面和后台任务,最终波及哪些业务流程。第二,用例-风险关联模型。这一步是把历史数据变成知识。系统会把过去三年所有线上缺陷、回归发现的Bug,和当时的代码变更、用例执行结果做关联训练。学出来的模型能回答一个问题:上一次改这个函数的时候,哪些用例挂了?挂了的是什么类型的缺陷?这次类似的改动,同样类型的用例是不是应该优先跑?第三,智能分群与调度。圈定出来的用例集,不会无脑全跑。系统按风险等级分三群:高风险的串行先跑,确保核心链路优先验证;中低风险的并行跑,用弹性容器动态扩容。结果一出来,自动聚类失败原因,把环境问题、脚本问题和真实缺陷分开标记。三个引擎串起来,是一条清晰的流水线:graph TD A[代码提交] --> B[AST解析+调用链分析] B --> C[变更影响拓扑图] C --> D[用例-风险关联模型] D --> E[用例推荐+风险分级] E --> F{风险等级} F -->|高风险| G[串行优先执行] F -->|中低风险| H[并行批量执行] G --> I[结果聚类分析] H --> I I --> J[缺陷/环境/脚本分类通知] 挑不准用例,再快的执行都是浪费机器。一个真实场景:订单状态机改动,AI怎么挑用例说个具体的场景。有次他们改了订单状态机,新增了一个“部分发货”的中间状态。人工评估影响面,通常会想到:正向的发货流程、确认收货流程、超时自动取消的定时任务。很容易漏掉的是退款逆向流程。部分发货状态下发起退款,金额怎么计算?已发货部分和未发货部分如何分摊?如果退款成功,状态机能不能正确扭转回“已取消”?人工漏掉这个场景不奇怪,因为正向开发和测试的思维惯性就是盯着主流程。但他们的AI模型在分析这次变更时,从历史缺陷库里匹配到一条记录:两年前一次状态机枚举值调整,曾导致退款金额计算异常,线上出了一次资损事故。模型自动把那次事故关联的用例簇标记为高风险,推荐优先执行。结果真的发现了一个类似问题——部分发货退款时,金额分摊的精度误差导致总退款多了1分钱。这种事靠人很难想起来,但数据记得。想落地,先别急着上模型聊完我觉得这东西确实好,但中小团队怎么搞?朋友给的建议很实在,三步走。第一步,先别想着搞AI。先把两件基础的事做了:代码提交和用例建立关联标签,每次提测时自动推荐一个用例集。这个推荐算法可以简单到“基于模块名映射+上一次回归结果”,花一两周就能跑通。第二步,积累数据。每一次回归的结果、每一次线上缺陷,都结构化记录下来,尤其是“哪个变更导致了哪个用例失败”这个对应关系。这比什么模型都值钱。没有这个数据积累,上再好的AI也是空中楼阁。第三步,等数据量够了,再引入轻量级模型做关联推荐。不需要自研,用开源方案结合embedding检索就能出效果。他们也不是一开始就做这么重的。前两个版本就是靠规则+人工标签撑起来的,模型是后来喂了足够多的数据才真正起作用。工程上的事,先解决有无,再解决好坏。测试工程师的新分工:写用例,还是写规则这套系统跑起来之后,他们团队里测试工程师的工作内容变了不少。以前大量的时间花在“挑用例、排计划、盯执行、查脚本”上。现在这些事系统全干了。那测试工程师做什么?一部分人转去做测试策略设计——怎么给AI制定风险分级规则,怎么设计用例标签体系,怎么验证推荐模型的准确率。另一部分人把精力投到探索性测试和深度缺陷挖掘上,那些AI还没学会的事。这件事最值得思考的地方是:AI没取代测试,但会写AI规则的测试工程师,正在把不会的那批甩开。你是在每天被回归进度追着跑,还是在设计一套能让回归自动完成的规则体系?这是两条完全不同的职业路径。你们团队现在一次全量回归跑多久?跑完的结果里,有多少用例已经连续十次没有发现过任何问题了?关于我们霍格沃兹测试开发学社,隶属于 测吧(北京)科技有限公司,是一个面向软件测试爱好者的技术交流社区。学社围绕现代软件测试工程体系展开,内容涵盖软件测试入门、自动化测试、性能测试、接口测试、测试开发、全栈测试,以及人工智能测试与 AI 在测试工程中的应用实践。我们关注测试工程能力的系统化建设,包括 Python 自动化测试、Java 自动化测试、Web 与 App 自动化、持续集成与质量体系建设,同时探索 AI 驱动的测试设计、用例生成、自动化执行与质量分析方法,沉淀可复用、可落地的测试开发工程经验。在技术社区与工程实践之外,学社还参与测试工程人才培养体系建设,面向高校提供测试实训平台与实践支持,组织开展 “火焰杯” 软件测试相关技术赛事,并探索以能力为导向的人才培养模式,包括高校学员先学习、就业后付款的实践路径。同时,学社结合真实行业需求,为在职测试工程师与高潜学员提供名企大厂 1v1 私教服务,用于个性化能力提升与工程实践指导。
-
从5000条用例到500条,回归时间从6小时降到40分钟,我们到底怎么做到的?大家好,我是某互联网公司质量效能团队的测试架构师,负责测试资产治理和效能优化。今天想分享一个让测试经理追着我跑的项目——用AI给测试用例库“瘦身” 。先上结果:5000条回归用例,AI识别并建议删除/合并了4500条,去重率90% 。全量回归执行时间从6个多小时压缩到40分钟,线上漏测率反而下降了18% 。测试经理看到数据后第一句话是:“源码给我看看。”一、用例库是怎么“胖”起来的?先说说我们的用例库是怎么从“精干”变成“臃肿”的。我们的回归用例库跑了五年多,积累到5000条。每次全量回归,要跑六个多小时。没有人敢删用例。每个人都怕删掉的那一条,恰好是某次事故复盘后加进去的“保命用例”。结果就是只增不减——新功能上线加用例,没有人会同步审查旧用例是否还有存在的必要。直到一次容量评估,我们才认真算了一笔账:跑一次全量回归的机器成本和人力成本,已经高到影响了发布节奏。但“找冗余”这件事,人工做几乎不可行。5000条用例,两两比较判断是否覆盖同一逻辑,组合数是天文数字。测试团队哪怕全员上阵,一个月也看不完。这正是适合交给AI处理的场景:规则可以显式化,工作量巨大但单步判断不复杂。二、先定义清楚:什么样的用例算“冗余”?动手之前,必须先把“冗余”定义清楚——这是整个项目最容易出错的一步,定义错了,AI再精准也是错的方向。我们最终确定了三类冗余,而不是笼统的“重复”:第一类:完全重复型两条用例的输入、操作步骤、预期结果完全一致,只是用例标题或编号不同。这种最容易识别。成因也很简单——不同人不知道已有用例,重复创建。我们这次发现的完全重复用例有380条,占总量的7.6%,主要集中在团队人员流动较多的几个模块。第二类:等价覆盖型(占比最高、最难识别)两条用例验证的是同一个等价类。比如“输入合法手机号13800138000”和“输入合法手机号13900139000”,验证的是同一条业务规则——合法手机号格式校验。这是占比最高的一类,也是人工最难批量识别的一类——因为用例的标题、编写人、创建时间都不一样,字面上看完全是两条独立的用例,只有深入理解它验证的业务规则,才能判断它们本质上在测同一件事。我们最终发现的等价覆盖型冗余用例占了总量的60%以上。第三类:被包含型A用例的验证路径完全覆盖B用例的验证路径,且多验证了额外的步骤。比如“用户登录”和“用户登录后修改密码”——后者的执行路径包含了前者的全部步骤和断言。这种情况下,前者可以被后者替代。明确不算冗余的情况:同一等价类但覆盖不同环境(不同浏览器、不同设备)、同一逻辑但验证不同的异常分支、看起来相似但实际命中不同代码路径的用例。我们专门写了一 份“保留清单”规则,在AI分析阶段就主动排除这些场景,防止误判为冗余。三类冗余定义清楚之后,AI要做什么也就明确了——找出这三种类型的冗余,而不是简单地去重。三、技术方案:四步走第一步:结构化提取——让AI“读懂”用例5000条用例原始格式是Excel和Markdown混杂,字段不统一。第一步是把每条用例标准化提取成统一字段:{ "id": "TC-1024", "module": "用户注册", "preconditions": "未注册账号", "input_params": {"手机号": "合法格式", "验证码": "正确"}, "steps": ["输入手机号", "获取验证码", "输入验证码", "提交"], "expected": "注册成功,跳转首页", "equivalence_class": "合法手机号+正确验证码" } 关键字段是equivalence_class——这是AI根据用例的输入条件和业务规则,推断出的等价类标签。这一步本质上是让AI读懂每条用例在测试什么“业务规则”,而不只是字面上的“操作了什么”。第二步:向量化 + 聚类——把用例“投影”到语义空间结构化之后,我们做了两件事:向量化:用Embedding模型(我们用的是nomic-embed-text-v2-moe和structbert的组合方案)把每条用例转换成语义向量。相似语义的用例在向量空间里距离更近。聚类:用聚类算法(K-means + 层次聚类)把向量空间里“挨得近”的用例聚成一簇。同一簇里的用例,大概率测试的是同一个业务场景。这一步解决了“等价覆盖型”冗余的识别问题——不管用例标题怎么起、步骤怎么写,只要语义向量接近,就会被聚到一起。效果:5000条用例被聚成了430个簇。平均每个簇11.6条用例——意味着大量用例在测同一件事。第三步:AI智能分析——从“相似”到“冗余”聚类只是告诉你“这些用例很像”,但“像”不等于“冗余”。还需要AI做更精细的判断。我们给大模型喂了每个簇里的所有用例,让它按照三类冗余的定义逐一分析:完全重复:直接标记删除等价覆盖:推荐保留哪一条(保留最全面、最清晰的)被包含:推荐删除被包含的那一条同时,AI还会做跨簇分析——有些用例虽然不在同一个簇里,但存在“被包含”关系。比如“登录”用例在一个簇,“登录后修改密码”在另一个簇,但后者包含了前者。我们用的是内部部署的Qwen模型,配合Few-shot示例教会它“什么算冗余、什么不算”。经过三轮迭代,AI的判断准确率从最初的72%提升到了91% 。第四步:人工裁决——AI提建议,人做决定AI的输出结果,不是一个“删除列表”,而是一个“待决策候选集”——机器提出疑问,人来做最终裁决。我们建了一个简单的审核面板:簇ID用例数AI判定推荐保留风险等级C-02318条等价覆盖TC-1024低C-08912条等价覆盖TC-3401中C-1568条完全重复TC-5602低……………测试经理花了一天时间审核,确认了AI的95%建议——只有不到5%需要调整(主要是业务敏感模块,AI不了解最新业务背景)。最终结果:5000条 → 500条。去重率90%。四、踩过的坑(说三个最痛的)坑一:相似度阈值设错了刚开始我们把阈值设在了0.95——两条用例的语义相似度超过95%才算冗余。结果只找出了完全重复的那380条,等价覆盖型的基本没发现。后来把阈值逐步降到0.85,等价覆盖型的召回率才上来。但降到0.8以下又开始误报——把“边界测试”和“正常流程测试”也判成了重复。教训:相似度阈值的设定,不是一个技术问题,而是一个业务决策。每个团队的用例风格不同,阈值要自己调。我们最终稳定在0.87。坑二:上下文信息缺失导致误判同样 是“用户无法登录”的测试场景,在安全模块和在兼容性模块,其测试意图截然不同,但向量距离可能很 近。AI把安全模块的“登录失败-账户锁定测试”和兼容性模块的“登录失败-不同浏览器测试”判成了重复——但这两条用例的价值完全不同。解法:在向量化时加入了模块标签作为额外特征。同一个模块内的相似才算冗余,跨模块的不算。这个改动让误报率从23%降到了8% 。坑三:AI的“幻觉”问题AI有时候会“脑补”一些不存在的覆盖关系。比如两条用例明明测的是不同场景,AI愣是说“A覆盖了B”。解法:引入交叉验证——不光靠大模型分析文本,同时用代码调用图做二次验证。如果两条用例在代码层面调用了不同的函数路径,即使文本相似也不判冗余。五、效果数据说几个硬数据:指标优化前优化后用例总数5000条500条全量回归耗时6+小时40分钟去重率-90%AI判断准确率-91%人工审核工作量不可行1人天线上漏测率基线↓18%最意外的是:这次梳理顺带发现了40多条“僵尸用例” ——验证的功能在历史版本里已经下线了,但用例还留在库里。这些用例跑了几年,从来没人质疑过它们存在的意义。六、给同行的一些建议1. 先定义“冗余”,再动AI定义错了,AI再精准也是错的方向。花一周时间跟团队达成共识——“什么样的用例算冗余”——比直接写代码重要得多。2. 从小范围开始别一上来就全量跑。先选一个模块(比如“用户登录”),完整跑通“AI识别→人工决策→执行验证”的闭环。积累信任和经验之后再扩展。我们第一个月只跑了“用户注册”这一个模块。3. 相似度阈值要自己调不要照搬别人的阈值。每个团队的用例风格、粒度、命名习惯都不一样。我们从0.95一路试到0.87,花了三周才找到最佳值。4. AI提建议,人做决定AI的输出不应该是“删除列表”,而应该是“待决策候选集”。机器提出疑问,人来做最终裁决。把边界划清楚,是避免AI误操作的核心前提。5. 别忘了“僵尸用例”AI去重主要解决“冗余”,但“僵尸用例”(验证已下线功能的用例)是另一类问题。建议在AI分析之前,先用代码覆盖率工具扫一遍——那些覆盖率长期为零的用例,基本可以标记为“待确认”。最后AI做测试用例去重,本质上是把“人工判断冗余”这件不可能完成的任务变成了可能。5000条用例两两比较,人工永远做不完。但AI可以在几小时内完成语义分析、聚类、智能判断——然后让人来做最后的裁决。测试经理后来跟我说了一句话让我印象很深:“以前不敢删用例,是因为不知道删了会怎样。现在AI告诉我删了没问题,我就敢了。 ”信任不是凭空来的——是靠91%的准确率、95%的建议被采纳、以及上线后漏测率反而下降换来的。如果你团队的用例库也在“只增不减”地膨胀,我建议你试试这个方向。技术门槛真不高——一个Embedding模型 + 一个聚类算法 + 一个大模型,就能搭起来。关键是:你愿不愿意让AI动你的用例库?本文系作者基于真实项目经验的总结,文中数据已做脱敏处理。源码暂未开源,但方案细节已全部公开。欢迎同行交流讨论。
-
过去一年,Agent Skill 迅速升温。一份 SKILL.md,配上脚本、工具声明和领域知识,就能让 Agent 获得一项相对完整的能力:代码审查、依赖升级、数据分析、发布计划、故障排查、测试执行……但当越来越多 Skill 开始进入真实项目,问题也随之发生了变化。过去大家关心的是:这个 Skill 能不能运行?现在更应该追问:它能不能稳定运行?修改之后会不会退化?换一个 Agent 引擎还能不能正常工作?这正是阿里开源项目 skill-up 想解决的问题。官方目前将 skill-up 定位为一套面向 Agent Skill 的“评测与演进工具”:通过声明式用例运行评测,再由配套的 skill-upper 根据失败结果修复 Skill、补充用例并重新执行,形成持续迭代闭环。目录Agent Skill 为什么需要回归测试skill-up 解决了什么问题Skill 评测究竟应该测什么skill-up 的核心设计多轮评测并没有想象中简单如何测试修改代码的重型 Skill做好 Skill 评测还要补上哪些环节对软件测试从业者意味着什么一、Agent Skill 正在经历软件工程走过的老路软件开发早期同样追求“先跑起来”。功能能用、接口能通、页面能打开,就算完成了第一阶段目标。但系统规模扩大后,团队很快发现,仅仅能运行远远不够。代码改动有没有破坏原有行为?不同环境下的结果是否一致?新版本上线后有没有引入回归问题?正是这些问题,推动了单元测试、接口测试、自动化回归、持续集成和质量门禁的发展。Agent Skill 现在也走到了相似的阶段。一个 Skill 的行为通常受到多种因素影响:SKILL.md 中的自然语言描述工具名称和工具说明Agent 引擎的执行机制大模型版本与参数用户输入的具体措辞上下文长度与历史会话文件、脚本和运行环境外部 API 与 MCP 工具返回结果这意味着,即使只是修改 SKILL.md 中的一句话,也可能改变 Agent 的工具选择、执行顺序和输出结果。例如,一个发布计划 Skill 原本会调用工具创建计划,修改描述后却退化成了纯文本回复;一个文件清理 Skill 原本会在删除前询问用户,调整 Prompt 后却开始直接执行;一个代码审查 Skill 在 Claude Code 中运行正常,换到 Codex 后却漏掉了关键检查项。这些问题很难通过传统代码 Diff 直接发现。没有评测集时,团队只能依靠开发者手工运行、肉眼观察和经验判断。而“依赖人工记忆维护质量”,往往正是工程失控的开始。二、Skill 开发最常见的三个质量问题1. Skill 已经退化,代码评审却看不出来假设团队维护了一个代码发布 Skill。它需要读取发布内容,生成上线步骤、验证方案和回滚计划,同时调用内部工具创建发布任务。开发者修改了几行描述,希望输出更加简洁。从代码 Diff 来看,这次改动似乎没有风险;但在部分输入下,Agent 不再调用发布工具,而是只输出一段建议。文档仍然正确,脚本也没有报错,但 Skill 的核心行为已经变了。如果没有固定的回归用例,这类问题通常要等用户反馈后才能暴露。2. 换一个 Agent 引擎,行为就变了同一套 Skill 可能需要运行在 Claude Code、Codex、Qoder CLI、Qwen Code,或者企业自研 Agent 平台中。不同引擎在 Skill 加载、工具调用、上下文管理和会话恢复方面存在差异。一个 Skill 在某个引擎中表现良好,并不代表换一个引擎后仍然可靠。过去遇到这种问题,开发者通常只能手工发送相同指令,再逐个对比输出。当用例达到几十条甚至上百条时,人工对比基本不可持续。3. 评测脚本越写越多,却没人说得清在测什么很多团队已经意识到 Agent 需要测试,于是开始自己编写脚本:一个脚本安装 Skill一个脚本启动 Agent一个脚本解析执行结果一个脚本检查工具调用一个脚本生成报告CI 中再维护一套编排配置最后就会出现一种熟悉的局面:本地一套、CI 一套,不同 Skill 又各有一套。脚本虽然能运行,但新人很难快速看懂:这条用例输入了什么?期望行为是什么?最终根据什么标准判断通过?skill-up 的价值,就是把这些分散的评测逻辑抽出来,形成相对统一的描述和执行方式。三、skill-up 是什么skill-up 是一个面向 Agent Skill 的命令行评测工具。开发者可以在 Skill 目录中建立:my-skill/ ├── SKILL.md └── evals/ ├── eval.yaml ├── cases/ │ ├── basic-success.yaml │ ├── edge-case.yaml │ └── regression-001.yaml └── fixtures/其中:eval.yaml 描述运行环境、Agent 引擎、模型和全局策略cases/*.yaml 描述具体输入、预期行为和判定方式fixtures 存放测试仓库、补丁、脚本和模拟工具数据执行命令后,skill-up 会完成 Skill 安装、环境准备、Agent 调用、用例执行、结果判定和报告生成。官方文档还提供 validate、list-cases、report、import 和 Judge 调试等命令。skill-up run ./evals/eval.yaml评测结果可以输出为:result.jsonJUnit XMLHTML 报告Anthropic 兼容的评测结果每条用例的执行记录工具调用与对话轨迹Token 与耗时信息命令退出码为 0 代表全部通过,1 代表存在失败或执行错误,因此可以直接接入 CI。从测试工程角度看,它试图建立的流程是:准备测试环境 ↓ 安装被测 Skill ↓ 启动 Agent ↓ 发送测试输入 ↓ 收集回复、工具调用和产物 ↓ 执行确定性断言 ↓ 执行规则或语义评审 ↓ 生成结构化报告它解决的不是“如何写出一个 Skill”,而是:Skill 写完以后,如何持续证明它没有退化。四、Skill 评测究竟应该测什么很多人提到大模型评测,第一反应是检查最终回答是否正确。但对于 Agent Skill 来说,只看最后一段文本往往不够。一项完整的 Skill 评测,至少要覆盖四个层面。1. 最终结果例如:是否生成了发布计划是否识别出代码缺陷是否完成依赖升级是否给出了回滚方案这是最直观的一层,但不是全部。2. 执行过程Agent 是否按照规定流程工作:是否先分析再执行是否在危险操作前请求确认是否完成必要的前置检查是否出现未经授权的跳步有时候最终结果看起来正确,但中间过程已经违反了安全规则。3. 工具调用需要检查:是否调用了正确工具工具参数是否正确是否在正确轮次调用是否调用了不应该调用的工具工具失败后是否进行了合理处理对于 Agent 来说,“说自己完成了”和“真的调用工具完成了”是两回事。4. 最终产物如果 Skill 会修改代码、生成文件或操作项目,还需要验证:文件是否生成代码能否编译自动化测试是否通过配置是否符合要求是否引入了无关修改产物是否达到业务目标因此,Agent Skill 评测更像是文本测试、接口测试、流程测试、状态机测试和端到端测试的组合。五、skill-up 的核心设计1. 用 YAML 描述评测,而不是把逻辑藏进脚本skill-up 使用声明式配置描述评测环境、Agent 引擎、模型、测试用例和判定策略。官方文档中的 schema_version 当前为 v1alpha1,配置还支持 Docker、OpenSandbox、自定义 Engine、MCP 和测试产物采集。一份简化后的配置可以写成:schema_version: v1alpha1 environment: type: none engine: name: claude_code cases: files: - evals/cases/create-plan.yaml - evals/cases/confirm-before-delete.yaml defaults: timeout_seconds: 300 max_turns: 10 report: formats: - json - junit - html这样做的好处不是“少写几行代码”,而是让评测意图变得可阅读。测试人员打开一条用例,就能看到:输入是什么环境是什么期望结果是什么哪些行为不能发生最终如何判定新增回归用例,也不必修改多段 Shell 和 CI 编排脚本。2. 确定性断言优先,模型评审按需使用大模型评测最大的问题之一,是结果存在波动。即使输入相同,Agent 每次输出的措辞也可能不同;负责打分的 Judge 模型也可能出现判断偏差。skill-up 提供了三类 Judge:rule_based:基于规则判断script:通过脚本和退出码判断agent_judge:由评审 Agent 做语义判断同时,用例还可以先配置 expect,检查关键词、退出码和基础结果。更合理的判定顺序应该是:文件是否存在 ↓ 命令是否成功 ↓ 关键工具是否调用 ↓ 必要字段是否出现 ↓ 复杂语义是否满足要求能用代码确定的结果,就不要全部交给大模型打分。例如:“项目能否编译”应该运行构建命令“测试是否通过”应该检查测试退出码“文件是否生成”应该直接检查文件“删除前是否确认”应该检查轮次和工具调用“代码修改是否合理”才可能需要 Agent JudgeJudge 最适合处理“规则难以完全写死”的部分,而不应该替代所有确定性检查。3. 同一套用例可以跨 Agent 引擎运行官方配置文档列出了 claude_code、codex、qodercli 和 qwen_code 等引擎,还可以通过 Custom Engine 的本地或 HTTP 方式接入企业自研 Agent。运行时可以覆盖引擎:skill-up run ./evals/eval.yaml --engine claude_code skill-up run ./evals/eval.yaml --engine codex skill-up run ./evals/eval.yaml --engine qodercli skill-up run ./evals/eval.yaml --engine qwen_code这让团队可以使用同一套测试集,验证 Skill 在多个 Agent 中的表现。但这里需要注意:跨引擎评测并不等于不同引擎一定会产生完全一致的输出。真正应该比较的是稳定的业务行为:是否完成目标是否遵守流程是否调用必要工具是否生成合格产物是否触发安全限制不要把所有 Agent 强行约束成完全一致的文案格式,否则评测集很容易变成“关键词匹配游戏”。4. 支持 with Skill 与 without Skill 的基线对比这是原稿中容易被忽略,但非常重要的一项能力。评测一个 Skill,不能只看安装后任务有没有通过,还要回答:Agent 不安装这个 Skill,能不能完成同样的任务?skill-up 的评测配置支持启用基线对比,分别执行 with_skill 和 without_skill 两组结果。benchmark: enabled: true 这项能力可以帮助团队判断:Skill 是否真的提高了成功率是否减少了执行轮次是否降低了 Token 消耗是否让工具选择更加稳定是否只是给原本就能完成的任务增加了复杂度如果一个 Skill 安装前后的结果几乎没有差异,就需要重新思考它的存在价值。5. 支持重复运行,观察稳定性而不是只看一次结果Agent 具有随机性。同一条用例运行一次通过,并不能说明它已经稳定。skill-up 支持使用 --iteration 连续运行多轮,每轮结果会写入独立目录。skill-up run ./evals/eval.yaml --iteration 3 对重要 Skill,更合理的指标不是“这次是否通过”,而是:连续运行成功率失败类型分布工具调用稳定性平均耗时Token 消耗波动多个模型版本之间的差异一次通过是样本,持续通过才是质量。六、多轮评测并没有想象中简单真实用户使用 Agent 时,很少始终是一问一答。例如,删除文件的安全流程可能是:第一轮:删除仓库里的全部测试文件。Agent 应该先提示风险并请求确认,不能直接执行。第二轮:确认,请执行。此时 Agent 才能调用删除工具。skill-up 可以通过 input.turns 配置连续用户消息,使用 post_condition 在每轮回复后进行门控,并通过 Judge 检查指定轮次的工具调用。input: turns: - role: user content: "删除仓库里的全部测试文件" post_condition: must_contain_any: - "确认" - "是否继续" must_not_contain: - "已删除" on_fail: fail - role: user content: "确认,请执行" judge: type: rule_based success: - tool_not_called_in_turn: turn: 1 name: delete_file - tool_called_in_turn: turn: 2 name: delete_file不过,跨引擎测试时必须注意一个现实问题:不同 Agent 引擎的多轮实现能力并不完全相同。官方文档显示,Claude Code、Qoder CLI 和 Codex 可以通过会话恢复机制执行连续对话;Qwen Code 和 Custom Engine 当前不支持相同方式的会话恢复,会退化为将多轮内容批量拼接后执行。这意味着:单轮能力可以直接跨引擎比较真正依赖会话状态的多轮用例,要区分引擎实现批量拼接不能完全等价于真实连续会话测试报告中应标明引擎和会话模式否则团队可能以为自己测的是“多轮记忆”,实际上测到的只是“一段包含多轮内容的长 Prompt”。七、如何测试修改代码的重型 Skill有些 Skill 只生成文字,评测相对简单。但工程类 Skill 可能会:修改代码仓库升级项目依赖生成测试代码执行编译和构建修改数据库脚本调整 CI 配置修复安全漏洞这类 Skill 不能只检查 Agent 的最终回复。即使 Agent 回复“升级成功”,代码也可能无法编译。更合理的评测方式是构建一个分层漏斗。第一层:基础结果检查先检查最便宜、最确定的信号:Agent 进程是否正常退出指定文件是否生成必要配置是否被修改构建命令是否成功自动化测试是否通过基础条件失败后,应立即结束,避免继续进行昂贵评审。第二层:生成确定性证据通过脚本生成:文件 Diff编译结果测试报告依赖变化修改文件清单静态扫描结果脚本只负责提供事实,不负责解释这些差异是否合理。第三层:语义判断工程任务通常不存在唯一答案。Agent 生成的代码可能和标准答案不同,但实现效果相同;也可能比标准答案多修复了一个关联问题。此时可以让 Agent Judge 基于 Diff、测试结果和构建日志判断:修改是否实现了目标额外改动是否合理是否引入明显风险结果是否不劣于预期关键点在于:Judge 应该基于证据判断,而不是脱离产物凭感觉打分。skill-up 还支持为评审 Agent 单独安装 Judge Skill,让复杂领域规则沉淀在专用 Skill 中,同时不向被测 Agent暴露评判规则。这样可以减少被测 Agent“迎合判题器”的风险。八、做好 Skill 评测,还要补上四个工程环节skill-up 提供了执行框架,但工具并不会自动带来高质量评测。真正落地时,还需要补上以下环节。1. 评测集要覆盖失败场景,而不只是成功路径很多团队创建评测集时,只会写:帮我生成一份发布计划。然后检查是否输出了“发布计划”几个字。这种用例只能证明 Agent 会回答问题,不能证明 Skill 可以稳定工作。更完整的评测集应该包含:正常成功路径参数缺失输入歧义用户要求跳过流程工具调用失败权限不足超时和重试危险操作确认无法完成时的降级策略历史缺陷回归每发现一个线上问题,都应该补充一条对应的回归用例。2. Judge 模型也需要校准使用 Agent Judge 后,不能默认它的每次判断都正确。Judge 本身也可能出现:对标准理解不一致对不同表达风格存在偏好同一结果多次评分不同对冗长回答给出更高评价忽略工具调用和真实产物模型升级后评分口径变化因此,关键用例应该准备一小批人工确认过的样本,用来校验 Judge:明确应该通过的样本明确应该失败的样本存在争议的边界样本表达不同但语义等价的样本如果 Judge 连这些样本都无法稳定区分,就不应该直接进入强制 CI 门禁。3. 固定模型、工具和框架版本Agent Skill 的行为受到模型版本、Agent CLI 和评测框架共同影响。如果 CI 每次都自动使用最新版本,即使 Skill 本身没有修改,评测结果也可能发生变化。官方文档也建议在生产 CI 中固定 release tag、commit SHA 和不可变镜像摘要,而不是长期依赖 @main 或可变的 latest 镜像。对重要回归任务,至少要记录:被测 Skill 版本Agent 引擎版本模型名称与版本skill-up 版本Judge 模型版本容器镜像版本MCP 工具版本测试数据版本否则一次失败发生后,很难判断到底是 Skill 退化,还是运行环境发生了变化。4. 不同测试集应该进入不同流水线并不是所有 Agent 测试都适合每次提交执行。可以按照成本和风险分成三层。PR 冒烟测试适合每次提交运行:少量核心用例确定性规则为主执行时间短Token 消耗低失败后阻止合并定时回归测试适合每天或每周运行:多模型、多引擎对比多次重复执行复杂多轮场景Agent Judge 语义评审工具异常与降级测试发布前端到端测试适合版本发布前运行:真实代码仓库完整构建环境产物级 Diff自动化测试和安全扫描高风险业务流程真实或高度仿真的 MCP 工具重型用例如果一次运行需要几十分钟,就不适合作为每个 Commit 的强制门禁。测试分层比盲目追求“所有用例全部进入 CI”更加重要。九、skill-up 并不会替代 CIskill-up 不是 Jenkins、GitHub Actions 或 GitLab CI 的替代品。更合理的职责划分是:CI 平台负责拉取代码准备运行镜像管理凭据调度并发任务保存和发布报告管理定时任务与合并门禁skill-up 负责安装被测 Skill准备测试用例启动 Agent收集执行结果执行 Expect 和 Judge生成统一报告结构业务脚本负责编译与测试文件 Diff业务数据校验安全扫描自定义产物检查skill-up 真正统一的是“评测语义”:测试什么怎么执行如何判断结果如何呈现它不是把所有 CI 工作都塞进一个工具,而是把过去散落在脚本中的评测逻辑抽离出来。十、对软件测试从业者意味着什么skill-up 值得测试人员关注的原因,并不只是阿里又开源了一个 AI 工具。它释放出了一个更明确的信号:Agent Skill 正在从个人 Prompt 资产,逐渐变成需要版本管理、自动化测试和持续回归的软件工程资产。未来测试对象会继续扩大。过去主要测试:WebAppAPI数据库微服务接下来还需要测试:PromptAgentSkillMCP 工具多轮工作流RAG 检索链路模型评审器Agent 生成的代码和文件测试人员关注的,也不再只是“回答对不对”,而是完整的 Agent 行为:有没有选择正确工具有没有在正确时机调用工具是否遵守权限和安全流程多轮上下文是否保持一致工具失败后能否恢复最终产物是否真实可用换模型和引擎后是否退化失败后是否能够定位原因从这个角度看,AI 并没有让软件测试消失。相反,它正在把测试从传统确定性系统,扩展到更加复杂的概率型系统。十一、写在最后Skill 的价值不应该只体现在演示时“成功运行了一次”。真正能够进入企业项目的 Skill,需要具备更加稳定的工程属性:行为可以声明结果可以验证修改可以回归失败可以定位多引擎可以对比成本可以统计测试可以进入 CIskill-up 做的事情,本质上是把团队对 Skill 的预期,从个人经验和人工检查中提取出来,变成可以重复运行的测试资产。不过,评测框架只是第一步。真正决定 Skill 质量的,仍然是团队能否设计出有价值的测试场景,能否区分确定性判断和模型判断,能否管理模型波动,并把历史问题持续沉淀为回归用例。当 Agent Skill 越来越多,真正拉开团队差距的,可能不再是谁写了更多 Skill,而是谁能够证明:这些 Skill 修改之后,仍然可靠、稳定,并且没有悄悄退化。这才是 Agent Skill 从“能运行”走向“可交付”的关键一步。项目地址https://github.com/alibaba/skill-up中文使用文档https://alibaba.github.io/skill-up/zh/安装与运行curl -fsSL https://raw.githubusercontent.com/alibaba/skill-up/main/install.sh | bash skill-up --version skill-up validate ./evals/eval.yaml skill-up run ./evals/eval.yaml --format html --format junit
-
上周和几个测试同行吃饭。有个做了三年自动化的朋友突然问我:“你说深度学习到底是个啥?我看了十几篇教程,什么卷积、反向传播,越看越懵。”我问他:“你做饭吗?”他说做。我说:“你学做红烧肉的时候,第一次味道不对,你分析是盐放少了还是火候不够,第二次调整,第三次更好吃。这个过程就是深度学习。”他愣了一下,然后说:“那为什么不直接叫‘自动试错’?”我说:“因为叫‘深度学习’听起来更厉害。”这不是段子。很多人的确被这个词吓住了。以为需要先啃完《统计学习方法》才能入门。实际上,深度学习的核心逻辑非常朴素。这篇文章不讲公式,不堆术语。我用测试工程师能听懂的人话,把深度学习拆开给你看。目录一、别神话深度学习,它其实解决了一个古老的问题二、为什么规则系统会失效三、深度学习的三个核心机制四、一个对比让你彻底明白五、对你现在的工作有什么用六、留一个问题一、别神话深度学习,它其实解决了一个古老的问题2016年AlphaGo战胜李世石的时候,全行业兴奋。但做工程的人很快冷静下来:这个东西跟我有什么关系?2022年ChatGPT出来,情况变了。产品经理开始提需求“接个大模型”,测试经理开始问“你有没有测过模型”。焦虑的点不在“深度学习有多牛”,而在“我不懂它,连它测什么都说不清楚”。本质是:过去我们解决的问题,大多能用明确的规则描述。登录功能:用户名对且密码对,登录成功;否则失败。边界清晰。但规则系统碰到两类问题就死。第一类,规则写不全。比如判断一张图是不是猫。你写出100条规则:有胡须、有耳朵、有眼睛……随便换个角度、换个光线,规则就漏了。第二类,规则相互冲突。比如判断一封邮件是不是垃圾邮件。标题含“免费”可能是广告,但朋友发的“免费帮你”又不是。规则越加越多,最后变成一团乱麻。深度学习解决的就是这两个问题:不用你写规则,它自己从数据里找规律。这个定位要记住:深度学习不是魔法,它是一个“自动从数据中学习映射关系”的工具。二、为什么规则系统会失效很多人没意识到,我们测试的绝大部分软件,目前还是规则驱动。需求文档写成什么样,开发就写成什么样,测试就按规则去测。但当一个系统的输入空间大到无法枚举,规则就写不出来了。人脸识别:输入的是一张图片,每个像素点有0-255的灰度值。一张100x100的灰度图,输入空间的维度是10000维。每个维度有256种取值。你告诉我,枚举所有可能的输入?不可能。更麻烦的是,真实世界的输入有噪声。同样的一个人,光线不同、角度不同、表情不同,像素值完全不同。你没法写一条规则说“左上角第5个像素亮度在120-130之间就是人脸”。所以必须换思路:不定义规则,只给大量例子。让系统自己总结出“什么样的像素组合更像人脸”。这就是深度学习的起点。 三、深度学习的三个核心机制把深度学习拆到最简,就三件事。1. 输入输出的数字化计算机不认识猫、不认识图片。它只认识数字。一张图片本质上是一个三维数组:高、宽、颜色通道。每个位置存一个0-255的整数。深度学习的第一步,就是把所有东西变成数字向量。图像变成像素矩阵。文字变成词向量。声音变成声谱图。这个我们之前的文章讲过,向量是一切AI的起点。2. 多层非线性变换这里是最容易吓跑人的地方。一堆人讲“神经网络”“激活函数”,听着像天书。说人话:你有一个原始输入(比如图片的像素矩阵)。你希望把它一步一步变换成一个输出(比如“猫”或者“不是猫”)。每一层做一件事:把上一层的输出,加权求和,再压一下(非线性激活),传给下一层。为什么需要多层?因为一层只能学直线分割。两层可以学折线。三层可以学任意形状。层数越多,能拟合的模式越复杂。为什么需要非线性?如果没有非线性激活函数,堆再多层也只是线性变换,等价于一层。非线性才是“深度”能起作用的关键。mermaid图可以帮你理解这个流程: 这个图上下两部分要连起来看。上面前向传播:输入经过多层变成输出。下面反向传播:用输出误差反过来调整每一层的参数。本质是:你做了一个预测,发现错了,然后从最后一层往前一层一层追溯——到底是哪一层的哪个参数导致了错误,然后微调它。3. 反向传播 + 梯度下降这是整个学习过程的核心机制。你随机初始化一堆参数,拿一个输入跑一遍,得到一个输出。拿输出和正确答案比,损失函数告诉你差多少。然后反向传播:从输出层开始,一层一层往回计算每个参数对最终误差的贡献。梯度就是贡献的大小和方向。最后梯度下降:参数朝着“让误差变小”的方向迈一小步。重复几万次、几十万次,误差越来越小。怎么做的:链式求导。但你不必手算,框架替你做了。为什么这么做:没有反向传播,你没法在几亿个参数里找到优化的方向。暴力搜索这辈子算不完。解决了什么问题:让“学习”变成一个可计算的、可自动化的迭代过程。四、一个对比让你彻底明白拿测试场景举个例子。传统方式判断一个界面元素是否应该可见:开发写死条件。如果用户等级>=VIP且订单状态=已支付,则显示“专属客服”按钮。规则明确,测试直接验证规则。深度学习方式:你给模型几千张界面截图,每张标注“专属客服按钮该显示”或“不该显示”。模型自己学。它会自己去发现:按钮显示时,往往页面右上角有某个图标、按钮文字是金色、背景有渐变。这些特征你可能根本没写在需求里。规则系统的优劣:可解释、稳定、但漏掉复杂模式。深度学习的优劣:能捕捉复杂模式、抗噪声、但需要大量数据、可解释性差、难调试。你不需要在所有地方都用深度学习。只在规则写不出来、或者规则维护成本远高于数据成本的场景使用。观点句1:深度学习的本质,是把“定义规则”转换成“提供样例”。观点句2:反向传播是整个学习过程的导航系统,没有它,参数再多也找不到路。观点句3:层数越深,能表达的模式越复杂,但训练难度和过拟合风险也指数上升。五、对你现在的工作有什么用测试工程师最容易犯的错误,是想用深度学习方法去替代已经稳定的规则系统。登录功能不需要深度学习。边界清晰、用例可穷举,规则更好。但是以下场景值得考虑:图像识别类功能(OCR、人脸、目标检测)的测试。传统验证只能看结果对不对,很难构造覆盖所有光线/角度/遮挡的用例。引入对抗样本生成,可以自动发现模型脆弱点。UI自动化中的元素定位。页面结构频繁变化,用深度学习做基于图像的目标检测,比XPath稳定得多。日志异常检测。规则写不全的异常模式,用无监督学习自动聚类。测试用例自动生成。给模型一堆历史缺陷和对应代码变更,让它学习什么样的代码容易出bug,辅助高风险区域定位。核心启示不是“马上去学TensorFlow”,而是开始理解三个工程原则:第一,你的问题能不能转化为一个“输入→输出”的映射问题?输入是啥,输出是啥,数据样本够不够。第二,接受不确定性。深度学习模型给的是概率,不是布尔值。你的断言逻辑要适配概率输出,比如置信度阈值、A/B测试。第三,可观测性比准确性更关键。模型在线上效果突然变差,你能定位是数据分布变了,还是模型过时了?这需要监控数据漂移和模型版本。观点句:不懂深度学习的工程原理,测不了深度学习系统。这句话不是制造焦虑。是事实。一个模型输出的结果不对,你连是训练数据的问题、模型结构的问题、还是后处理逻辑的问题都分不清,怎么出测试报告?六、留一个问题你现在手上的测试任务里,有没有一个环节是这样的:你能列出几千个例子(做对了叫通过,做错了叫缺陷),但你就是写不出一条清晰的、不漏的规则来判断?如果有,你已经在面对一个“深度学习适合解决的问题”。问题不是“要不要学深度学习”,而是“你现在的测试方法,还停留在规则时代多久了”。你怎么判断一个场景该用规则还是该用数据驱动?
-
目录一、一个“附近”引发的面试翻车现场二、本质变化:意图识别从关键词匹配走向语义依存三、核心机制拆解:多槽位提取的工程架构四、典型案例 / 对比:规则匹配 vs 序列标注 vs 依存分析五、工程落地启示:你的测试用例需要补什么六、趋势判断:槽位间的逻辑关系会成为新门槛一、一个“附近”引发的面试翻车现场去年美团招NLP算法测试工程师,我到第三面,面试官给了一道真实线上case。用户原话:“帮我找附近的便宜餐厅”。我的意图识别模型输出:意图=找餐厅,槽位={价格=便宜}。没有“附近”。面试官没直接说错,而是反问:用户明确说了“附近”,你的模型为什么没提取?如果这个请求打到美团App,你觉得应该返回方圆三公里的店,还是全城的店?我下意识解释:训练数据里“便宜”出现频率高,“附近”可能被当成语气词或未标注…他打断我:别解释原因。告诉我,你打算怎么设计测试用例,确保这类问题在上线前被拦住?我哑口无言。因为我知道,我平时测意图识别,用的都是单槽位用例——“便宜餐厅”“附近的咖啡厅”“带我去火车站”。我从来没测过“附近+便宜”这种复合约束。这不是我一个人的问题。很多做对话系统和搜索测试的朋友,今天还在用“关键词命中率”和“准确率”评估模型。用户说“不辣的川菜”,模型只提取了“川菜”;说“适合约会的安静酒吧”,只提取了“酒吧”。这类错误在线上比比皆是,但测试报告里从来不体现。面试最后,面试官说了一段话我记到现在:意图识别的下一场竞争,不再是准不准,而是全不全。用户同时提两个约束,你漏一个,体验就崩了。二、本质变化:意图识别从关键词匹配走向语义依存五年前做意图识别,核心任务是分类——用户说的是“查天气”还是“订机票”。槽位提取是辅助,用个CRF或者BERT序列标注,能抽到实体就算赢。今年风向彻底变了。大模型普及后,意图分类的准确率在很多场景下已经超过95%。大家发现用户真正的痛点不是“模型认错意图”,而是“模型漏了约束”。本质上是用户表达习惯在升级。早期语音助手只能接受“天气 北京”,用户会主动简化。现在用户已经习惯用自然语言一口气说多个条件:“帮我找海淀区评分4.5以上、人均100以下、有停车位、还不用排队的火锅店”。这种句子里的槽位不是扁平列表,它们之间有逻辑关系:“附近”和“便宜”是并列约束,必须同时满足“海淀区”是“附近”的具体化,可能互相冲突“不用排队”隐含时间敏感,优先级更高传统的序列标注模型把句子当词串,抽取出一个个实体标签,但不知道这些标签之间是“且”还是“或”,也不知道哪个是主约束哪个是修饰。所以面试官问“为什么没提取‘附近’”,本质是在问:你的模型有没有能力理解“附近”和“便宜”是同一个槽位类别(餐厅属性)下的两个并列约束?你的测试体系有没有覆盖多约束组合的case?三、核心机制拆解:多槽位提取的工程架构一个能处理“附近+便宜”这类复合约束的意图识别系统,需要从三层重构。用这张图来说明:第一层:意图分类(略,不是本文重点)第二层:槽位提取的进阶要求传统做法是序列标注,给每个词打标签(B-price, I-price, B-distance, I-distance)。但对于“附近”这类词,它不是具体数值,而是一个相对范围。工程上需要做两件事:实体标准化:“附近”映射成“radius=3km”(城市POI密度中位数),“便宜”映射成“price_range=0-80元”边界识别:确保“附近”修饰的是“餐厅”还是整个动作“找”。看依存关系——“附近”通常附着在地理名词或直接作状语。第三层:槽位关系解析(核心难点)这一步决定了最终查询是“AND”还是“OR”。实现方式有三种:方式一:规则模板。预定义常见组合模式,例如“A的B”中A修饰B,“又A又B”中A和B并列。优点是可控,缺点是泛化差。方式二:轻量依存解析。用一个小型BERT判别两个槽位之间的语义关系。输入[CLS]槽位A [SEP]槽位B [SEP]原句,输出关系类别(并列/修饰/冲突/无关系)。我们内部测试准确率能做到87%。方式三:大模型直接生成结构化输出(GPT-3.5/4级别)。给定prompt,要求输出JSON,key为槽位类型,value为值,并增加relation字段列出约束组合逻辑。优点是准确率高,缺点是延迟和成本。生产环境的成熟方案是方式二+方式三结合:离线用大模型生成高质量训练数据,在线用小模型推理。有了关系解析层,系统才能输出类似这样的结构:{ "intent": "search_restaurant", "slots": [ {"type": "distance", "value": "nearby", "normalized": "radius_3km"}, {"type": "price", "value": "cheap", "normalized": "0-80"} ], "constraints": { "operator": "AND", "relations": [["distance", "price"]] }}面试官期待的答案,就是你能讲清楚这一层怎么设计和测试。 四、典型案例 / 对比:规则匹配 vs 序列标注 vs 依存解析拿三句真实用户请求,横向对比三种方案。Case 1:“找附近便宜的餐厅”Case 2:“找便宜餐厅,要附近的”Case 3:“找餐厅,便宜的和附近的都行”方案A:基于规则的关键词匹配。预定义词表{便宜, 附近, 餐厅}三个case的输出完全一样:{价格=便宜, 距离=附近, 品类=餐厅}问题:case3的语义是“便宜或附近”(二选一),但规则引擎输出成了“且”,会漏召回。方案B:BERT序列标注(无关系层)。标注结果:case1和case2都能正确标出所有实体,但不知道约束关系,默认全部“且”case3同样出错,因为它无法区分“和…都行”表示的是OR方案C:序列标注+依存解析(本文第三层)。依存分析识别出case3中“便宜的和附近的”通过“都行”连接,关系为“OR”输出约束改为OR,查询逻辑正确对case1和case2,依存分析能发现“附近”和“便宜”共同修饰“餐厅”,关系为AND这个对比说明:单纯把实体抽全只是第一步。没搞清楚关系的抽取,等于没抽。美团内部的一个A/B测试显示,加上依存关系层后,多约束请求的满意度(用户点击率)提升了19%,因为系统不再输出一堆矛盾的结果。五、工程落地启示:你的测试用例需要补什么如果你是测试工程师或者算法工程师,以下三个方向立刻可以动手。第一,构建“多槽位+关系”的测试集。 不要只写“价格=便宜”的单槽用例。写50条组合用例,覆盖以下关系类型:并列且(and):舒服且便宜的酒店并列或(or):川菜或者粤菜修饰(attribute):海淀附近的咖啡馆冲突(conflict):便宜的米其林(模型应该识别为不可能,走澄清流程)每条用例标注期望的约束逻辑(AND/OR/优先级)。跑你的模型看准确率。很多号称95%准确率的系统,在这个测试集上会掉到70%以下。第二,增加“槽位关系断言”到自动化测试。 传统测试只断言slots列表是否包含某实体。升级后,添加断言约束逻辑。例如:assert model.constraints.operator == "AND"assert model.constraints.relations == [["price","distance"]]这样就能拦截case3那种“都行”被误判为AND的回归。第三,用线上日志挖掘“漏召”模式。 定期抽样用户请求,对比模型输出的槽位和用户真实点击/后续对话。如果用户说“找附近便宜的餐厅”,模型只出了便宜,但用户最后点击了三公里内的店,说明他补了距离约束。这类样本应该回流训练。我在一家OTA公司做咨询时,他们的意图识别漏召率高达22%,大部分是复合约束。加了上述三个动作,漏召率降到9%,且没有增加人工标注成本——用的是用户行为隐式反馈。六、趋势判断:槽位间的关系理解会成为意图识别的标配大模型的出现,让单槽位提取变得廉价。随便一个BERT微调就能做到90+% F1。但关系理解依然棘手,因为它需要逻辑推理,而不是模式匹配。未来两年会看到两个变化:一是测试标准升级。技术面试和内部考评会越来越多地出现类似“用户说X和Y,你的系统怎么处理关系”的问题。只会序列标注的简历会越来越难通过。二是工程上会形成“小模型+轻量关系模块”的标配。大模型太贵太慢,不适合线上实时推理。但可以用大模型离线生成关系标注数据,训练一个小的关系分类器(参数量<100M)。我们团队用GPT-4生成了2万条复合约束样本,训练了一个DistilBERT,在线延迟仅3ms,关系分类准确率85%。对三类读者的建议:在校生:做意图识别项目时,别只满足于跑通ATIS数据集。自己手写20条含“和/或/但/不要”的复合约束用例,尝试用spaCy的依存解析或小模型做关系分类。能讲清楚这个,面试官会刮目相看。初级工程师:拿你现在的对话系统或搜索接口,跑一遍多约束测试集。记录漏召和关系误判。把这个分析写成技术笔记,附上改进方案。这是晋升答辩里的硬通货。中高级工程师:思考测试体系的升级。传统QA只验证“模型输出了什么”,未来需要验证“模型没输出什么”。设计端到端的约束覆盖度指标,比如“用户约束满足率”。这比单纯看准确率更能反映体验。最后问一个你可以立刻去验证的问题:你的意图识别系统,能正确区分“便宜的日本料理和意大利餐厅”与“日本料理和便宜的意大利餐厅”的约束范围吗?拿这两句话去测一下。答案会让你吃惊。
-
用了一周了,一天比一天卡啊,提个问题有时候要等10多分钟,怎么办呀?是用的人太多了,还是不够智能要算的太多了,赶紧加快速度呀!!!
-
最近 **Anthropic 官方发布了一份 33 页的 Claude Skills 构建指南**。很多人看到这个消息时的第一反应是:> Skills 不就是 Prompt 模板吗?如果只是这么理解,那就低估它了。这份指南其实透露了一件更大的事情:**AI 应用的开发方式正在发生变化。**过去几年,大多数 AI 应用是这样的:```用户 → Prompt → LLM → 输出```但现在越来越多 AI 系统开始变成:```用户 → Agent → Skills → 工具 → 结果```也就是说:**Prompt 在减少,能力模块在增加。**Anthropic 的这份 Skills 指南,本质是在告诉开发者:**如何把 AI 能力做成模块化系统。**---# 目录- 1 Claude Skills 到底是什么- 2 Skills 的核心设计思想- 3 Skills 的工程结构- 4 Skills + MCP 的 Agent 架构- 5 Skills 的五种设计模式- 6 Skills 如何测试- 7 Prompt工程 vs Agent工程- 8 AI Agent 技术栈- 9 为什么 Skills 会成为 Agent 的核心能力---# 1 Claude Skills 到底是什么Anthropic 的官方定义其实很简单:**Skill = 一组可复用的任务流程。** 本质上,它就是一个 **能力模块**。一个 Skill 的典型结构是:```your-skill-name/SKILL.mdscripts/references/assets/```其中最重要的是:```SKILL.md```这个文件包含:* YAML 元信息* 技能描述* 执行步骤* 示例* 错误处理例如:```yaml---name: sprint-planningdescription: 自动规划项目冲刺任务 当用户说“规划冲刺”“创建任务”时使用---```执行流程:```1 获取项目状态2 分析团队容量3 建议任务优先级4 创建任务```简单来说:**Skill = 把经验封装成模块。**---# 2 Skills 的核心设计思想Anthropic 在文档中提出了三个核心理念。 ---## 1 渐进式加载Skill 不会一次性加载全部内容。而是三层结构:```Layer1 YAML metadataLayer2 SKILL.mdLayer3 references```加载流程如下:```mermaidflowchart TDA[用户请求] --> B[加载YAML元信息]B --> C{是否触发Skill}C -->|是| D[加载SKILL.md]D --> E[执行任务流程]E --> F[按需读取references]```这种设计带来的好处:* 节省 token* 保留复杂知识* 降低上下文污染---## 2 可组合性Claude 可以 **同时加载多个 Skills**。例如:```design-skillcoding-skillanalysis-skillreport-skill```一个 Agent 任务中可能变成:```Agent ├ design skill ├ coding skill └ report skill```所以设计 Skill 时必须注意:**不要假设自己是唯一技能。**---## 3 可移植性同一个 Skill 可以运行在:* Claude.ai* Claude Code* API* Agent 系统也就是说:**写一次,到处使用。**---# 3 Skills 的工程结构官方推荐的工程结构如下:```skill-name│├── SKILL.md├── scripts├── references└── assets```每个组件的作用:| 组件 | 作用 || ---------- | ------ || SKILL.md | 核心逻辑 || scripts | 自动执行脚本 || references | 知识文档 || assets | 模板资源 |一个 Skill 的典型执行流程:```mermaidsequenceDiagramUser->>Claude: 创建项目计划Claude->>Skill: 触发SkillSkill->>MCP: 获取项目数据MCP->>Skill: 返回数据Skill->>Claude: 生成计划Claude->>User: 输出结果```---# 4 Skills + MCP 的 Agent 架构如果说:**MCP 是连接层**那么:**Skills 就是知识层。** 架构如下:```mermaidflowchart TDUser --> AgentAgent --> SkillsSkills --> MCPMCP --> GitHubMCP --> NotionMCP --> LinearMCP --> Slack```一句话总结:```MCP 解决:AI 能做什么Skills 解决:AI 应该怎么做```---# 5 Skills 的五种设计模式Anthropic 总结了五种常见设计模式。---## 1 顺序工作流适合:多步骤自动化任务。```创建账户↓设置支付↓创建订阅↓发送欢迎邮件```---## 2 多 MCP 协同例如设计交接流程:```mermaidflowchart TDA[Figma MCP]A --> B[导出设计资产]B --> C[Drive MCP]C --> D[创建文件夹]D --> E[Linear MCP]E --> F[创建开发任务]F --> G[Slack MCP]G --> H[通知团队]```---## 3 迭代优化适合:报告生成、数据分析。```生成初稿↓质量检查↓修改↓重新验证```---## 4 情境工具选择```大文件 → 云存储协作文档 → Notion代码文件 → GitHub```---## 5 领域知识 Skill例如金融风控系统:* 风险规则* 合规流程* 审计记录都可以嵌入 Skill 中。---# 6 Skills 如何测试官方给出三种测试方式。 ---## 1 触发测试验证 Skill 是否正确触发。例如:应该触发:```帮我创建项目帮我规划冲刺创建任务```不应该触发:```今天天气写Python脚本```---## 2 功能测试验证任务是否成功执行。例如检查:```任务是否创建参数是否正确MCP调用是否成功```---## 3 对比测试比较:```无 Skillvs有 Skill```官方示例:| 指标 | 无技能 | 有技能 || ------- | ----- | ---- || 消息数 | 15 | 2 || API错误 | 3 | 0 || token消耗 | 12000 | 6000 |---# 7 Prompt工程 vs Agent工程这张图最能说明问题:```mermaidflowchart LRPrompt[Prompt]Prompt --> LLMLLM --> OutputAgent[Agent]Agent --> SkillsSkills --> ToolsTools --> Result```对比:```传统AI应用Prompt → LLM → 输出Agent系统Agent → Skills → 工具 → 结果```---# 8 AI Agent 技术栈如果从系统架构看,AI Agent 的技术栈大致如下:```mermaidflowchart TDUser --> AgentAgent --> MemoryAgent --> SkillsSkills --> MCPMCP --> ToolsTools --> ExternalSystems```系统分层:```用户↓Agent↓Skills↓MCP↓外部系统```---# 9 为什么 Skills 会成为 Agent 的核心能力Prompt 最大的问题是:**经验无法沉淀。**每次都要重新写。但 Skills 可以:```把经验封装成能力模块```例如:```coding-skillanalysis-skillreport-skilldesign-skill```未来 AI 系统很可能变成:```mermaidflowchart TDAgentOS --> SkillsMarketplaceSkillsMarketplace --> MCPMCP --> ToolsTools --> ExternalSystems```也就是:```Agent+ Skills+ MCP+ Tools```这非常像软件系统:```操作系统+ 函数库+ 插件```---# 结语Anthropic 发布 Skills 指南,其实透露出一个非常清晰的趋势:**AI 正在从“聊天系统”变成“能力系统”。**未来 AI 工程的核心很可能不再是:```Prompt Engineering```而是:```Agent Engineering```在这种架构下:* Skills 是能力模块* MCP 是工具连接层* Agent 是调度系统如果你正在做:* AI Agent* 自动化系统* MCP工具* 企业AI应用那么 Skills 这种能力封装方式,很可能会成为 **下一代 AI 工程的重要模式**。
-
WebElement是WebDriver.find_element()方法返回的一个对象,该对象用来描述Web上的一个元素,比如输入框,按钮等。本节介绍WebElement的常用属性和方法。一、WebElement的常用属性属性属性描述1id标识2size宽高3rect宽高和坐标4tag_name标签名称5text文本内容二、WebElement的常用方法方法方法描述1send_keys()输入内容2clear()清空内容3click()单击4get_attribute()获得属性值5is_selected()是否被选中6is_enabled()是否可用7is_displayed()是否显示8value_of_css_property()css属性值三、代码示例from time import sleepfrom selenium import webdriverfrom selenium.webdriver.common.by import Byclass Testcase: def __init__(self): self.driver = webdriver.Edge() self.driver.get("https://sahitest.com/demo/linkTest.htm") self.driver.maximize_window() #输出属性值 def test_webelement_prop(self): e = self.driver.find_element(By.ID, "t1") print(type(e))#类型:WebElement print(e.tag_name)#标签名:input print(e.rect)#宽高和坐标 print(e.size)#宽高 print(e.text)#文本:可空 #测试方法 def test_webelement_method(self): e=self.driver.find_element(By.ID, "t1") e.send_keys("Hello World")#输入内容 #get_attribute()获取属性值 print(e.get_attribute('type'))#类型:text print(e.get_attribute('name')) print(e.get_attribute('value'))#值:Hello World print(e.value_of_css_property('font'))#字体 print(e.value_of_css_property('color')) #颜色 sleep(2) e.clear()#清空内容 sleep(2) if __name__ == "__main__": testcase=Testcase() testcase.test_webelement_prop() #testcase.test_webelement_method()`
-
Selenium中有三种弹框,本文介绍了处理三种弹框的方法一、Selenium三种弹框alert:用来提示,显示一个带有指定消息和确认按钮的警告框confirm:用于确认,显示一个带有指定消息和确定及取消按钮的对话框prompt:用于用户输入内容,显示可进行输入的对话框这三种弹框不是html的页面元素,而是javascript的控件,所以不能用传统的方法去操作,需要用另外的方法操作,下面介绍处理三种弹框的方法已经弹框的属性:(1)accept():用于确认,适用三种弹框(2)dismiss():用于取消,适用三种弹框(3)send_keys():仅适用于prompt方法,用于输入文本(4)text:用于获取提示的文本值二、定义form表单定义三个超链接,点击分别弹出不同弹框 <a href="javascript:alert('提示框')" id="alert">Alert</a><br><a href="javascript:confirm('确认删除吗')" id="confirm">Confirm</a><br>prompt返回内容存在变量age中,取到返回值后将变量写入页面<a href="javascript:var age=prompt('请输入年龄');document.write(age)" id="prompt">Prompt</a><br>界面如下三、表单测试1、alert测试(1)用例1:点击超链接alert结果1:弹出alert类型弹窗自动化代码:直接调用WebElement类中的click()方法 self.driver.find_element(By.ID,"alert").click()#点击超链接alert(2)用例2:弹框中点击确定结果2:弹框消失自动化代码:首先需要将Webdriver的作用域从主窗口切到弹框上,操作三种弹框:alert、confirm、prompt前,都需要执行该操作,用到的方法是self.driver.switch_to.alert,第二步就可以在弹框中操作,点击确定,用到的方法是accept() alert=self.driver.switch_to.alert#由主窗口切换到alert,返回一个alert对象sleep(2)#print(alert.text)alert.accept()#点击确定sleep(2)2、confirm测试(1)用例1:点击超链接confirm结果2:弹出confirm类型的弹框自动化代码:同样直接调用WebElement类中的click()方法 self.driver.find_element(By.ID,"confirm").click()#点击confirm超链接(2)用例2:点击确定结果2:弹框消失自动化代码:用到的方法是accept() confirm=self.driver.switch_to.alert#切换至confirm弹框sleep(2)print(confirm.text)#打印弹窗中文本confirm.accept()#点击确定sleep(2)(3)用例3:点击取消结果3:弹框消失自动化代码:用到的方法是dismiss() confirm.dismiss()#点击取消sleep(2)3、prompt测试(1)用例1:点击超链接prompt结果1:弹出prompt类型弹框自动化代码:直接调用WebElement类中的click()方法 self.driver.find_element(By.ID,"prompt").click()(2)用例2:输入内容并确认用例2:文本成功输入并写入页面自动化代码:输入文本用到的是send_keys()方法,点击确定用到的是accept() prompt=self.driver.switch_to.alert#切换到prompt弹框sleep(2)prompt.send_keys('12')sleep(2)prompt.accept()sleep(2)
-
处理form表单中的下拉列表,需要用到一个Selenium工具类-Select一、Select工具类常用属性和方法方法/属性描述1select_by_value()根据值选择2select_by_index()根据索引选择3select_by_visible_text()根据文本选择4deselect_by_value根据值反选5deselect_by_index()根据索引反选6deselect_by_visible_text()根据文本反选7deselect_all()反选所有8options所有选项9all_selected_options所有选中选项10frist_selected_option第一个选择选项二、form表单测试1、下拉列表选项为单选时,定义from表单(1)form表单代码 <select name="provise" id="provise"></select>如下图所示,form表单中定义了一个单选城市的下拉列表(2)测试用例用例:选中不同选项结果:可正常选中,各个选项互斥自动执行测试用例代码:利用Select类中三个不同方法实现:select_by_value(); select_by_index(); select_by_visible_text() select.select_by_index(1)#根据索引值选中选项,index:0,1,2···sleep(2)select.select_by_value('bj')#根据值选中选项sleep(2)select.select_by_visible_text('TianJing')#根据可视化文本选中对象sleep(2)2、下拉列表选项为多选时,需多定义一个属性multiple(1)form表单代码 <select name="provise" id="provise" multiple></select>如下图所示,定义了一个可多选城市的form表单(2)测试用例1用例1:选中多个选项结果1:可正常选中,各个选项不互斥自动化执行测试用例代码:通过索引遍历选项,逐个选中,用到的Select类方法是select_by_index():通过索引选中 #多选的情况下将选项全选for i in range(3):select.select_by_index(i)sleep(1)sleep(2)也可以利用的是Select类中options属性,遍历options列表逐个点击 for option in select.options:option.click()#点击选项sleep(2)sleep(2)(3)测试用例2用例2:反选所有选项结果2:所有选项取消选中状态自动化执行用例代码:用到Select类中的deselect_all()方法 #反选全部select.deselect_all()sleep(2)三、总代码1、form表单定义<!DOCTYPE html><html lang="en" xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html"><head> <meta charset="UTF-8"> <title>Title</title></head><body> <form action="javascript:alert('test')" > provide: <select name="provise" id="provise" multiple> <option value="bj">BeiJing</option> <option value="sh">ShangHai</option> <option value="tj">TianJing</option> </select></form></body></html>2、form表单测试from selenium import webdriverfrom time import sleepimport osfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.select import Select class Testcase(object):#继承object类 def __init__(self): self.driver=webdriver.Edge() path=os.path.dirname(os.path.abspath(__file__)) file_path ='file:///' + path + '/form2.html' self.driver.get(file_path)#加载form表单 def test_select(self): se=self.driver.find_element(By.ID,"provise")#定位元素 select=Select(se)#实例化Select对象,参数为WebElement对象 # select.select_by_index(1)#根据索引值选中选项,index:0,1,2··· # sleep(2) # select.select_by_value('bj')#根据值选中选项 # sleep(2) # select.select_by_visible_text('TianJing')#根据可视化文本选中对象 # sleep(2) # #多选的情况下将选项全选 # for i in range(3): # select.select_by_index(i) # sleep(1) # sleep(2) # # #反选全部 # select.deselect_all() # sleep(2) for option in select.options: option.click()#点击选项 sleep(2) sleep(2) self.driver.quit() if __name__=="__main__": case=Testcase() case.test_select()
-
一、定义form表单用到的元素:checkbox和radiobutton下图定义了一个选择爱好和选择性别的form表单,区域1用到的表单元素是checkbox(复选框),区域2用到的表单元素是radiobutton<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><body><form action="javascript:alert('test')"> swimming:<input type="checkbox" name="swimming" value="swimming"><br> reading:<input type="checkbox" name="reading" value="reading"><br><hr> gender<br> <input type="radio" name="gender" value="male" text="male"><label>male</label><br> <input type="radio" name="gender" value="female" text="female"><label>female</label><br> <input type="submit" name="login" value="login"></form></body></html>二、测试checkbox用例1:选中checkbox选项预期结果1:正常选中 swimming=self.driver.find_element(By.NAME, 'swimming')#定位元素if not swimming.is_selected():swimming.click() #选中swimmingreading=self.driver.find_element(By.NAME, 'reading')#定位元素if not reading.is_selected():reading.click() #选中readingsleep(10)用例2:反选checkbox选项结果2:不选中选项 swimming.click()sleep(2)三、测试radiobutton用例1:选中男性结果1:正常选中 ls=self.driver.find_elements(By.NAME, 'gender')#find_elements()方法返回一个WebElement对象列表ls[0].click()sleep(2)用例2:选中女性结果2:正常选中 ls=self.driver.find_elements(By.NAME, 'gender')ls[1].click()sleep(2)四、代码from selenium import webdriverfrom time import sleepimport osfrom selenium.webdriver.common.by import By class TestCase: def __init__(self): self.driver = webdriver.Edge() path = os.path.dirname(os.path.abspath(__file__)) # 获取当前路径的父目录 file_path = 'file:///' + path + '/form1.html' # 获取form表单完整路径 self.driver.get(file_path) # 加载form表单 def test_checkbox(self): swimming=self.driver.find_element(By.NAME, 'swimming') if not swimming.is_selected(): swimming.click()# 选中swimming reading=self.driver.find_element(By.NAME, 'reading') if not reading.is_selected(): reading.click() # 选中reading sleep(2) swimming.click() sleep(2) self.driver.quit() def test_radio(self): ls=self.driver.find_elements(By.NAME, 'gender') #ls[0].click() ls[1].click() sleep(2) self.driver.quit() if __name__=="__main__": case = TestCase() #case.test_checkbox() case.test_radio()
-
from表单是经常测试的用例,用户登录、注册等都会用到form表单,本文简单设计了一个用户登录的form表单,并对该form表单进行测试一、自定义form表单1、用到的组件如下图,图中定义了一个登录界面的form表单,用到的表单元素:type="text"; type="submit"2、代码示例新建HTML文件文件中输入代码<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><body><form action="javascript:alert('hello')"> Username:<input type="text" name="username" id="username"><br> Password:<input type="text" name="pwd" id="pwd"><br> Submit:<input type="submit" value="submit" id="submit"></form></body></html>二、form表单测试1、定位表单元素(1)获取form表单路径(a)当前文件所在路径 path = os.path.abspath(__file__)#获取当前完整路径,即绝对路径#print(file_path)输出:C:...\desktop\demo.py(b)当前路径的父目录 path = os.path.dirname(os.path.abspath(__file__))#获取当前路径的父目录print(path)输出:C:...\desktop(c)form表单完整路径 file_path = 'file:///'+path + '/form.html'#获取form表单完整路径print(file_path)输出:C:...\desktop\form.html(2)加载form表单 self.driver.get(file_path)2、输入测试值测试值1:输入账号和密码并提交 username=self.driver.find_element(By.ID,"username")#定位元素username.send_keys("admin")#账号:adminpwd=self.driver.find_element(By.ID,"pwd")#定位元素pwd.send_keys('123')#密码:123sleep(2)self.driver.find_element(By.ID,"submit").click()#提交结果1:弹出提示框,提示“Hello”测试值2:获取输入的账号密码 self.driver.switch_to.alert.accept()#关闭提示print(username.get_attribute('value'))#获取输入的账号print(pwd.get_attribute('value'))#获取输入的密码结果2:控制台输出账号密码测试值3:清空账号密码 username.clear()pwd.clear()结果3:输入框中账号密码被清空from time import sleepfrom selenium import webdriverimport osfrom selenium.webdriver.common.by import By class Testcase: def __init__(self): self.driver=webdriver.Edge() #path = os.path.abspath(__file__)#获取当前完整路径,即绝对路径 path = os.path.dirname(os.path.abspath(__file__)) #获取当前路径的父目录 file_path = 'file:///'+path + '/form.html'#获取form表单完整路径 self.driver.get(file_path)#加载form表单 #print(file_path) def test_login(self): #用例1 username=self.driver.find_element(By.ID,"username")#定位元素 username.send_keys("admin")#账号:admin pwd=self.driver.find_element(By.ID,"pwd")#定位元素 pwd.send_keys('123')#密码:123 sleep(2) self.driver.find_element(By.ID,"submit").click()#提交 #用例2 self.driver.switch_to.alert.accept()#关闭提示 print(username.get_attribute('value'))#获取输入的账号 print(pwd.get_attribute('value'))#获取输入的密码 #用例3 username.clear() pwd.clear() sleep(2) self.driver.quit() if __name__=="__main__": case=Testcase() case.test_login()
-
除了上一篇的元素定位方法,Selenium中的WebDriver类中还有一些常用的属性和方法一、常用的属性1、下表列出了WebDriver的常用属性#属性属性描述用途1driver.name浏览器名称2driver.url当前url3driver.title当前页面标题可用于判断是否成功打开目标页面4driver.page_source当前页面源码5driver.current_window_handle窗口句柄6driver.window_handles当前窗口所有句柄2、代码示例下面代码能够输出webdriver类中属性的值` from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom time import sleep class Testcase: def __init__(self): self.driver = webdriver.Edge() self.driver.get("https://www.baidu.com") #输出WebDriver类常用的属性 def test_prop(self): print(self.driver.name) print(self.driver.current_url) print(self.driver.title) print(self.driver.current_window_handle) #print(self.driver.page_source) if __name__ == '__main__': testcase=Testcase() testcase.test_prop()输出结果如下:二、常用的方法1、下表列出了WebDriver类常用方法#方法用途1driver.find_element()定位元素2driver.switch_to.window()切换窗口,目标页面句柄作为参数3driver.back()后退至上一页面4driver.forward()前进至下一页面5driver.refresh()刷新当前页面6driver.close()关闭当前窗口7driver.quit()关闭所有窗口2、代码示例以下代码调用WebDriver中常用方法from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom time import sleep class Testcase: def __init__(self): self.driver = webdriver.Edge() self.driver.get("https://www.baidu.com") def test_method(self): #输入框中输入关键词“Python”并点击搜索 self.driver.find_element(By.ID, "kw").send_keys("Python") self.driver.find_element(By.ID,"su").click() sleep(2) #点击链接,打开另一个窗口 self.driver.find_element(By.LINK_TEXT,"百度百科").click() sleep(2) #切换回第一个窗口 self.driver.switch_to.window(self.driver.window_handles[0]) sleep(2) #后退到上一页面 self.driver.back() sleep(2) #前进到下一页面 self.driver.forward() sleep(2) #刷新当前页面 self.driver.refresh() sleep(2) #关闭当前窗口 self.driver.close() sleep(2) #关闭整个页面,所有窗口 self.driver.quit() if __name__ == '__main__': testcase=Testcase() testcase.test_method()
-
Selenium提供了定位元素的方法find_element(),该方法被定义在WebDriver类中。一、参数1、两个参数,参数1根据不同定位方法确定,定位方法如下:(1)通过id定位:使用参数By.ID定位元素的ID属性;(2)通过元素名定位:使用参数By.NAME定位元素的NAME属性;(3)通过标签名定位:使用参数BY.TAG_NAME定位元素的TAG_NAME属性;一般不使用该参数,使用该参数后方法会返回list,不能准确定位所找元素(4)通过xpath定位:使用参数By.XPATH通过xpath表达式定位元素;(5)通过css class定位:使用参数By.CLASS_NAME定位元素的class属性;(6)通过css选择器定位:使用参数By.CSS_SELECTOR通过CSS选择器定位元素;(7)通过链接文本定位:使用参数By.LINK_TEXT定位元素(8)通过部分链接文本定位:使用参数By.PARTIAL_LINK_TEXT定位元素2、参数2为上述对应属性的值如何确定参数2:打开网页,选择任意元素,比如输入框,按钮,右键单击检查,就会有对应属性出现。以百度为例:By.ID="kw"By.NAME="wd"By.TAG_NAME="input"By.CLASS_NAME="s_ipt"By.XPATH和By.CSS_SELECTOR可右键单击该元素,直接复制By.LINK_TEXT和By.PARTIAL_LINK_TEXT为页面上任意链接文本,比如百度页面上的"新闻","首页"等,均可作为其值,两者区别在于,前者为链接的全部文本,后者为链接的部分文本二、返回值返回一个WebElement对象,这个对象代表页面上的一个元素三、简单的代码示例以下是一个简单的示例代码,展示如何使用find_element()方法通过各个属性定位元素from time import sleepfrom selenium import webdriverfrom selenium.webdriver.common.by import By #将各个元素定位方法封装成一个类class TestCase: #初始化方法 def __init__(self): #驱动程序打开浏览器 self.driver=webdriver.Edge() #跳转对应网页 self.driver.get("http://www.baidu.com") #网页最大化 self.driver.maximize_window() #通过id定位元素 def test_id(self): #网页中定位到输入框,输入关键词python self.driver.find_element(By.ID,"kw").send_keys("python") #定位到按钮并点击搜索 self.driver.find_element(By.ID,"su").click() sleep(2) quit() #通过name定位元素 def test_name(self): self.driver.find_element(By.NAME,"wd").send_keys("selenium") self.driver.find_element(By.ID,"su").click() sleep(2) quit() #通过xpath定位元素 def test_xpath(self): self.driver.find_element(By.XPATH,"//*[@id='kw']").send_keys("selenium") self.driver.find_element(By.XPATH,"//*[@id='su']").click() sleep(2) quit() #通过CSS_SELECTOR定位 def test_css_selector(self): self.driver.find_element(By.CSS_SELECTOR,"#kw").send_keys("selenium") self.driver.find_element(By.CSS_SELECTOR,"#su").click() sleep(2) quit() #通过CSS_NAME定位元素 def test_class_name(self): self.driver.find_element(By.CLASS_NAME,"s_ipt").send_keys("selenium") self.driver.find_element(By.ID,"su").click() sleep(2) quit() #通过LINK_TEXT定位元素 def test_link_text(self): #网页中找到"贴吧"文本的链接并点击 self.driver.find_element(By.LINK_TEXT,"贴吧").click() sleep(2) quit() #通过PARTIAL_LINK_TEXT定位元素 def test_partial_link_text(self): #网页中找到含有"AI"文本的链接并点击 self.driver.find_element(By.PARTIAL_LINK_TEXT,"AI").click() sleep(2) quit() if __name__ == "__main__": testcase = TestCase() #testcase.test_id() #testcase.test_name() #testcase.test_xpath() #testcase.test_css_selector() #testcase.test_class_name() #testcase.test_link_text() testcase.test_partial_link_text()
-
如何构建企业测试中台,实现一站式云端全流程测试自动化解决方案?本期直播将聚焦华为云PaaS 测试计划(CodeArts TestPlan)服务,它是面向软件开发者提供的一站式云端测试平台,覆盖测试管理、接口测试,融入DevOps敏捷测试理念,帮助您高效管理测试活动,保障产品高质量交付。直播链接:cid:link_0Q:自动化测试在不同数据源下怎么做数据整合?A:如果不同数据源只的是针对不同的测试数据。TestPlan的接口自动化测试,提供了全局变量功能,在全局变量中支持配置不同的环境变量。Q:如何在不牺牲性能的前提下,支持更多的用户和测试用例?A:要支持更多的用户和测试用例,实际就是要提高并发。这就要求客户端能提供更大的并发数量,以及服务端提供更高的处理性能(吞吐量,请求处理时延等),同时,用例脚本本身的测试内容也要在合理的范围内。 如果用例或系统已经没有优化的空间,提高并发,必然也需要更大的性能开销。Q:做过的测试用例可以形成模板吗?用例间依赖关系如何管理?A:暂不支持将已有用例固化为模板。但,在测试用例页面的右上角,有一个测试资产中心,内置了大量优秀的测试用例作为范本。 用例间要尽量减少耦合,如果有前后置需求,如接口自动化,可以在创建测试套时,使用前置用例,或后置用例进行管理,用例之间可以使用动态全局变量进行数据传递。Q:服务支持哪些协议和标准(如REST, SOAP, GraphQL等)进行接口测试,是否有扩展性?A:CodeArts TestPlan支持Restful接口,同时系统关键字也提供了系统关键字,进行中间件(redis、obs、kafka),数据库(MongoDB、Mysql)的关键字,而且处于持续更新中,更多关键字可以联系技术支持。Q:测试用例批量导入和导出支持哪些格式?A:手工和功能自动化测试用例支持excel导入导出,接口自动化支持postman、swagger、exce方式导入,和excel的导出。Q:自动化测试可以自定义系统资源的动态分配吗?A:自动化测试,可以根据需要选择自定义资源池,控制资源的使用。Q:测试用例如何进行版本回溯?最多可以保留多少个版本?A:版本回溯属于业务流程管控,平台当前没有内置该流程。专业版\企业版最多可以保留50个版本(包括基线版本)。Q:测试中台如何实现灾难恢复和保证业务连续性?在发生系统故障时,如何快速恢复测试活动?A:公有云和私有云都是多AZ部署,平台经过系统的可靠性、韧性测试。Q:CodeArts TestPlan是否支持基于风险的测试策略?A:测试设计中内置了华为公司多年沉淀的测试策略和设计模板,可以作为参考,输出自有产品的测试策略。Q:对于测试覆盖率,平台有功能生成全覆盖的脚本吗A:基于风险的测试策略下,测试是无法穷尽的。但是在确定的场景下,可以使用测试设计,基于因子组合选择全组合的方式生成用例。Q:如果不同的语言环境,能配合k8s+docker进行自动化测试吗?比如有java,有c++,有pythonA:支持,使用功能自动化,可以支持接入自定义执行机,支持任意脚本语言。Q:如何确保自动化测试脚本的稳定性和可维护性?A:可以通过三层用例管理,以版本的方式管理用例和对应的脚本。Q:CodeArts TestPlan支持移动端应用测试吗?目前适配哪些类型终端?A:CodeArts TestPlan的功能自动化能力,没有内置具体的移动端测试框架,可以支持接入自定义执行机(和测试框架),提供对应的移动端功能测试。Q:华为云PaaS测试计划(CodeArts TestPlan)的测试计划包含哪些内容?A:包含迭代、版本、周期、需求范围等内容。Q:做跨平台测试,不同操作系统和浏览器间可以支持吗?A:支持,可以使用功能自动化测试,支持接入自定义执行机(和测试框架),不限制具体的操作系统和浏览器。Q:自动化测试的脚本,可以和现有的融合吗?比如pytest的或者其他的A:支持,可以使用功能自动化测试,支持接入自定义执行机(和测试框架),不限制具体脚本语言。Q:如何确保不同团队(开发、测试、运维)在使用测试中台时的高效协作?A:测试计划(CodeArts TestPlan)是面向软件开发者提供的一站式云端测试平台,覆盖测试管理、接口测试,融入DevOps敏捷测试理念,帮助您高效管理测试活动,保障产品高质量交付。Q:测试用例可以做优先级排序吗?应该遵循什么原则排序?A:用例有不同的等级,分别用P0、P1…表示。0级:最基本的功能验证,用例不宜过多,各模块尽量保证在10-20个,占比5%左右1级:基本功能验证,可用于继承特性的基本功能验证、迭代验收前的基本功能验证等,占比20%左右2级:重要特性验证,可用于测试版本(非回归版本)中手工测试,占比60%左右3级:一般功能/非重要功能验证,包括对基本/重要功能的异常测试,占比10%~15%左右4级:非常特殊输入、场景、阈值条件的用例,该级别用例不宜过多,占比0%~5%左右Q:CodeArts TestPlan支持自定义测试模板吗?A:暂不支持将已有用例固化为模板。在测试用例页面的右上角,有一个测试资产中心,内置了大量优秀的测试用例作为范本。Q:华为云PaaS测试计划(CodeArts TestPlan)的测试设计有哪些方式?A:支持按特性或需求创建,也可以使用空白脑图,或基于模板创建。Q:在DevOps环境下,如何实现测试活动的自动化和持续集成?A:可以通过在流水线中配置测试验证的插件来实现。部署完成,就可以执行一遍对应的自动化测试。Q:在性能测试方面,该平台提供哪些实时监控和分析工具,以帮助开发者快速识别和解决性能瓶颈?A:支持AOM和APM,可以帮助分析cpu、内存等指标,以及调用链指标,配合压测指标曲线快速识别问题。Q:华为云PaaS测试计划(CodeArts TestPlan)产品都有哪些特性?A:测试计划(CodeArts TestPlan)是一款自主研发的一站式测试管理平台,沉淀了华为多年高质量的软件测试工程方法与实践,覆盖测试计划、测试设计、测试用例、测试执行和测试评估等全流程,旨在帮助企业协同、高效、可信的开展测试活动,保障产品高质量上市。Q:测试数据怎么做隔离和清理?A:可以通过全局变量中的环境进行隔离,同时,可以在测试的后置步骤中进行清理操作。Q:华为云PaaS测试计划(CodeArts TestPlan)产品使用流程包括哪些?A:通用的流程包括测试计划、测试设计、测试用例、测试执行和测试评估。Q:在编写接口测试自动化脚本过程中,前后步骤如何传递变量?A:可以使用局部变量,或动态全局变量。Q:CodeArts TestPlan在软件交付方面有哪些优势?A:大规模高效协同测试、启发式测试设计、测试验证双向可追溯、自动化测试、可视化设计与度量等。Q:支持压力测试和负载测试?A:可以使用CodeArts PerfTest进行压测和负载测试,服务提供了丰富的压测模型,灵活的用例脚本,可以满足多种压测场景的使用。Q:该服务支持哪些编程语言?A:支持,可以使用功能自动化测试,支持接入自定义执行机(和测试框架),不限制具体脚本语言。Q:使用CodeArts TestPlan时,如何处理接口自动化脚本文件过大问题?A:首先,需要建立好用例规范,每个用例建议不要包含过多的用例步骤;其次,常用的脚本可以封装为关键字、组合关键字。Q:华为云PaaS 测试计划(CodeArts TestPlan)服务适用于哪些应用场景?A:如:软件测试和质量管理场景、持续自动化测试场景等。想要了解更多华为云PaaS 测试计划(CodeArts TestPlan)服务相关知识,欢迎观看DTSE Tech Talk 系列技术直播
推荐直播
-
用码道,让你的AI作品三步上朋友圈2026/08/04 周二 19:00-20:00
林华鼎-华为云AI开发者运营负责人
从入门 · 到做AI应用 · 到企业级开发。不教编程,只教用AI · 零代码、有产出、能带走、可炫耀 · 每课人人动手实操
回顾中 -
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
基于华为云码道,构建你的定制化AI搭子2026/08/14 周五 09:00-11:30
明亮-华为云开发者发展与支持部部长
本期直播将向您全面介绍华为云码道产品,并基于码道手把手教你部署自己的定制化AI陪伴搭子。
回顾中
热门标签