从硬编码到自然语言:用"Harness Engineering"重构自动化测试
——以 Agent 系统为例
摘要
当被测系统是一个业务流程密集的软件系统(例如我们实践中的可配置 AI Agent 平台)时,传统的自动化测试方法很容易碰到天花板——API 组合爆炸、业务理解断层、维护成本陡增。本文记录了我们从 pytest 硬编码封装,到构建 CLI SDK 与 Skill 体系,最终实现"用自然语言描述测试场景即执行"的完整探索过程。虽然文章以 Agent 系统作为示例,但这一套方法本质上适用于绝大多数暴露 API 的软件产品——无论是电商、金融、SaaS 还是内部工具平台。核心思路是从"喂代码给 AI 生成测试"的 Context Engineering,转向"给 AI 套上一副马具"的 Harness Engineering,在充分发挥 AI 决策能力的同时,约束其对业务系统的理解方式,让测试真正回归"用自然语言描述行为和预期"的初始形态。
1. 背景:当业务系统的复杂度超出传统自动化测试的承载能力
为了具体阐述,我们以一个曾深度测试过的系统为例:一个面向用户的 Agent 构建平台。用户可以在 Web 页面完成以下操作:
· 创建 Agent,为其命名;
· 选择并配置底层的大语言模型;
· 绑定知识来源(文档库、数据库等);
· 配置技能(插件、工具调用、工作流);
· 保存 Agent 并将其发布给指定用户;
· 以某一用户的身份对该 Agent 发起问答,并获得带技能调用记录、引用来源的结构化响应。
系统中的每一步操作都有独立的RESTful API 提供支持。创建 Agent 是一个 API,绑定知识来源是另一个 API,发起问答则又是一个需要带上会话上下文的API。这类业务流程密集、接口编排复杂的特征,在电商下单、贷款审批、SaaS 多租户配置等场景中同样普遍存在。因此,这个 Agent 平台所遇到的测试难题,实际上代表了一大类软件系统的共同痛点。
起初,我们的自动化测试策略非常"标准":在 pytest 框架中为每个 API 编写封装函数,然后在测试用例中按顺序调用这些函数,硬编码参数、捕获响应、提取字段、执行断言。例如,一个"端到端问答"的测试大概是这样的:
def test_agent_qa_with_skill():
# 1. 创建 agent
agent_id = create_agent(name="test_agent", model="gpt-4o")
# 2. 绑定知识库
bind_knowledge(agent_id, kb_id="kb_123")
# 3. 配置技能
add_skill(agent_id, skill_name="weather_search")
# 4. 保存并发布
publish(agent_id, user="user_001")
# 5. 以用户身份提问
resp = chat(agent_id, user="user_001", question="今天上海天气如何?")
# 6. 断言技能调用记录及语义
assert "weather_search" in resp["skill_calls"]
assert resp["ttft"] < 2.0
这种写法在业务逻辑简单时尚可应付,但当系统功能迅速膨胀时,问题接踵而来:
维护成本极高:一个业务变更可能导致数十个测试函数的硬编码参数需要修改。
表达能力受限:测试人员的大多数精力花在编排 API 调用顺序和数据传递上,而不是描述"这个功能该如何表现"。
非开发人员被拒之门外:产品经理或 QA 脑子里有清晰的行为预期,却无法绕过 Python 代码直接参与自动化测试设计。
我们需要一种新的方式,让测试的编写和执行更接近于人类描述软件行为的方式——用自然语言表达用例,且这个用例能被可靠地执行。而且,这种方式不应只适用于 Agent 平台,而应能推广到任何可以通过 API 驱动的业务系统。
2. 灵感:LLM as a Judge 与 AI 驱动的交互式工具
在 LLM as a Judge 模式的启发下,我们开始尝试用大模型评判测试结果的语义一致性,而不是死板地匹配关键词。同时,像Claude Code、OpenCLAW 这类 AI 交互工具的流行,让我们看到了另一种可能性:如果开发者可以通过自然语言让 AI 操作终端、读写文件、调用外部工具,那么我们是否也能让 AI 直接操作我们的业务系统?
一个朴素的想法是:把整个业务系统的接口文档、使用示例甚至源码全部喂给大模型,然后要求它根据自然语言描述生成测试代码并执行。这就是典型的 Context Engineering——通过提供尽可能完整的上下文,期望 AI 能自主理解业务、编排 API 并产出正确的测试。
但现实很快给了我们教训:
幻觉严重:大模型会"发明"不存在的 API 端点、错误的参数组合,甚至臆造业务规则。
API 协调失控:创建资源、配置属性、触发动作这些操作之间存在严格的顺序依赖和状态传递(例如必须先用上一步返回的ID 进行下一步)。完全让 AI 自主决策,生成的调用序列经常因顺序错乱而失败,试错成本高昂。
反馈循环长:当测试本身依赖 AI 生成的代码时,失败后很难判断是业务缺陷还是生成的代码有问题。
于是我们意识到,不能放任 AI 在黑暗里摸索。我们需要给它一张"地图"和一套"标准动作",让它既能在限定范围内发挥决策能力,又不会偏离业务的正确路径。这张"地图"不应该只为某一类系统定制,而应成为一种可复用的模式。
3. 从 Context Engineering 到 Harness Engineering
我们提出的方案是 Harness Engineering(马具工程)。这个词的意象很直白:给 AI 套上一副马具,让它有方向地奔跑,而不是在旷野中盲目冲撞。
实现 Harness 的核心步骤有两层,这两层对任何有 API 的系统都成立:
第一层:对业务 API 进行 CLI SDK 封装
我们将系统中所有核心 API 封装成一个命令行接口(CLI),每个业务操作就是一个带有明确参数和输出的命令。例如在我们的 Agent 平台中:
agent-cli create --name "test_agent" --model "gpt-4o"
agent-cli bind-knowledge --agent-id <id> --kb-id "kb_123"
agent-cli add-skill --agent-id <id> --skill "weather_search"
agent-cli publish --agent-id <id> --user "user_001"
agent-cli chat --agent-id <id> --user "user_001" --question "今天上海天气如何?"
CLI 层强制规定了正确的调用方式,参数校验在命令执行前就已经完成。任何不符合规范的调用都会立即得到明确的错误提示,而不是等到几个步骤后才报错。
第二层:基于 CLI SDK 开发 Skills(技能)
有了 CLI 命令,我们进一步将其抽象为 AI 可调用的 Skills。在 Claude Code 或 OpenCLAW 这类环境中,Skill 就是一个带有详细描述、参数定义和调用示例的"工具"。例如,一个 create_agent 的 Skill 定义包含:
描述:在平台中创建一个新的 Agent,需要指定名称、模型等。
参数:name (string), model (string), knowledge_bases (list), skills (list)...
调用方式:背后实际执行 agent-cli create ... 命令。
这就是 Harness Engineering 的核心价值:将"理解业务如何运转"的责任从 AI 转移到事先设计好的工具和约束上,同时保留AI 在任务规划和语义判断上的优势。AI 不再需要从零推导业务流程,而是在一个安全的沙盒里,利用标准化的动作完成目标。无论被测系统是Agent 平台、交易系统还是审批流程,只要其行为可以通过API 驱动,就能套用这副"马具"。
4. 实践落地:用自然语言直接完成集成测试
经过上述改造,我们的测试执行变成了下面这样的流程:
1. 测试人员(或产品经理、开发者)打开 Claude Code,在提示词中用自然语言描述测试场景。
2. AI 根据描述,调用我们预置的业务Skills,按正确顺序操作系统。
3. 每一步的响应被 AI 解析;最终结果通过 LLM as a Judge 进行语义断言。
一个真实的测试指令可能是这样的:
帮我创建一个名叫"天气助手"的 Agent,使用系统已有的 gpt-4o 模型,配置 weather_search 技能和 travel_kb 知识库,保存后发布给用户 lily。然后用 lily 的身份向这个 Agent 提问:"明天去杭州需要带伞吗?" 预期的答案是:答案中必须能够看到 weather_search 技能的调用记录,最终回复的语义应当表明明天杭州是否下雨,并且 TTFT 不得高于 2 秒。
AI 会自行规划任务序列,调用 create_agent、bind_knowledge、add_skill、publish、chat 等 Skill,收集最终返回结果,然后基于我们预设的Judge 规则(语义相似度、关键字包含、TTFT 指标)给出通过与否的结论。整个过程不再需要一行 hard code 的测试函数。
对于其他系统,场景同样自然。例如:
在订单系统中,帮我用用户张三的账号创建一个包含商品 A 和商品 B 的订单,使用优惠码"SUMMER",预期订单总价应该是原价的 8 折,且订单状态为"待支付"。
只要将订单系统的 API 封装为对应的 order-cli 命令和 Skills,AI 就能以完全相同的方式执行这种测试。我们所做的就是:
用 CLI 封装了全部 RESTful API(Harness 的第一层);
将 CLI 命令映射为 AI 环境中的 Skill(Harness 的第二层);
为 AI 配备了一个 Judge Skill,用于结果断言;
让人类用自然语言定义输入和预期。
测试的形态发生了根本性变化:测试用例本身回归到了自然语言描述的行为与预期,而执行和断言则交给了受约束的 AI。
5. 为什么这条路径可行:自动化测试的演进回顾
回顾自动化测试所走过的路,可以发现一条清晰的演进脉络:
过去:自然语言描述的测试用例,指导测试人员手动操作;
现在:自然语言用例指导测试人员写出自动化脚本(pytest、Selenium 等),然后通过 CI 流水线定时执行;
不久的过去:尝试将用例和代码一起塞给 AI,让它生成测试脚本并执行,但幻觉和协调成本高;
未来(我们正在做的事):测试人员将业务能力封装为 Skills(Harness),然后直接用自然语言编写测试用例,让Agent 执行它们。
最后这个阶段,其实是让测试回归到它的初始状态:用自然语言描述软件该做什么,以及我们期望看到什么。不同是的,现在有一个受过"马具训练"的 AI Agent 可以忠实地去完成这个验证工作,而不是必须由人去充当翻译官,把自然语言转写为代码。
6. 收益与反思
这套测试体系在我们的 Agent 平台及其他业务系统上落地后,收获了明显的收益:
测试编写效率大幅提升:用自然语言描述一个端到端场景,比编写对应的 Python 代码快 5 倍以上。
维护成本骤降:当业务 API 发生变更时,只需修改对应 CLI 命令和 Skill 的实现,所有自然语言测试用例无需任何改动即可继续运行。
团队协作边界消融:产品经理可以将验收标准直接以自然语言形式写入测试集合,它们会在每次发布前被 AI 自动执行,真正做到"活文档"。
测试即文档:自然语言用例本身就高度可读,省去了额外维护测试说明的负担。
当然,挑战同样存在。Harness Engineering 要求初期投入精力设计合理的 CLI 接口和 Skill 抽象,这需要测试开发人员对业务有深度的理解,但这一投入是一次性的,且可以跨项目复用模式。另外,LLM as a Judge 在某些模糊场景下仍可能产生评判偏差,需要持续校准 Judge 的提示词和标准。
7. 结语:让测试回归本原
我们这次探索最深刻的体会是:让 AI 成功落地的关键,往往不是给它更多的自由,而是赋予它恰到好处的约束。通过构建"马具"——CLI SDK 和 Skill 体系,我们让 AI 在理解业务系统时既有方向又有边界。这一方法并不局限于 Agent 系统,它在订单、审批、配置、数据流等各类软件场景中同样成立。最终,测试行为回归到了人类交流的原始形式:用句子描述你希望系统做什么,以及你如何判定它做对了。
当有一天,任何一个团队成员都能在聊天框中输入一段自然语言,便能驱动一个覆盖全业务流程的自动化测试运行时,我们才真正可以说:测试没有阻碍交付,它本身就是交付的一部分。
这篇文章凝聚了我们在多个业务系统上应用 AI 测试的实践经验,尤其感谢"Context Engineering 到 Harness Engineering"这一思想跃迁带来的启发。如果你也在任何具有 API 的产品上探索 AI 驱动的测试,欢迎交流。