(rpc-integration-tests)= # 集成测试参考 面向**系统测试**与**集成开发**的完整 grpcurl 用例库:每条用例含**命令**、**预期响应**与**判定标准**。 前置:完成 [01 §1.1](01_connection_guide.md#11-环境配置) 环境变量导出。 ```bash export REPO=/path/to/autonomy export BRIDGE=127.0.0.1:5005 export PROTO_OPTS="-import-path $REPO -proto autonomy/bridge/proto/external_command_service.proto" export SVC=autonomy.bridge.proto.AutonomyService new_cmd_id() { echo "test-$(date +%s)-$RANDOM"; } # JSON 请求体:heredoc + -d @-(标准输入,无需转义) grpc_call() { grpcurl -plaintext $PROTO_OPTS -d @- "$BRIDGE" "$SVC/$1" } ``` --- ## 13.1 服务发现 服务发现与 schema 查看 → [01 §1.2](01_connection_guide.md#12-服务发现)(`list` / `describe` 命令与判定标准)。 --- ## 13.2 响应判读约定 用例含 **命令**、**预期响应** JSON 与 **判定**。Command Stream 以末帧 `ack.final=true` 结束。 | 类型 | 结束判据 | 成功判据 | |------|----------|----------| | Query / System Unary | 单条 JSON | 字段符合下表;System 看 `success=true` | | Command Stream | **`ack.final=true`** 末帧 | 末帧 `ack.success=true` | | Push Stream | 无末帧,Ctrl+C 结束 | 周期输出且字段非空 | **TaskType 整型**:导航=1 · 跟随=2 · 遥操=3 · 探索=4 · 对接=5 · 地图=6 **TaskStatus 整型**:IDLE=1 · RUNNING=2 · PAUSED=3 · SUCCEEDED=4 · FAILED=5 · CANCELED=6 每条 **TC-*** 用例采用与 Command 页相同的卡片:上方 **发送指令**(蓝)、下方 **收到结果**(绿);纯步骤/判定类用例仅展示绿色结果区。 --- ## 13.3 Query 测试用例 (tc-q-001)= ### TC-Q-001 GetCapabilities — 能力探测 --- (tc-q-002)= ### TC-Q-002 GetRobotSnapshot — 空闲快照 --- (tc-q-003)= ### TC-Q-003 GetActiveTask — 无活跃任务 --- (tc-q-004)= ### TC-Q-004 GetActiveTask — 导航进行中 先执行 [TC-NAV-001](#tc-nav-001) 且在 Stream 未结束前,**另开终端**: --- ## 13.4 Stream 测试用例 (tc-s-001)= ### TC-S-001 ReceiveBotStates — 周期状态 --- (tc-s-002)= ### TC-S-002 ReceiveBotEvents — 任务生命周期事件 --- (tc-s-003)= ### TC-S-003 Snapshot 与 Stream 一致性 终端 A 订阅 `ReceiveBotStates`,终端 B 调用 `GetRobotSnapshot`: --- ## 13.5 System 测试用例 (tc-sys-001)= ### TC-SYS-001 EmergencyStop — 空闲时急停 --- (tc-sys-002)= ### TC-SYS-002 EmergencyStop — 导航中抢占 1. 终端 A 发起 [TC-NAV-001](#tc-nav-001)(保持 Stream 打开) 2. 终端 B 执行 TC-SYS-001 --- (tc-sys-003)= ### TC-SYS-003 CancelAllTasks — 取消全部 --- (tc-sys-004)= ### TC-SYS-004 CancelAllTasks — 按类型取消 --- ## 13.6 SendNavigationCommand 测试用例 (tc-nav-001)= ### TC-NAV-001 单点导航 START --- (tc-nav-002)= ### TC-NAV-002 多点巡航 START --- (tc-nav-003)= ### TC-NAV-003 PAUSE / RESUME 导航进行中(Stream 未结束),**新 RPC 调用**(若实现支持同任务控制)或先发 PAUSE: --- (tc-nav-004)= ### TC-NAV-004 STOP — 停止当前导航 --- (tc-nav-005)= ### TC-NAV-005 CANCEL — 取消并清理 --- (tc-nav-006)= ### TC-NAV-006 REPLAN — 保持目标重规划 导航进行中: --- (tc-nav-neg-001)= ### TC-NAV-NEG-001 缺少 header — 参数错误 --- (tc-nav-neg-002)= ### TC-NAV-NEG-002 任务互斥 — 重复 START 任务 A 未结束时再 START: --- (tc-nav-neg-003)= ### TC-NAV-NEG-003 不可达目标 --- ## 13.7 SendExplorationCommand 测试用例 (tc-exp-001)= ### TC-EXP-001 START 探索 --- (tc-exp-002)= ### TC-EXP-002 PAUSE / RESUME --- (tc-exp-003)= ### TC-EXP-003 SAVE_MAP --- (tc-exp-004)= ### TC-EXP-004 SET_AREA 限定区域 --- ## 13.8 SendFollowCommand 测试用例 (tc-fol-001)= ### TC-FOL-001 跟随行人模式 START --- (tc-fol-002)= ### TC-FOL-002 跟随目标位姿 --- (tc-fol-003)= ### TC-FOL-003 UPDATE_TARGET 动态更新 --- (tc-fol-004)= ### TC-FOL-004 目标丢失 目标离开视野且 `reacquire_on_lost=false`: --- ## 13.9 SendTeleopCommand 测试用例 grpcurl 对 Bidi Stream 能力有限;**完整测试请用 Python**(见 [§13.14](#1314-python-集成测试脚本))。 (tc-tel-001)= ### TC-TEL-001 START 探测 --- (tc-tel-002)= ### TC-TEL-002 VELOCITY 连续发送(Python) --- (tc-tel-003)= ### TC-TEL-003 STOP --- ## 13.10 SendDockCommand 测试用例 (tc-dck-001)= ### TC-DCK-001 按站点 ID 对接 --- (tc-dck-002)= ### TC-DCK-002 按位姿对接 --- (tc-dck-003)= ### TC-DCK-003 UNDOCK --- ## 13.11 SendMapCommand 测试用例 (tc-map-001)= ### TC-MAP-001 LOAD 地图 --- (tc-map-002)= ### TC-MAP-002 SWITCH 地图 --- (tc-map-003)= ### TC-MAP-003 SET_INITIAL_POSE 重定位 --- (tc-map-004)= ### TC-MAP-004 CLEAR_COSTMAP --- (tc-map-005)= ### TC-MAP-005 ADD / REMOVE 禁行区 --- ## 13.12 端到端集成场景 (e2e-001)= ### E2E-001 标准导航闭环 | 步骤 | RPC | 预期 | |------|-----|------| | 1 | `GetCapabilities` | `supportsNavigation=true` | | 2 | `GetActiveTask` | `type=NONE` | | 3 | `SendNavigationCommand` START | Stream 进度帧 | | 4 | `GetActiveTask` | `type=NAVIGATION`, `RUNNING` | | 5 | `ReceiveBotStates` | `activeCmdId` 匹配 | | 6 | Stream 末帧 | `SUCCEEDED` | | 7 | `GetActiveTask` | `type=NONE` | --- (e2e-002)= ### E2E-002 急停打断导航 | 步骤 | RPC | 预期 | |------|-----|------| | 1 | START 导航 | `NAVIGATING` | | 2 | `EmergencyStop` | `success=true` | | 3 | 导航 Stream | 末帧 `CANCELED` 或 `FAILED` | | 4 | `ReceiveBotEvents` | `ROBOT_EVENT_EMERGENCY_STOP` | | 5 | `GetActiveTask` | `type=NONE` | --- (e2e-003)= ### E2E-003 探索 → 保存地图 → 加载导航 | 步骤 | RPC | 预期 | |------|-----|------| | 1 | `SendExplorationCommand` START | `EXPLORING` | | 2 | `SendExplorationCommand` SAVE_MAP | `map_name` 写入 | | 3 | `SendMapCommand` LOAD 同名地图 | `currentMapName` 一致 | | 4 | `SendNavigationCommand` START | 在新地图上 `SUCCEEDED` | --- (e2e-004)= ### E2E-004 重定位后导航 | 步骤 | RPC | 预期 | |------|-----|------| | 1 | `SendMapCommand` SET_INITIAL_POSE | `success=true` | | 2 | `GetRobotSnapshot` | `pose` 与设定一致 | | 3 | `SendNavigationCommand` START | 正常规划 | --- ## 13.13 一键冒烟序列(bash) 复制整段执行;每步打印 `[OK]` / `[FAIL]`(需 Bridge 运行中): ```bash set -e REPO=${REPO:-/path/to/autonomy} BRIDGE=${BRIDGE:-127.0.0.1:5005} PROTO_OPTS="-import-path $REPO -proto autonomy/bridge/proto/external_command_service.proto" SVC=autonomy.bridge.proto.AutonomyService run() { echo ">>> $1"; shift; "$@" && echo "[OK] $1" || echo "[FAIL] $1"; } run "list services" grpcurl -plaintext $PROTO_OPTS $BRIDGE list run "GetCapabilities" grpcurl -plaintext $PROTO_OPTS -d '{}' $BRIDGE $SVC/GetCapabilities run "GetRobotSnapshot" grpcurl -plaintext $PROTO_OPTS -d '{}' $BRIDGE $SVC/GetRobotSnapshot run "GetActiveTask" grpcurl -plaintext $PROTO_OPTS -d '{}' $BRIDGE $SVC/GetActiveTask CID="smoke-$(date +%s)" run "Nav STOP" grpcurl -plaintext $PROTO_OPTS -d @- $BRIDGE $SVC/SendNavigationCommand < int: ch = grpc.insecure_channel(BRIDGE) stub = stubs.AutonomyServiceStub(ch) # TC-Q-001 caps = stub.GetCapabilities(empty_pb2.Empty()) print("Capabilities:", caps.bridge_version, "nav=", caps.supports_navigation) assert caps.bridge_version, "bridge_version empty" # TC-Q-003 task = stub.GetActiveTask(empty_pb2.Empty()) print("ActiveTask:", task.type, task.status) # TC-NAV-004 STOP cmd_id = f"py-{int(time.time())}" final = None for resp in stub.SendNavigationCommand(pb.NavigationCommandRequest( header=pb.RequestHeader(cmd_id=cmd_id, client_id=CLIENT_ID), command=pb.NAV_CMD_STOP, )): print("Nav STOP ack:", resp.ack.success, resp.ack.final, resp.status) if resp.ack.final: final = resp break assert final and final.ack.final, "missing final frame" # TC-S-001 采样 3 帧 stream = stub.ReceiveBotStates(empty_pb2.Empty()) for i in range(3): state = next(stream) print(f"State[{i}]: bat={state.battery_percent:.0f}% task={state.active_task_type}") # TC-SYS-001 ack = stub.EmergencyStop(pb.EmergencyStopRequest( header=pb.RequestHeader(cmd_id=f"estop-{cmd_id}", client_id=CLIENT_ID), reason="python integration test", )) print("Estop:", ack.success, ack.message) assert ack.success print("ALL PASSED") return 0 if __name__ == "__main__": sys.exit(main()) ``` **Teleop Bidi 示例**(TC-TEL-002): ```python from autonomy.commsgs.proto import geometry_msgs_pb2 def teleop_velocity_test(stub, duration_sec=3.0): cmd_id = f"teleop-{int(time.time())}" def requests(): yield pb.TeleopCommandRequest( header=pb.RequestHeader(cmd_id=cmd_id, client_id=CLIENT_ID), command=pb.TELEOP_CMD_START, watchdog_timeout_sec=1.0, max_linear_speed=0.3, ) t0 = time.time() while time.time() - t0 < duration_sec: yield pb.TeleopCommandRequest( header=pb.RequestHeader(cmd_id=cmd_id, client_id=CLIENT_ID), command=pb.TELEOP_CMD_VELOCITY, velocity=geometry_msgs_pb2.TwistStamped( twist=geometry_msgs_pb2.Twist( linear=geometry_msgs_pb2.Vector3(x=0.1), ) ), ) time.sleep(0.1) yield pb.TeleopCommandRequest( header=pb.RequestHeader(cmd_id=cmd_id, client_id=CLIENT_ID), command=pb.TELEOP_CMD_STOP, ) for resp in stub.SendTeleopCommand(requests()): if resp.ack.final: assert resp.status == pb.TELEOP_STATUS_IDLE or resp.ack.success break ``` --- ## 13.15 用例索引 | 分类 | 用例 ID | 章节 | |------|---------|------| | Query | TC-Q-001 … 004 | §13.3 | | Stream | TC-S-001 … 003 | §13.4 | | System | TC-SYS-001 … 004 | §13.5 | | Navigation | TC-NAV-001 … 006, TC-NAV-NEG-001 … 003 | §13.6 | | Exploration | TC-EXP-001 … 004 | §13.7 | | Follow | TC-FOL-001 … 004 | §13.8 | | Teleop | TC-TEL-001 … 003 | §13.9 | | Dock | TC-DCK-001 … 003 | §13.10 | | Map | TC-MAP-001 … 005 | §13.11 | | E2E | E2E-001 … 004 | §13.12 |