24、测试与调试:单元测试、集成测试、端到端测试、调试工具链
说实话,WebRTC 的测试,是我在写这个课程时最想跟你聊透的话题之一。
为什么?因为 WebRTC 涉及网络、媒体流、信令、ICE 穿透……太多环节了。我见过太多项目,开发时跑得挺好,一上线就崩。说白了,就是测试没做到位。
我自己带团队做视频会议产品时,吃过不少亏。有一次,一个看似简单的音视频同步问题,愣是排查了两天。后来发现是单元测试没覆盖到某个边界情况。嗯,从那以后,我对测试的态度就变了——不是「要不要做」,而是「怎么做才够」。
24.1 测试金字塔:你的护身符
先看一张图,这是我每次做技术评审时都会画出来的。
你想想看,如果底层单元测试没写好,上层的集成测试和端到端测试就像在沙子上盖楼。我个人的习惯是:单元测试占 70%,集成测试占 20%,端到端测试占 10%。这个比例不是拍脑袋定的,是经过多个项目验证过的。
24.2 单元测试:把每个零件拧紧
单元测试测什么?说白了,就是测你的函数、类、模块在孤立环境下能不能正常工作。
在 WebRTC 场景里,我通常会对以下几类代码写单元测试:
- 信令消息的序列化与反序列化——比如 JSON 的编解码
- 房间管理逻辑——用户加入、离开、权限校验
- 媒体流状态机——比如从「连接中」到「已连接」的转换
- ICE 候选地址的排序与过滤
举个例子,这是我对信令消息做单元测试的代码片段:
// 使用 Jest 框架
describe('信令消息编解码', () => {
test('应该正确编码加入房间消息', () => {
const msg = {
type: 'join',
roomId: 'room-123',
userId: 'user-456'
};
const encoded = SignalingCodec.encode(msg);
const decoded = SignalingCodec.decode(encoded);
expect(decoded).toEqual(msg);
});
test('应该处理空字段的消息', () => {
const msg = { type: 'leave', roomId: '', userId: '' };
const encoded = SignalingCodec.encode(msg);
const decoded = SignalingCodec.decode(encoded);
expect(decoded.roomId).toBe('');
});
});
24.3 集成测试:看模块之间怎么配合
单元测试通过后,就该看模块之间的配合了。集成测试关注的是:信令服务器和 WebRTC 客户端能不能正常握手?媒体流能不能正常转发?
我一般会用 mocha + sinon 来做集成测试。关键点是:
- 模拟真实的信令交互——用 WebSocket 客户端模拟多个用户
- 验证媒体流状态——检查
RTCPeerConnection的连接状态变化 - 测试 ICE 重连机制——模拟网络切换场景
这里有个我常用的集成测试套路:
// 模拟两个用户加入同一房间
const userA = new SimulatedClient('userA');
const userB = new SimulatedClient('userB');
await userA.joinRoom('room-1');
await userB.joinRoom('room-1');
// 验证 userA 收到了 userB 的加入通知
const joinEvent = await userA.waitForEvent('peerJoined');
expect(joinEvent.userId).toBe('userB');
// 验证双方建立了 PeerConnection
expect(userA.pc.connectionState).toBe('connected');
expect(userB.pc.connectionState).toBe('connected');
24.4 端到端测试:模拟真实用户操作
端到端测试,说白了就是让测试脚本像真人一样操作浏览器。打开页面、点击按钮、加入会议、说话、看画面……
我推荐用 Playwright 或 Puppeteer 来做。为什么?因为它们能控制真实的 Chromium 浏览器,能捕获 WebRTC 的内部事件。
一个典型的端到端测试流程:
- 启动两个浏览器实例
- 分别打开视频会议页面
- 输入房间号,点击「加入」
- 等待双方媒体流建立
- 验证视频画面是否正常渲染
- 模拟一方断网,验证重连机制
// 使用 Playwright 的端到端测试示例
test('两个用户应该能正常视频通话', async () => {
const browser = await chromium.launch();
const pageA = await browser.newPage();
const pageB = await browser.newPage();
await pageA.goto('https://meeting-app.local/room/123');
await pageB.goto('https://meeting-app.local/room/123');
// 等待视频元素出现
await pageA.waitForSelector('video.remote-video');
await pageB.waitForSelector('video.remote-video');
// 验证视频流正在播放
const videoPlaying = await pageA.evaluate(() => {
const video = document.querySelector('video.remote-video');
return video.readyState >= 2; // HAVE_CURRENT_DATA
});
expect(videoPlaying).toBe(true);
});
24.5 调试工具链:出了问题怎么办
测试写得再好,线上还是可能出问题。这时候就需要一套趁手的调试工具。
我个人的调试工具箱:
| 工具 | 用途 | 我的使用频率 |
|---|---|---|
| Chrome DevTools → WebRTC 面板 | 查看 ICE 状态、候选地址、连接延迟 | ⭐⭐⭐⭐⭐ |
chrome://webrtc-internals |
导出完整的 WebRTC 统计信息 | ⭐⭐⭐⭐ |
| Wireshark + RTP 解析 | 抓包分析媒体流质量 | ⭐⭐⭐ |
| 自建信令日志系统 | 追踪信令消息的完整生命周期 | ⭐⭐⭐⭐⭐ |
| StatsReport API | 实时获取音视频统计(丢包率、延迟等) | ⭐⭐⭐⭐ |
这里重点说一下 chrome://webrtc-internals。这个页面能导出所有 WebRTC 连接的底层数据。我遇到过一个诡异的问题:视频偶尔卡顿,但本地测试一切正常。后来导出 webrtc-internals 数据一看,发现是某个 ICE 候选地址的延迟异常高。排查后发现是那个地区的 CDN 节点有问题。
RTCPeerConnection 的 onconnectionstatechange 和 oniceconnectionstatechange 的日志输出。这样即使没有 DevTools,也能从服务端日志里看到连接状态的变化。我习惯把状态变化时间戳精确到毫秒级,方便后续排查。
24.6 避坑指南:我踩过的那些坑
最后,分享几个我亲身经历过的教训:
- 测试环境不要用 localhost——localhost 不走网卡,ICE 候选地址跟生产环境完全不同。我曾经用 localhost 跑测试,所有用例通过,上线后全部失败。后来改用
127.0.0.1都不行,必须用局域网 IP。 - 别忘了测试 STUN/TURN 服务器——很多团队只测 P2P 直连,忽略了 TURN 中继场景。结果用户在内网环境下无法通话。我建议在测试用例里强制走 TURN 模式跑一遍。
- 日志要分级——调试时开 DEBUG 级别,线上只保留 ERROR。我见过有人把
console.log留在生产代码里,结果日志刷爆了磁盘。 - 自动化测试要定时跑——WebRTC 的底层实现会随着浏览器版本更新而变化。我建议每周跑一次完整的测试套件,确保没有因为浏览器升级而引入兼容性问题。
嗯,测试与调试这块,说到底就是一句话:别等到线上出问题才后悔。把测试做在前面,把调试工具准备好,你的 WebRTC 应用才能稳定运行。