26、WebRTC测试:单元测试框架、端到端测试、压力测试与性能基准
做WebRTC开发,最怕什么?我最怕的是:本地跑得好好的,一上生产就崩。音视频这东西,涉及网络、编解码、信令、ICE穿透……任何一个环节出问题,用户感知就是「卡顿」「黑屏」「断连」。所以,测试不是可选项,是必选项。
今天咱们就聊聊WebRTC的测试体系。我把它分成四个层次:单元测试、端到端测试、压力测试、性能基准。每个层次解决不同的问题,缺一不可。
一、单元测试:把每个模块锁死
单元测试,说白了就是测试「最小的可测试单元」。在WebRTC里,这个单元可能是一个PeerConnection的状态机、一个SDP解析函数、或者一个ICE候选地址的排序逻辑。
我个人习惯用 Jest 做单元测试。为什么?因为WebRTC的很多逻辑是纯JavaScript,Jest的mock能力很强,可以模拟RTCPeerConnection、MediaStream这些浏览器API。
举个例子,测试一个SDP解析函数:
// sdpParser.test.js
const { parseSDP, getMediaTracks } = require('./sdpParser');
describe('SDP Parser', () => {
test('should extract audio and video tracks from SDP', () => {
const mockSDP = `v=0
o=- 123456 2 IN IP4 127.0.0.1
s=-
t=0 0
m=audio 49170 RTP/AVP 0
a=rtpmap:0 PCMU/8000
m=video 51372 RTP/AVP 96
a=rtpmap:96 H264/90000`;
const result = getMediaTracks(mockSDP);
expect(result.audio).toBe(true);
expect(result.video).toBe(true);
});
test('should return false for missing video', () => {
const sdpWithoutVideo = `v=0
o=- 123456 2 IN IP4 127.0.0.1
s=-
t=0 0
m=audio 49170 RTP/AVP 0`;
const result = getMediaTracks(sdpWithoutVideo);
expect(result.video).toBe(false);
});
});
我在项目中遇到过一个问题:SDP里ice-ufrag字段拼写错误,导致ICE连接一直失败。单元测试一跑,立马暴露了。嗯,从那以后,所有SDP相关的解析函数我都加了单元测试。
jest.fn() 模拟RTCPeerConnection的createOffer、setLocalDescription等方法。这样不用真的建立连接,就能测试信令交互逻辑。
二、端到端测试:模拟真实用户场景
单元测试只能保证「零件没问题」,但零件组装起来能不能跑?这就需要端到端测试了。
端到端测试,我推荐用 Puppeteer 或 Playwright。它们可以启动真实的浏览器,打开两个页面,一个作为发送端,一个作为接收端,然后验证视频流是否正常传输。
核心思路是这样的:
- 启动两个浏览器实例(或两个页面)
- 页面A加入房间,页面B加入同一个房间
- 页面A开始推送本地视频流
- 等待几秒,检查页面B是否收到了视频帧
- 断言:接收到的帧数 > 0
代码大概长这样:
// e2e.test.js
const puppeteer = require('puppeteer');
describe('WebRTC End-to-End Test', () => {
let browser, pageA, pageB;
beforeAll(async () => {
browser = await puppeteer.launch({ headless: true });
pageA = await browser.newPage();
pageB = await browser.newPage();
});
test('should establish video call between two peers', async () => {
await pageA.goto('https://your-webrtc-app.com/room/123');
await pageB.goto('https://your-webrtc-app.com/room/123');
// 模拟用户点击「加入房间」
await pageA.click('#join-btn');
await pageB.click('#join-btn');
// 等待ICE连接建立
await pageA.waitForSelector('#remote-video', { timeout: 10000 });
await pageB.waitForSelector('#remote-video', { timeout: 10000 });
// 检查是否有视频帧
const frameCount = await pageA.evaluate(() => {
const video = document.getElementById('remote-video');
return video.webkitDecodedFrameCount || 0;
});
expect(frameCount).toBeGreaterThan(0);
}, 30000); // 超时设为30秒
afterAll(async () => {
await browser.close();
});
});
你想想看,如果没有端到端测试,你怎么知道信令服务器和客户端之间的协议是否一致?我曾经踩过一个坑:信令服务器返回的JSON字段名是camelCase,但客户端代码写的是snake_case。单元测试全过了,端到端一跑,直接报错。从那以后,我坚持每个PR必须跑一遍端到端测试。
三、压力测试:看看系统能扛多少人
WebRTC的压力测试,和普通HTTP压力测试完全不同。HTTP压测是「发请求、等响应」,WebRTC压测是「建立连接、维持流、模拟丢包」。说白了,更复杂。
我常用的工具是 K6 配合 WebRTC的k6扩展。K6本身是压测工具,加上WebRTC扩展后,可以模拟多个虚拟用户同时建立PeerConnection。
压力测试的核心指标:
| 指标 | 说明 | 健康阈值 |
|---|---|---|
| 并发连接数 | 同时建立的PeerConnection数量 | 根据服务器配置,一般建议 < 1000 |
| 连接成功率 | 成功建立连接的比例 | > 99% |
| 平均连接耗时 | 从发起连接到ICE完成的时间 | < 5秒 |
| CPU使用率 | 服务器端CPU占用 | < 80% |
一个简单的K6脚本示例:
// stress-test.js
import { check, sleep } from 'k6';
import { WebRTC } from 'k6/x/webrtc';
export const options = {
stages: [
{ duration: '30s', target: 50 }, // 30秒内升到50并发
{ duration: '1m', target: 50 }, // 维持1分钟
{ duration: '30s', target: 0 }, // 30秒内降到0
],
};
export default function () {
const peer = new WebRTC.PeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
});
const result = peer.connect('wss://your-signaling-server.com/ws');
check(result, {
'connection established': (r) => r.status === 'connected',
});
sleep(10);
peer.close();
}
四、性能基准:量化每一次改动
性能基准测试,不是为了「通过/不通过」,而是为了「对比」。每次代码改动后,跑一遍基准测试,看看延迟、吞吐量、丢包率有没有恶化。
我常用的基准测试工具是 Google Chrome的chrome://webrtc-internals 配合 WebRTC Stats API。通过编程方式采集RTCStatsReport,然后输出到文件或数据库。
采集的关键指标:
- 端到端延迟:从发送端采集到接收端播放的时间差
- 丢包率:packetsLost / packetsReceived
- 抖动:jitter值,单位毫秒
- 编解码时间:encodeTime / decodeTime
- 帧率:framesPerSecond
代码示例:
// benchmark.js
async function collectStats(peerConnection) {
const stats = await peerConnection.getStats();
const report = {};
stats.forEach((stat) => {
if (stat.type === 'inbound-rtp' && stat.kind === 'video') {
report.packetsLost = stat.packetsLost;
report.jitter = stat.jitter;
report.framesDecoded = stat.framesDecoded;
}
if (stat.type === 'outbound-rtp' && stat.kind === 'video') {
report.packetsSent = stat.packetsSent;
report.encodeTime = stat.avgEncodeMs;
}
});
return report;
}
// 每5秒采集一次
setInterval(async () => {
const stats = await collectStats(peerConnection);
console.log(JSON.stringify(stats));
}, 5000);
我个人习惯把基准测试结果存到InfluxDB,然后用Grafana画成折线图。每次提交代码后,自动跑一遍基准测试,如果延迟增加了20%以上,CI直接失败。这样能及时发现性能回退。
五、知识体系总览
下面这张图,是我对WebRTC测试体系的总结。你可以把它当作一个检查清单:
看到这张图,你应该能理解:单元测试保底,端到端测试保通,压力测试保量,性能基准保优。四个层次层层递进,缺一不可。
好了,这一章的内容就到这里。测试是个慢功夫,但值得投入。你想想看,一个线上事故造成的损失,可能够你写一年的测试代码了。
公众号:蓝海资料掘金营,微信deep3321