从接口压测到全链路质量保障:AI智能客服系统的软件测试实践

一、AI智能客服系统 的测试挑战

智能客服系统远不止普通的 CRUD 业务系统,它是一个典型的 长连接 + 高并发 + AI 推理链路 + 分布式路由 复合体。任何一个环节异常,用户侧感知往往就是"卡死""丢消息""答非所问"。

对测试工程师而言,这套系统存在四个核心难点:

难点 典型表现 对测试的冲击
多渠道接入 WebSocket 主通道 + 第三方 IM SDK 回调 同一套消息逻辑要走完全不同的接入路径
多实例部署 服务水平扩容,连接分散在不同节点 消息可能发到 A 实例,但用户连在 B 实例,需要跨实例路由
AI 链路复杂 敏感词 → 意图识别 → 知识检索 → 大模型生成 每一步都有外部依赖,失败模式呈指数级增长
响应式全栈 WebFlux + Reactor + R2DBC 传统断言方式失效,ThreadLocal 不可用,调试门槛陡增

一句话概括这类系统的测试本质 :在异步、非阻塞、分布式的条件下,保证消息 不丢、不重、不乱序、不超时


二、被测系统技术特征概览

2.1 核心技术栈(通用化)

技术选型 说明
JDK Java 21 LTS,虚拟线程就绪
Web 框架 Spring Boot 3.x + Spring WebFlux 响应式编程模型
服务治理 Spring Cloud + Nacos 注册中心 / 配置中心
RPC Dubbo + Triple(基于 gRPC) 服务间高效通信
数据库 PostgreSQL / MySQL R2DBC 非阻塞访问
缓存 / 消息 Redis(Hash / Stream / 分布式锁) ReactiveRedisTemplate
向量检索 云厂商向量数据库 用于 RAG 知识检索
大模型 主流 LLM 云服务 支持流式输出
认证鉴权 响应式 Sa-Token Reactor Context 传递登录态
对象存储 云厂商 OSS 文件上传 / 下载
调度 XXL-Job 分布式定时任务
监控 Micrometer + Prometheus + 分布式追踪 指标采集与链路追踪
压测工具 自研 Python WebSocket 压测客户端 200 并发 / 200 QPS

2.2 测试关注点映射

模块 核心职责 测试重点 风险等级
会话模块 WebSocket 接入、消息分发、跨实例路由 连接管理、消息可靠性、心跳超时、多端路由 🔴 P0
AI 编排 意图识别、RAG、回复生成、转人工决策 三级降级链路、节点分支断言、策略切换 🔴 P0
LLM 服务 大模型对话 流式输出完整性、取消语义、超时降级 🟡 P1
RAG 模块 向量检索 置信度阈值、多分区并发、命中 / 未命中 🟡 P1
网关 统一入口、限流、鉴权 路由规则、限流阈值、Token 透传 🟡 P1
权限中心 认证、RBAC、部门数据隔离 登录态、权限拦截、上下文透传 🟡 P1
运营后台 前端管理页面 前后端接口契约、权限渲染 🟢 P2

三、IM 核心全链路流程(测试视角)

3.1 消息从发送到接收的完整链路

复制代码
用户浏览器 / APP
    │  WebSocket 连接(wss://域名/ws/chat)
    ▼
WebSocket 接入层
    │  接收 TEXT 帧 → 解析为内部消息对象
    ▼
消息分发器(Dispatcher)
    │  按 userType + messageType 路由到对应处理器
    ▼
用户消息处理器(UserMessageProcessor)
    │
    ├─ 1. 敏感词检测
    │       └─ 命中 → 替换为 ***
    │
    ├─ 2. AI 回复策略选择
    │       ├─ 热门话术缓存命中 → 直接返回
    │       ├─ 本地缓存命中 → 直接返回
    │       └─ 未命中 → 调用大模型
    │
    ├─ 3. AI 编排工作流执行
    │       │
    │       ▼
    │   StateGraph 状态图驱动
    │       ├─ 意图识别节点
    │       ├─ 知识库检索节点(RAG)
    │       ├─ 回复生成节点
    │       ├─ 转人工节点
    │       └─ 兜底回复节点
    │
    ├─ 4. 大模型服务调用
    │       │
    │       ▼
    │   流式返回 Flux<String>
    │       逐 token 推送到前端
    │
    ├─ 5. 消息持久化
    │       │
    │       ▼
    │   R2DBC → PostgreSQL / MySQL
    │
    └─ 6. 消息推送路由
            │
            ├─ 本地连接 → 直接内存推送
            ├─ Redis 连接缓存命中 → 写入消息通道
            └─ 跨节点 → Redis Stream 广播
                    │
                    ▼
            WebSocketSession.send() → 客户端接收

3.2 必须覆盖的 12 条分支清单

编号 分支场景 测试类型 预期结果
1 匿名连接发送消息 功能 不走分布式路由,Handler 直发,返回提示登录
2 已登录连接发送消息 功能 走完整业务编排链路
3 本机连接消息发送 功能 直接内存推送,不写 Redis
4 跨实例连接消息发送 集成 写入 Redis Stream,目标实例消费并推送
5 单端在线 功能 正常收发
6 多端同时在线(PC + 微信) 功能 消息推送到所有在线端,不漏不重
7 热门话术命中 功能 返回热门话术列表,不调 AI
8 本地缓存命中 功能 返回缓存回复,不调 AI
9 大模型策略触发 集成 调 AI 服务链路,流式返回
10 敏感词命中 功能 消息脱敏替换,原始内容仍持久化
11 排队转人工 功能 分布式锁互斥,仅一个请求能进入转人工
12 AI 超时降级 容灾 返回兜底话术,不阻塞用户

四、测试方案分层设计(七层模型)

层级 工具 / 手段 覆盖范围 示例
L1 单元测试 JUnit 5 + Mockito + Reactor StepVerifier 单个类 / 方法的分支覆盖 AI 策略三分支逻辑
L2 切片测试 @WebFluxTest / @DataR2dbcTest Web 层路由、R2DBC Repository WebSocket 握手、消息持久化
L3 集成测试 Testcontainers(PG / Redis / 注册中心)+ Dubbo 本地调用 跨模块端到端编排 会话 → AI 编排 → LLM 完整链路
L4 接口测试 Postman / Newman / Shell 脚本 REST 接口契约 登录、会话 CRUD、知识库管理
L5 WS 协议测试 自研 Python WebSocket 客户端 长连接入参 / 出参 / 时序 握手鉴权、消息推送、心跳
L6 性能压测 自研 Python 压测工具 200 并发 / 200 QPS RT / P95 / P99 / 可用率
L7 容灾测试 手动 / 自动化故障注入 跨实例消息可达性 实例宕机、Redis 抖动、网络隔离

五、重点一:WebSocket 自动化测试设计

5.1 测试设计思路

WebSocket 测试的核心难点在于:它是异步双向流,不是请求-响应模型。需要同时验证:

  • 服务端能正确接收客户端消息
  • 服务端能主动推送消息到客户端
  • 心跳机制正常工作
  • 断连后资源正确释放

5.2 响应式流断言(StepVerifier)

测试目标:验证 WebSocket 文本消息被正确处理并分发。

文字描述测试方案

  1. 准备阶段 :构造一条模拟的 TEXT 帧,内容为 JSON 格式的消息体(如 {"type":"***","content":"你好"}
  2. 执行阶段 :将该消息传入 Flux.just() 模拟客户端输入流
  3. 断言阶段 :使用 StepVerifier 验证输出流:
    • 第一条消息应为 AI 回复类型
    • 消息内容包含预期的回复文本
    • 流在超时时间内正常完成

关键代码片段思路

java 复制代码
// 1. 构造输入 Flux
Flux<WebSocketMessage> input = Flux.just(mockTextMessage(jsonPayload));

// 2. 调用被测 Handler 获取输出 Flux
Flux<WebSocketMessage> output = handler.handle(input);

// 3. StepVerifier 断言
StepVerifier.create(output)
    .assertNext(msg -> {
        String payload = msg.getPayloadAsText();
        assertTrue(payload.contains("AI_REPLY"));
    })
    .expectComplete()
    .verify(Duration.ofSeconds(5));

5.3 心跳与断连测试

测试目标:验证 PING/PONG 心跳机制与超时断连。

测试方案

  1. 模拟客户端连接后不发送任何 PING 帧
  2. 验证服务端在配置的心跳超时时间后主动关闭连接
  3. 验证 Redis 中的连接记录被正确清理

关键断言逻辑

  • 使用 expectNoEvent(Duration) 验证在心跳间隔内无服务端主动消息
  • 超过超时时间后,连接状态变更为 CLOSED
  • Redis 中 key:{value} 对应的 Hash 记录被删除

5.4 多端消息同步测试

测试目标:验证同一用户多端在线时,消息推送到所有终端。

测试方案

  1. 模拟同一用户 ID 建立两个 WebSocket 连接(PC 端 + 移动端)
  2. 向该用户发送一条消息
  3. 验证两个连接都收到推送
  4. 验证消息内容一致、不重复

六、重点二:AI 编排工作流测试

6.1 意图三级降级链路

意图识别是 AI 客服的核心决策点,采用 三级降级 策略:

复制代码
Level 1: 向量匹配(向量数据库语义检索)
    ├─ 命中(score ≥ 阈值) → 直接使用向量匹配结果
    └─ 未命中 ↓

Level 2: 小模型意图分类
    ├─ 置信度 ≥ 阈值 → 使用小模型结果
    └─ 置信度 < 阈值 ↓

Level 3: 大模型兜底(LLM 直接判断)
    └─ 最终结果
测试设计思路

采用 数据驱动 + Mock 控制 的方式,固定 expectedIntent,通过 Mock 控制每一级的输入和输出。

Level 1 向量命中场景

测试步骤

  1. Mock 向量数据库客户端,使其返回高置信度结果(如 score=0.92,阈值=0.85)
  2. 构造输入文本:"我的余额是多少"
  3. 执行意图识别节点
  4. 断言
    • 返回的意图为 query_balance
    • 意图来源标记为 VECTOR_MATCH
    • 不再调用 Level 2 和 Level 3
Level 2 小模型命中场景

测试步骤

  1. Mock 向量数据库返回空(未命中)
  2. Mock 小模型返回中等置信度结果(如 confidence=0.78,阈值=0.7)
  3. 构造输入文本:"我要转账"
  4. 执行意图识别节点
  5. 断言
    • 返回的意图为 transfer_money
    • 意图来源标记为 SMALL_MODEL
    • 不再调用 Level 3
Level 3 大模型兜底场景

测试步骤

  1. Mock 向量数据库返回空
  2. Mock 小模型返回低置信度结果(如 confidence=0.35,阈值=0.7)
  3. Mock 大模型返回 JSON 格式意图判断:{"intent": "complaint", "reason": "服务态度"}
  4. 构造输入文本:"你们什么破服务"
  5. 执行意图识别节点
  6. 断言
    • 返回的意图为 complaint
    • 意图来源标记为 LLM_FALLBACK

6.2 RAG 命中 / 未命中分支测试

RAG 命中场景

测试步骤

  1. Mock 向量数据库返回高分知识片段(score=0.88,阈值=0.75)
    • 内容:"7天无理由退货"
    • 内容:"退货需保留包装"
  2. 构造输入文本:"怎么退货"
  3. 执行 RAG 节点
  4. 断言
    • 上下文中包含知识库内容
    • isKnowledgeHit 标记为 true
    • 后续进入知识增强回复节点
RAG 未命中场景

测试步骤

  1. Mock 向量数据库返回低分结果(score=0.21)
  2. 构造输入文本:"我想去火星旅游"
  3. 执行 RAG 节点
  4. 断言
    • isKnowledgeHit 标记为 false
    • shouldCallLLM 标记为 true
    • 后续进入大模型直答节点

6.3 转人工决策测试

测试目标:投诉类语句触发转人工流程。

测试步骤

  1. 准备启用转人工的 Bot 配置,设置投诉关键词列表(如"投诉""差评""垃圾")
  2. 构造输入文本:"我要投诉你们的服务态度"
  3. 执行转人工节点
  4. 断言
    • 节点状态变更为 TRANSFER_TO_HUMAN
    • 事件列表中包含 TransferToHumanEvent
    • 转人工服务层的 initiateTransfer 方法被调用一次

七、重点三:跨实例消息路由测试

7.1 跨实例路由架构

复制代码
用户 A 连接 → 实例 A(WebSocket 接入)
                    │
                    │ 消息目标用户在实例 B
                    ▼
           消息分发器判断路由
                    │
          ┌─────────┴─────────┐
          │ 目标在本地         │ 目标在远端
          │ → 直接推送         │ → 写 Redis Stream
          └───────────────────┘              │
                                             ▼
                              实例 B 消费 Stream → 推送用户 B

7.2 Redis Hash 连接存储测试

Redis Key 结构设计

复制代码
Key:   im:user:channels:{userId}
Type:  Hash
Field: {channelType}:{deviceId}
Value: JSON {instanceId, sessionId, channelType, deviceId}
TTL:   24h(自动过期,防止僵尸连接)

测试用例设计

步骤 操作 预期结果
1 注册连接(userId, instanceId, sessionId, channelType, deviceId) 返回 true
2 查找连接(userId) 返回完整的 ChannelInfo 对象
3 验证 TTL 接近 24h(允许误差)
4 注销连接(userId, field) 返回 true
5 再次查找 返回空

八、性能与稳定性测试

8.1 压测方案设计

采用自研 Python WebSocket 压测工具,支持以下模式:

模式 并发 QPS 持续时间 目的
标准压测 200 200 10 分钟 验证常规负载能力
长稳压测 50 50 24 小时 验证内存泄漏 / 连接稳定性
降级专项 100 100 5 分钟 模拟 AI 超时,验证降级话术
峰值冲击 500 500 1 分钟 验证限流与熔断策略

压测核心指标

  • 连接建立成功率:目标 ≥ 99.9%
  • 消息往返延迟(RT):P95 < 500ms
  • P99 延迟:< 2s
  • 错误率:< 0.1%
  • 流式首字延迟(TTFT):< 1.5s

8.2 监控指标与告警阈值

指标 采集路径 告警阈值 说明
接口 QPS /actuator/prometheus P95 > 500ms 告警 IM 接口响应时间
AI 接口 RT Prometheus 指标 P95 > 3s 大模型调用延迟
WebSocket 在线数 自定义指标 抖动 > 20% 连接稳定性
Redis Stream 积压 XPENDING > 1000 条 消费能力不足
JVM GC 暂停 jvm_gc_pause_seconds 单次 > 200ms 内存压力
错误率 自定义错误计数 > 0.1% 整体健康度

8.3 分布式追踪排查

TraceId 串联排查流程

  1. 从 Prometheus / Grafana 发现异常请求,获取 TraceId

  2. 在各服务日志中按 TraceId 过滤,按时间排序

  3. 还原完整调用链:

    网关层 | 14:23:01.100 | 收到用户请求 | userId=10086
    会话层 | 14:23:01.105 | WebSocket 消息解析完成
    AI 编排层 | 14:23:01.110 | 意图识别开始 | input="查余额"
    向量检索 | 14:23:01.115 | 向量搜索完成 | topK=5
    AI 编排层 | 14:23:01.130 | 意图=query_balance | source=VECTOR
    LLM 服务 | 14:23:01.135 | 大模型流式开始 | model=qwen-plus
    会话层 | 14:23:01.280 | 推送 AI 回复 | chunks=15


九、典型故障排查指南

9.1 场景一:消息丢失

排查路径

复制代码
1. 确认消息是否到达 WebSocket 层
   → 查看 WebSocket Handler 日志,确认收到 TEXT 帧

2. 确认消息是否进入分发器
   → 查看 Dispatcher 路由日志

3. 确认路由结果
   → 查看路由日志中的标记:LOCAL / REDIS / STREAM

4. 如果走了 STREAM → 检查消费者组是否在线
   → XINFO GROUPS 消费者key

5. 如果消费者在线 → 检查 Pending 是否堆积
   → XPENDING  消费者key 消费组key

6. 如果 Pending 堆积 → 消费者处理阻塞
   → 检查消费者线程池 / 下游服务延迟

9.2 场景二:转人工死锁

排查路径

复制代码
1. 查看 Redis 中的锁状态
   → GET lock:transfer:human:{会话Id}
   → TTL lock:transfer:human:{会话Id}

2. 如果 TTL = -1(永不过期)
   → 看门狗续期逻辑异常,锁永远不会释放
   → 修复方向:unlock 放入 doFinally() 确保执行

3. 如果 TTL 正常倒计时但锁未释放
   → 业务异常导致 unlock 未执行
   → 修复方向:增加锁持有时间上限(强制过期)

4. 如果 Redis 主从切换
   → DEL 命令可能丢失
   → 修复方向:使用 Redlock 或增加重试机制

9.3 场景三:AI 回复超时

排查路径

复制代码
1. 确认超时发生在哪一层
   → 网关超时?AI 编排超时?LLM 调用超时?

2. 查看 LLM 服务日志
   → 是否收到请求?是否开始流式输出?

3. 查看 AI 编排状态机
   → 是否触发了降级节点?
   → 降级话术是否正确返回?

4. 验证超时配置
   → WebClient 超时 < AI 编排超时 < 网关超时
   → 确保每层都有超时保护,不会无限等待

10.2 测试经验

  1. 响应式代码必须用 StepVerifier :传统 assertEquals 无法断言异步流的行为,StepVerifier 是标配
  2. Redis Stream 是跨实例消息的命脉:Pending 监控、ACK 机制、Stream 修剪缺一不可
  3. AI 链路必须三级降级:向量 → 小模型 → 大模型,任何一级都要有超时和兜底
  4. 分布式锁必须有看门狗:没有自动续期的锁在生产环境一定会出问题
  5. 压测要分档位:短促高压验证极限,长稳低压验证泄漏,两者不可互相替代
  6. TraceId 是排查的核武器:从网关到 LLM 全链路串联,5 分钟定位问题根因

写在最后 :智能客服系统的测试,本质上是在 异步、分布式、AI 依赖 三重不确定性中,用工程化的手段建立确定性。分层测试是骨架,Mock 是肌肉,监控是可观测的神经------三者缺一不可。希望本文的实战经验能给正在做类似系统的测试同学一些参考。

相关推荐
火山引擎开发者社区12 小时前
TLS for DeepSeek Harness 可观测实践:从系统总览到会话复盘
人工智能
数字融合14 小时前
透明化地铁线视频孪生综合监控项目技术
大数据·人工智能·virtualenv
鼎艺创新科技15 小时前
不依赖 UE/Unity:我们如何从零搭建一套国产三维 GIS 渲染引擎
人工智能·算法·unity·游戏引擎·三维电子沙盘
十三画者15 小时前
【文献分享】ConfRetro:融合3D构象信息的逆合成预测Transformer框架
人工智能·深度学习·数据挖掘·数据分析·transformer·数据可视化
前沿在线15 小时前
百度文心助手推出任务引擎 2.0,日活用户同比增长 83%,日均对话轮次增长超 2 倍
人工智能·ai·大模型
zandy101115 小时前
AI办公工具选哪个?千问办公、百度搭子、WorkBuddy三款高阶智能体深度拆解
人工智能·ai办公工具
mengpp_12345615 小时前
AIoT平台 vs 普通IoT平台 核心区别
人工智能
canonical_entropy16 小时前
可逆不是逆向运行:DeepSeek Harness 架构的数学本质
人工智能·架构·agent
别动我齐刘海16 小时前
机器人运动控制学习2——基础进阶
c++·人工智能·神经网络·学习·目标检测·机器学习·机器人
冬奇Lab17 小时前
Code Agent 解剖(05):模型怎么知道有哪些工具可以用?Function Calling 如何实现?
人工智能·开源·agent