一、WebRTC 测试:单元测试框架、端到端测试、压力测试方案

聊到 WebRTC 测试,我得先坦白一件事。我早年做音视频 SDK 的时候,总觉得测试是「别人该干的事」。结果呢?有一次上线前,发现 PeerConnection 在弱网环境下直接崩了——那叫一个惨。从那以后,我养成了一个习惯:测试不是可选项,是必选项

WebRTC 的测试,说白了分三层:单元测试端到端测试压力测试。每一层都有它的坑,也有它的套路。今天我就把这三层的方案掰开揉碎了讲给你听。

1.1 单元测试框架:把每一块积木都测稳

单元测试,测的是最细粒度的逻辑。比如一个编码器参数解析函数、一个 RTP 包组装逻辑。你想想看,如果这些基础模块都不稳,上层再漂亮也是白搭。

我个人习惯用 Jest 做 WebRTC 的单元测试。为什么?因为它快,而且对 TypeScript 支持好。WebRTC 的很多逻辑其实跑在 Node.js 环境里,比如 SDP 解析、ICE 候选处理,这些都可以脱离浏览器测。

核心思路:把 WebRTC 的纯逻辑层和浏览器 API 层解耦。纯逻辑层用 Jest 测,浏览器 API 层用模拟(mock)测。

举个例子,测一个 SDP 解析函数:

// sdpParser.test.ts
import { parseSDP } from './sdpParser';

describe('parseSDP', () => {
  it('应该正确解析音频轨道的 SSRC', () => {
    const sdp = `v=0
o=- 0 0 IN IP4 127.0.0.1
m=audio 9 UDP/TLS/RTP/SAVPF 111
a=ssrc:12345 cname:test`;

    const result = parseSDP(sdp);
    expect(result.audio.ssrc).toBe('12345');
  });

  it('缺少媒体行时应该抛出错误', () => {
    const sdp = `v=0
o=- 0 0 IN IP4 127.0.0.1`;
    expect(() => parseSDP(sdp)).toThrow('缺少媒体描述');
  });
});

嗯,这里要注意:不要 mock 太多。我见过有人把整个 RTCPeerConnection 都 mock 了,结果测出来的东西跟实际跑起来完全两码事。我的建议是——只 mock 你控制不了的部分,比如网络 I/O、浏览器 API。

小技巧:用 jest-webrtc-mock 这个库,它帮你 mock 了 MediaStream、RTCPeerConnection 等核心对象,省去你手写 mock 的麻烦。

1.2 端到端测试:模拟真实用户场景

单元测试过了,不代表你的 WebRTC 应用就能跑通。为什么?因为真实场景里有网络抖动、有不同浏览器、有各种奇怪的设备。端到端测试,就是把这些变量都拉进来。

我建议用 PuppeteerPlaywright 来做端到端测试。它们能启动真实的浏览器实例,模拟两个用户建立连接、传输音视频。

一个典型的端到端测试流程是这样的:

  1. 启动两个浏览器页面(模拟两个用户)
  2. 每个页面创建本地媒体流(用 fake 设备)
  3. 通过信令服务器交换 SDP 和 ICE 候选
  4. 等待连接建立(iceConnectionState === 'connected')
  5. 验证音视频数据是否正常传输

我曾经踩过一个坑:测试环境里一切正常,但上线后用户反馈「看不到对方画面」。查了半天,发现是 getUserMedia 在 HTTPS 下才工作,而测试环境用的是 HTTP。从那以后,我所有的端到端测试都强制跑在 HTTPS 下。

// e2e_test.js
const browser = await puppeteer.launch();
const page1 = await browser.newPage();
const page2 = await browser.newPage();

await page1.goto('https://localhost:3000/room/1');
await page2.goto('https://localhost:3000/room/1');

// 等待连接建立
await page1.waitForFunction(
  () => document.querySelector('#status').textContent === 'connected',
  { timeout: 10000 }
);

// 验证视频流
const hasVideo = await page1.evaluate(() => {
  const video = document.querySelector('#remoteVideo');
  return video && video.readyState >= 2;
});
expect(hasVideo).toBe(true);
注意:端到端测试很慢,别把它当单元测试跑。我一般只在 CI 的「预发布」阶段跑,或者每天定时跑一次。

1.3 压力测试:看看系统能扛多少

压力测试,说白了就是「往死里怼」。你的 WebRTC 应用能同时支持多少路视频流?网络丢包 20% 时还能不能通话?这些问题,只有压力测试能回答。

我常用的压力测试工具有两个:

  • k6:适合测信令服务器的并发能力
  • Gatling:适合测 WebRTC 媒体流的压力场景

但说实话,WebRTC 的压力测试比普通 HTTP 难得多。因为每个连接都要维护状态,还要模拟音视频数据。我见过有人用 100 个虚拟机同时跑 Chrome 实例来做压力测试——效果是好,但成本也高得吓人。

一个更务实的方案是:用无头浏览器 + 虚拟音视频设备。比如用 ffmpeg 生成一个测试视频文件,然后通过虚拟摄像头喂给浏览器。

# 创建虚拟视频设备(Linux)
sudo modprobe v4l2loopback

# 用 ffmpeg 生成测试视频并推送到虚拟设备
ffmpeg -re -f lavfi -i testsrc2=size=640x480:rate=30 \
  -f v4l2 /dev/video0

然后你就可以用 Puppeteer 启动多个页面,每个页面都从 /dev/video0 获取视频流,模拟多路并发。

关键指标:
  • 并发连接数:系统能同时维持多少路 PeerConnection
  • CPU 使用率:编码/解码的压力有多大
  • 内存泄漏:长时间运行后内存是否持续增长
  • 丢包恢复时间:网络恶化后,视频质量多久能恢复

我记得有一次做压力测试,发现连接数到 50 的时候,CPU 直接飙到 90%。查了半天,原来是 Simulcast 编码 默认开了三层,每层都要独立编码。关掉不必要的层之后,CPU 降到了 40%。

1.4 测试框架对比:选哪个?

测试类型 推荐工具 适用场景 我的经验
单元测试 Jest + jest-webrtc-mock SDP 解析、ICE 逻辑、编解码参数 覆盖率目标 80% 以上,重点测边界条件
端到端测试 Playwright + fake 设备 连接建立、媒体传输、信令交互 跑在 CI 的预发布阶段,别跑太频繁
压力测试 k6 + 虚拟摄像头 并发能力、弱网表现、内存泄漏 先跑 10 路,再逐步增加到 100 路

1.5 测试金字塔:WebRTC 版

你可能听过经典的「测试金字塔」——底层单元测试多,上层端到端测试少。WebRTC 的测试金字塔,我稍微调整了一下:

单元测试 Jest + jest-webrtc-mock SDP解析、ICE逻辑、编解码参数 端到端测试 Playwright + 真实浏览器 压力测试 k6 + 虚拟摄像头 数量多 数量少 成本高 成本低

你看这个金字塔,底层是单元测试,数量最多、跑得最快。中间是端到端测试,数量适中。顶层是压力测试,数量最少但最接近真实场景。我个人的经验是:单元测试覆盖 80% 的逻辑,端到端测试覆盖 15% 的关键路径,压力测试覆盖 5% 的高风险场景

我的建议:别追求 100% 的测试覆盖率。WebRTC 的很多逻辑跟浏览器 API 强相关,mock 太多反而没意义。把精力放在「最容易出问题」的地方——比如 SDP 协商、ICE 连接状态机、弱网恢复逻辑。

1.6 避坑指南:我踩过的那些坑

最后,分享几个我踩过的坑,希望能帮你少走弯路:

  • 不要用 setTimeout 等连接建立。ICE 连接时间受网络影响很大,用 iceConnectionStateChange 事件来驱动测试流程。
  • fake 设备要模拟真实数据。别用静态图片,用动态视频流。否则你测不出编码器的真实压力。
  • 压力测试要监控系统资源。CPU、内存、网络带宽都要记录。我曾经只关注连接数,结果内存泄漏导致 OOM,整个测试集群都挂了。
  • 端到端测试要处理超时。WebRTC 连接可能因为各种原因失败,设置合理的超时时间(我一般设 15 秒),超时后自动重试。

嗯,WebRTC 测试这块,说白了就是「多测、早测、自动化测」。别等到上线前才想起来测,那时候发现问题,改起来成本就高了。

好了,这一章的内容就到这里。测试是 WebRTC 开发中容易被忽视但极其重要的一环。希望今天分享的这些方案和坑,能帮你把测试做得更扎实。


公众号:蓝海资料掘金营,微信deep3321