18 KiB
18 KiB
调试与测试
**本文引用的文件** - [package.json](file://package.json) - [jest.config.js](file://jest.config.js) - [src/index.ts](file://src/index.ts) - [test/websockethandler.test.ts](file://test/websockethandler.test.ts) - [test/httphandler.test.ts](file://test/httphandler.test.ts) - [client/package.json](file://client/package.json) - [client/jest.config.js](file://client/jest.config.js) - [client/jest.setup.js](file://client/jest.setup.js) - [client/test/signaling.test.js](file://client/test/signaling.test.js) - [client/test/mocksignaling.js](file://client/test/mocksignaling.js) - [client/test/testutils.js](file://client/test/testutils.js) - [client/test/peerconnection.test.js](file://client/test/peerconnection.test.js) - [src/log.ts](file://src/log.ts) - [run.bat](file://run.bat)目录
简介
本指南面向开发者与测试工程师,系统化讲解本项目的测试体系与调试方法,覆盖以下主题:
- JUnit(在本项目中为 Jest)测试框架的配置与使用,包括单元测试与集成测试的编写规范
- 测试环境搭建、测试数据准备与断言策略
- WebSocket 与 HTTP 服务的调试技巧(断点设置、日志分析)
- 客户端测试:WebRTC 连接测试与信令测试
- 模拟对象的使用:mock-socket 与 jest-websocket-mock
- 性能测试与压力测试方法
- 测试覆盖率报告的生成与分析
项目结构
该项目采用前后端分离的测试布局:
- 后端(Node/Express + WebSocket/HTTP 信令):位于根目录,测试文件位于 test/ 下
- 前端(Web 客户端,含 WebRTC 与信令逻辑):位于 client/ 目录,测试文件位于 client/test/ 下
- 根目录与 client/ 目录分别维护独立的 Jest 配置与脚本
graph TB
subgraph "后端服务"
A["src/index.ts<br/>启动 HTTP/HTTPS 与 WebSocket 信令"]
B["test/httphandler.test.ts<br/>HTTP 信令集成测试"]
C["test/websockethandler.test.ts<br/>WebSocket 信令集成测试"]
end
subgraph "前端客户端"
D["client/test/signaling.test.js<br/>信令端到端测试"]
E["client/test/mocksignaling.js<br/>模拟信令管理器"]
F["client/test/peerconnection.test.js<br/>WebRTC 连接单元测试"]
G["client/jest.config.js<br/>Jest 配置JSDOM 环境"]
H["client/jest.setup.js<br/>全局 polyfill 与 mock"]
end
A --> B
A --> C
D --> E
D --> F
G --> D
H --> D
图表来源
- src/index.ts:1-109
- test/httphandler.test.ts:1-510
- test/websockethandler.test.ts:1-191
- client/test/signaling.test.js:1-485
- client/test/mocksignaling.js:1-225
- client/test/peerconnection.test.js:1-251
- client/jest.config.js:1-196
- client/jest.setup.js:1-35
章节来源
核心组件
- 后端入口与信令模式选择:根据命令行参数决定 HTTP 或 WebSocket 信令模式,并按模式启动对应信令服务
- 前端信令抽象:支持 HTTP 与 WebSocket 两种信令;同时提供 MockSignaling 用于快速验证
- WebRTC 连接层:封装 RTCPeerConnection、事件与候选处理
- 日志系统:统一的日志级别控制,便于调试与生产输出
章节来源
架构总览
下图展示从客户端发起信令到后端处理再到另一客户端接收的关键流程。
sequenceDiagram
participant ClientA as "客户端A"
participant SignalingA as "信令适配(HTTP/WebSocket/Mock)"
participant Server as "后端服务(src/index.ts)"
participant SignalingB as "信令适配(HTTP/WebSocket/Mock)"
participant ClientB as "客户端B"
ClientA->>SignalingA : "创建连接/发送Offer"
SignalingA->>Server : "POST /offer 或 WebSocket 消息"
Server-->>SignalingB : "广播/转发 Offer"
SignalingB-->>ClientB : "触发 offer 事件"
ClientB->>SignalingB : "发送 Answer/Candidate"
SignalingB->>Server : "上报 Answer/Candidate"
Server-->>SignalingA : "转发 Answer/Candidate"
SignalingA-->>ClientA : "触发 answer/candidate 事件"
图表来源
- client/test/signaling.test.js:67-208
- test/httphandler.test.ts:71-120
- test/websockethandler.test.ts:52-82
- src/index.ts:75-88
详细组件分析
后端 HTTP 信令测试(集成测试)
- 使用 @jest-mock/express 提供的 getMockReq/getMockRes 构造 Express 请求/响应对象
- 通过 createSession/checkSessionId/createConnection/post*/getAll/delete* 等接口组合,验证公共/私有模式下的消息流转
- 关键断言点:
- 连接创建时返回的 polite 标识
- Offer/Answer/Candidate 的收发与过滤规则
- 超时清理与会话删除行为
- 超时场景通过循环调用 checkSessionId 并等待超时实现
flowchart TD
Start(["开始"]) --> CreateSess1["创建会话A"]
CreateSess1 --> CreateSess2["创建会话B"]
CreateSess2 --> ConnA["会话A 创建连接"]
ConnA --> ConnB["会话B 创建连接"]
ConnB --> Offer["会话A 发送 Offer"]
Offer --> ExpectOffer["会话B 收到 Offer"]
ExpectOffer --> Answer["会话B 发送 Answer"]
Answer --> ExpectAnswer["会话A 收到 Answer"]
ExpectAnswer --> Candidate["双方发送 Candidate"]
Candidate --> Cleanup["删除连接/会话"]
Cleanup --> End(["结束"])
图表来源
章节来源
后端 WebSocket 信令测试(集成测试)
- 使用 jest-websocket-mock 搭建本地 WebSocket 服务器,模拟真实信令通道
- 验证公共/私有模式下 connect/polite、offer/answer、candidate、disconnect 的消息一致性
- 使用 toReceiveMessage/toHaveReceivedMessages 断言消息内容与顺序
sequenceDiagram
participant WS as "WS 服务器(jest-websocket-mock)"
participant Handler as "wsHandler"
participant C1 as "客户端1"
participant C2 as "客户端2"
C1->>WS : "连接"
C2->>WS : "连接"
C1->>Handler : "onConnect(connectionId1)"
Handler-->>WS : "广播 connect(polite=true)"
WS-->>C1 : "收到 connect"
C2->>Handler : "onConnect(connectionId1)"
Handler-->>WS : "广播 connect(polite=true)"
WS-->>C2 : "收到 connect"
C1->>Handler : "onOffer({connectionId1,sdp})"
Handler-->>WS : "广播 offer"
WS-->>C2 : "收到 offer"
C2->>Handler : "onAnswer(...)"
Handler-->>WS : "广播 answer"
WS-->>C1 : "收到 answer"
C1->>Handler : "onDisconnect(...)"
Handler-->>WS : "广播 disconnect"
WS-->>C1 : "收到 disconnect"
WS-->>C2 : "收到 disconnect"
图表来源
章节来源
前端信令测试(端到端测试)
- 支持三种模式:mock、http、websocket
- 使用 jest-dev-server 启动本地服务,或使用 MockSignaling 进行纯内存测试
- 通过自定义事件 connect/offer/answer/candidate/disconnect 驱动断言
- 使用 waitFor/sleep 辅助异步等待与稳定测试节奏
sequenceDiagram
participant Test as "Jest 测试"
participant DevServer as "本地服务(可选)"
participant Signaling as "Signaling/WS 签名器"
participant PeerA as "Peer A"
participant PeerB as "Peer B"
Test->>DevServer : "启动(可选)"
Test->>Signaling : "start()"
Test->>PeerA : "createConnection()"
Test->>PeerB : "createConnection()"
Test->>PeerA : "sendOffer()"
PeerA-->>PeerB : "触发 offer 事件"
Test->>PeerB : "sendAnswer()"
PeerB-->>PeerA : "触发 answer 事件"
Test->>PeerB : "sendCandidate()"
PeerB-->>PeerA : "触发 candidate 事件"
Test->>PeerA : "deleteConnection()"
PeerA-->>PeerB : "触发 disconnect 事件"
图表来源
- client/test/signaling.test.js:23-65
- client/test/signaling.test.js:67-208
- client/test/signaling.test.js:221-264
章节来源
前端模拟信令管理器(MockSignaling)
- 公共模式:任意两个 Signaling 实例之间互相广播消息
- 私有模式:基于 connectionId 维护对等集合,仅在双方均已打开连接时才互相广播
- 提供延迟以模拟网络时延,便于验证事件顺序与时序
classDiagram
class MockSignaling {
+interval
+start()
+stop()
+createConnection(connectionId)
+deleteConnection(connectionId)
+sendOffer(connectionId, sdp)
+sendAnswer(connectionId, sdp)
+sendCandidate(connectionId, candidate, sdpMLineIndex, sdpMid)
}
class MockPublicSignalingManager {
+list : Set
+add(signaling)
+remove(signaling)
+openConnection(signaling, connectionId)
+closeConnection(signaling, connectionId)
+offer(owner, data)
+answer(owner, data)
+candidate(owner, data)
}
class MockPrivateSignalingManager {
+connectionIds : Map
+openConnection(signaling, connectionId)
+closeConnection(signaling, connectionId)
+offer(owner, data)
+answer(owner, data)
+candidate(owner, data)
-findList(owner, connectionId)
}
MockSignaling --> MockPublicSignalingManager : "公共模式"
MockSignaling --> MockPrivateSignalingManager : "私有模式"
图表来源
- client/test/mocksignaling.js:10-52
- client/test/mocksignaling.js:54-113
- client/test/mocksignaling.js:115-224
章节来源
WebRTC 连接单元测试
- 验证 Peer 对象在不同场景下的事件触发与状态变化
- 包括添加轨道/Transceiver/DataChannel 自动触发 Offer
- 在不同角色(polite/impolite)下正确触发 Answer
- 候选收集与接受的条件判断
flowchart TD
Init["初始化 Peer(config)"] --> AddTrack["添加轨道/Transceiver/DataChannel"]
AddTrack --> FireOffer["触发 sendoffer 事件"]
FireOffer --> OnOffer["收到 Offer 描述"]
OnOffer --> MaybeAnswer{"是否需要 Answer?"}
MaybeAnswer --> |是| FireAnswer["触发 sendanswer 事件"]
MaybeAnswer --> |否| Skip["不触发 Answer"]
FireAnswer --> OnAnswer["收到 Answer 描述"]
OnAnswer --> Negotiate["触发 negotiated 事件"]
AddTrack --> Candidate["收集 ICE Candidate"]
Candidate --> Accept{"已有远端描述?"}
Accept --> |是| Store["保存候选"]
Accept --> |否| Drop["丢弃候选"]
图表来源
- client/test/peerconnection.test.js:69-122
- client/test/peerconnection.test.js:124-188
- client/test/peerconnection.test.js:222-238
章节来源
依赖分析
- 根目录测试依赖
- jest、jest-websocket-mock、@jest-mock/express、ts-jest、typescript
- 客户端测试依赖
- jest、jest-environment-jsdom、jest-dev-server、node-fetch、@jest/globals
- 关键配置
- 根目录 jest.config.js 开启覆盖率收集,使用 ts-jest 转换 TS
- 客户端 jest.config.js 使用 JSDOM 环境,加载 jest.setup.js 注入 polyfill 与 WebRTC mock
章节来源
- package.json:28-46
- client/package.json:9-18
- jest.config.js:19-34
- client/jest.config.js:18-25
- client/jest.config.js:130-131
性能考虑
- 单元测试应避免真实网络 I/O,优先使用 Mock 与内存态模拟
- 集成测试中,WebSocket/HTTP 信令测试通过本地 mock 降低外部依赖
- WebRTC 测试通过自定义事件与延迟函数控制执行节奏,避免非确定性
- 大规模并发场景建议拆分测试套件,使用并行 worker 与独立端口运行多实例
故障排查指南
- 启动与调试
- 使用 run.bat 构建并启动服务,同时开启日志级别以便定位问题
- 通过 src/log.ts 的日志级别控制,将日志提升至 info 或更高等级
- WebSocket 调试
- 在 jest-websocket-mock 中断言 toReceiveMessage/toHaveReceivedMessages,确保消息类型与字段匹配
- 若出现“未收到消息”,检查信令管理器是否正确广播以及客户端监听是否生效
- HTTP 信令调试
- 使用 @jest-mock/express 的请求/响应对象构造,逐个接口断言状态码与 JSON 结果
- 注意会话超时逻辑,必要时增加等待时间或调整超时阈值
- 客户端测试
- 若使用 jest-dev-server 启动本地服务,注意端口占用与进程回收
- 使用 waitFor/sleep 稳定异步事件,避免竞态条件导致断言失败
- 日志分析
- 利用 setLogLevel 与 log 输出统一格式的时间戳与级别前缀,结合断点逐步缩小范围
章节来源
结论
本项目提供了完善的测试与调试基础设施:
- 后端通过 HTTP/WebSocket 两种信令模式的集成测试,覆盖公共/私有模式的消息流转与超时清理
- 前端通过 MockSignaling 与 jest-dev-server 支持端到端测试,结合 WebRTC 单元测试保障连接稳定性
- Jest 配置与覆盖率收集已就绪,便于持续改进质量与回归保障
附录
测试环境搭建与运行
- 根目录测试
- 安装依赖后,使用 npm 脚本运行测试与覆盖率收集
- 客户端测试
- 在 client/ 目录下安装依赖,使用 npm 脚本运行前端测试
- 覆盖率报告
- 根目录与客户端目录均启用覆盖率收集,生成 coverage 报告目录
章节来源
测试数据准备与断言规范
- HTTP 信令
- 使用 getMockReq/getMockRes 构造请求体与头部,断言 res.json/res.sendStatus 的返回值
- 通过 getAll/getOffer/getAnswer/getCandidate 验证消息队列状态
- WebSocket 信令
- 使用 jest-websocket-mock 的 toReceiveMessage/toHaveReceivedMessages 断言消息
- 前端信令
- 使用自定义事件断言,结合 waitFor/sleep 控制异步时机
- WebRTC
- 通过事件监听与内部状态断言,验证 Offer/Answer/Candidate 的触发与接受条件
章节来源
- test/httphandler.test.ts:14-33
- test/websockethandler.test.ts:17-28
- client/test/signaling.test.js:67-208
- client/test/peerconnection.test.js:69-122
WebSocket 与 HTTP 服务调试技巧
- 断点设置
- 在 src/index.ts 的信令分支处设置断点,观察 mode/type 参数对行为的影响
- 在 wsHandler/httphandler 的消息处理函数中设置断点,验证输入与输出
- 日志分析
- 使用 src/log.ts 的 log 函数输出关键路径,结合 setLogLevel 提升日志级别
- 在客户端测试中,利用 waitFor/sleep 精确控制事件时序,便于定位异常
章节来源
客户端测试:WebRTC 连接与信令
- 连接测试
- 通过 Peer 对象的 addTrack/addTransceiver/createDataChannel 触发 Offer
- 在不同角色下验证 Answer 的触发时机
- 信令测试
- 使用 MockSignaling 或 jest-dev-server 启动的服务进行端到端验证
- 断言 connect/offer/answer/candidate/disconnect 事件的完整性
章节来源
模拟对象使用指南
- jest-websocket-mock
- 用于 WebSocket 信令的本地服务器模拟,断言消息收发
- @jest-mock/express
- 用于 HTTP 信令的请求/响应对象构造与断言
- jest-dev-server
- 用于启动本地服务参与端到端测试
- WebRTC mock
- 在 client/jest.setup.js 中注入 RTCPeerConnection/SessionDescription/IceCandidate/ResizeObserver 等 polyfill
章节来源
- test/websockethandler.test.ts:1
- test/httphandler.test.ts:1
- client/test/signaling.test.js:3
- client/jest.setup.js:21-35
性能测试与压力测试方法
- 单元测试阶段保持无外部依赖,使用 Mock 与同步断言
- 集成测试阶段,通过多个客户端并发创建连接、发送 Offer/Answer/Candidate,统计事件完成时间
- 使用独立端口与进程并行运行多实例,评估吞吐与延迟
- 建议在 CI 中开启覆盖率报告,持续监控代码覆盖度
测试覆盖率报告生成与分析
- 根目录与客户端目录均启用覆盖率收集,输出目录为 coverage
- 可在 CI 中配置覆盖率阈值,确保关键路径被覆盖
章节来源