1. 项目背景与测试目标
作为一名长期从事AI系统开发的工程师,我深知功能测试对于智能体(Agent)系统的重要性。这次分享的打卡/日报Agent v1.0测试用例设计,源于我们团队在实际开发过程中遇到的真实需求——如何确保一个看似简单的打卡Agent在各种复杂场景下都能稳定运行。
这个Agent的核心功能看似简单:记录工作碎片、查询状态、处理打卡请求。但在实际使用中,用户输入千变万化,系统状态组合多样,如何保证Agent不会"自作主张"地越权操作,或者被用户的情绪化表达带偏,就成了测试设计的重点。
提示:在AI系统测试中,行为可控性比功能完整性更重要。一个不会"帮忙"的Agent,远比一个乱帮忙的Agent更可靠。
2. 测试框架设计思路
2.1 输入分类体系
我们首先建立了严格的输入分类标准,这是所有测试用例的设计基础:
| 输入类型 | 典型特征 | 允许行为 | 风险点 |
|---|---|---|---|
| 事实输入 | 描述已完成工作 | 记录碎片 | 误解析为指令 |
| 查询输入 | 包含"吗""是不是"等疑问词 | 只读查询 | 误触发写操作 |
| 打卡确认 | 明确包含"打卡"关键词 | 修改打卡状态 | 时间推断错误 |
| 情绪/混合输入 | 感叹词、模糊表达 | 兜底回应 | 误记录为事实 |
| 越界指令 | 包含"写""帮"等动词 | 明确拒绝 | 部分执行风险 |
这种分类不是基于语义分析,而是从用户意图和行为边界角度划分,更符合实际测试需求。
2.2 系统状态定义
我们定义了5种基础系统状态(Fixture),覆盖了Agent可能遇到的主要场景:
- S0(空白状态):测试Agent的初始化行为
- S1(有当日碎片):验证增量记录能力
- S2(已上班打卡):检查状态依赖逻辑
- S3(完成全天打卡):测试终态处理
- S4(跨天历史数据):验证时间边界
这种状态设计确保了每个测试用例都有明确的上下文环境,避免了"理想条件下能运行,实际场景出问题"的常见陷阱。
3. 核心测试用例详解
3.1 事实记录类用例设计
3.1.1 单条事实记录(TC-F-01)
这是最基础的测试场景,但有几个关键验证点:
- 输入中的时间描述(如"今天")是否会被错误解析为打卡指令
- 专业术语(如"WMS 0.8")是否能完整保留
- 是否触发不必要的自然语言处理(如自动总结)
我们在实现时特别处理了这类边界情况:
python复制def record_fragment(text):
# 禁用所有NLP处理,纯文本存储
if contains_clock_keywords(text): # 防止误判为打卡
return error_response()
return save_raw_text(text) # 原样存储
3.1.2 多任务事实输入(TC-F-02)
这个用例验证了一个常见场景:用户用一句话描述多个任务。我们的处理原则是:
- 不自动拆分任务(避免误解析)
- 不提取关键词(保持原始信息)
- 整句存储为一条记录
这看起来"不智能",但实际避免了90%的误解析问题。
3.1.3 增量记录测试(TC-F-03)
验证在有历史碎片的情况下:
- 新记录是否正常添加
- 是否错误关联历史内容
- 存储时序是否正确
我们通过独立的存储ID和严格的时间戳管理确保记录的隔离性。
3.2 查询类用例设计
3.2.1 工作内容查询(TC-Q-01)
这个用例的关键是防止"查询变生成"的常见问题。我们特别检查:
- 是否触发日报生成(即使碎片很少)
- 返回内容是否包含原始文本(未经加工)
- 响应中是否包含建议或补充内容(应避免)
实现上采用了严格的只读模式:
python复制def get_fragments_by_date(date):
results = query_db(date) # 简单查询
return format_as_list(results) # 不添加任何分析
3.2.2 打卡状态查询(TC-Q-02)
验证当用户查询打卡状态时:
- 是否自动补打卡(常见错误行为)
- 返回信息是否准确反映系统状态
- 是否包含额外建议(如"您是否要下班打卡")
我们通过状态机明确区分查询和操作:
mermaid复制stateDiagram
[*] --> 查询
查询 --> 返回状态: 仅读取
查询 --> 操作: 严格隔离
3.3 打卡操作类用例
3.3.1 明确打卡指令(TC-C-01)
这类用例验证:
- 指令解析是否准确(上班/下班)
- 时间记录方式(不自动推断)
- 状态转换是否正确
我们采用显式确认机制:
code复制用户:准备下班了,打卡
Agent:已记录下班打卡(18:05)
(不自动补充"工作了8小时"等信息)
3.3.2 模糊打卡请求(TC-C-02)
针对不完整的打卡指令:
- 是否要求明确补充
- 是否默认执行某个操作
- 错误提示是否清晰
这是安全性的关键测试点,我们实现了严格的指令校验:
python复制def process_clock_request(text):
if not has_clear_clock_type(text):
return "请明确说明是上班还是下班打卡"
# 后续处理...
4. 边界与异常用例设计
4.1 情绪化输入处理
4.1.1 负面情绪表达(TC-E-01)
验证当用户输入包含情绪时:
- 是否错误提取"事实"部分
- 是否触发任何写操作
- 响应是否简短中立
我们采用情绪词过滤机制:
python复制emotional_words = ["烦死了","好累","无语"]
if contains_any(text, emotional_words):
return simple_response("收到") # 不存储不处理
4.2 越界指令拒绝
对于明显超出范围的请求(如TC-R-01写日报):
- 拒绝是否明确(不是委婉拒绝)
- 是否触发任何后台操作
- 响应是否包含可乘之机(如"虽然不能...但是...")
我们采用白名单机制:
python复制ALLOWED_ACTIONS = ["record_fragment", "query_status"...]
if action not in ALLOWED_ACTIONS:
return "抱歉,我无法执行此操作"
5. 测试执行与持续集成
5.1 用例执行策略
我们将这些用例分为三组执行:
- 冒烟测试组(TC-F-01,TC-Q-01等基础功能)
- 边界测试组(TC-E系列和TC-R系列)
- 回归测试组(全部用例)
每组有不同的通过标准和执行频率。
5.2 自动化实现要点
在自动化这些用例时,有几个关键实践:
- 状态隔离:每个用例执行前重置测试环境
- 输入多样化:核心用例增加10种同义表达
- 结果验证:不仅检查输出,还检查系统状态变化
示例自动化脚本结构:
python复制def test_tc_f_01():
reset_state(S0)
input = "今天写WMS 0.8版本用例"
response = agent.process(input)
assert response.action == "record_fragment"
assert db.count() == 1
assert "总结" not in response.text
6. 经验总结与延伸思考
在实际测试过程中,我们发现了几个值得注意的现象:
-
时间敏感性问题:早上9点输入的"打卡"通常指上班,下午6点则指下班,但我们在v1.0中刻意不做这种推断,因为早期版本宁可"笨"也不要"错"。
-
专业术语处理:像"WMS 0.8"这类专业术语,如果做实体识别很容易出错,所以我们选择原样存储,牺牲"智能"换取准确性。
-
情绪表达误判:像"今天好像没干啥呢"这种模糊表达,早期版本会错误触发记录操作,后来通过增加情绪词库解决了这个问题。
这套测试用例目前已经稳定运行了3个月,拦截了超过20个潜在问题。它的价值不在于覆盖面有多广,而在于建立了一个可靠的行为基线——任何新功能的增加,都必须先通过这些基础用例的考验。