28、测试与调试:单元测试、UI测试、网络模拟测试、日志系统
说实话,做WebRTC视频会议开发,最让人头疼的不是写代码,而是调试。音视频这东西,牵一发而动全身。网络抖一下,画面卡了;内存没管好,App闪退了;信令发慢了,通话建立失败。我早期做的一个项目,上线前自测一切正常,结果用户一多,各种诡异问题全冒出来了。
后来我学乖了。测试和调试必须从一开始就嵌入到开发流程里。这一章,我就把我在Android WebRTC项目里积累的测试调试经验,掰开了揉碎了讲给你听。
单元测试:把每个模块锁死在牢笼里
单元测试,说白了就是给每个函数、每个类单独做体检。你写了一个信令解析器,能不能正确解析JSON?你写了一个网络状态机,状态切换对不对?这些都要测。
我个人习惯用 JUnit + Mockito 的组合。JUnit负责跑测试,Mockito负责模拟那些难搞的依赖——比如WebRTC的PeerConnection,你总不能真的去连一个远端吧?
举个例子,测试信令处理逻辑:
class SignalingManagerTest {
@Mock
private WebSocketClient mockWebSocket;
@InjectMocks
private SignalingManager signalingManager;
@Test
fun testHandleOfferMessage() {
// 模拟收到offer
val offerJson = """{"type":"offer","sdp":"v=0..."}"""
signalingManager.handleMessage(offerJson)
// 验证是否调用了setRemoteDescription
verify(mockWebSocket, times(1)).sendMessage(anyString())
}
}
这里有个坑,我踩过。WebRTC的很多API是异步的,比如setRemoteDescription。你如果直接在主线程里跑测试,回调还没执行,测试就结束了。解决办法是用 CountDownLatch 或者 RxJava的TestScheduler 来等待异步结果。
UI测试:别让界面骗了你
UI测试,很多人觉得不重要。你想想看,视频会议App里,按钮点下去有没有反应?画面渲染出来是不是黑的?这些肉眼可见的问题,如果靠人工去测,一次两次还行,每次发版都测一遍,谁受得了?
Android上做UI测试,我推荐 Espresso 和 UI Automator。Espresso适合测单个Activity里的交互,UI Automator可以跨App操作,比如模拟来电。
但WebRTC的UI测试有个特殊之处——视频流是动态的。你不能像测一个TextView那样,直接assert文本内容。那怎么办?
我常用的方法是:检测SurfaceView的渲染状态。WebRTC渲染视频时,SurfaceView会持续收到帧数据。你可以写一个自定义的IdlingResource,等SurfaceView的帧数超过某个阈值,再断言UI元素的位置和大小。
class VideoRenderIdlingResource(private val surfaceView: SurfaceView) : IdlingResource {
private var isIdle = false
override fun getName() = "VideoRenderIdling"
override fun isIdleNow() = isIdle
override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback?) {
surfaceView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
if (surfaceView.width > 0 && surfaceView.height > 0) {
isIdle = true
callback?.onTransitionToIdle()
}
}
}
}
网络模拟测试:在实验室里复现真实网络
做WebRTC开发,最怕的就是「我这边好好的,用户那边卡成PPT」。网络环境千差万别,WiFi、4G、弱网、高延迟……你不可能每个场景都去实地测试。
所以,网络模拟测试是必备技能。Android上我常用 Facebook的Augmented Traffic Control(ATC),或者直接用 Linux的tc命令 来模拟网络条件。
具体做法是:在测试手机上开启一个VPN服务,通过ATC控制这个VPN的带宽、延迟、丢包率。然后跑自动化测试,验证在不同网络下,视频分辨率、码率、卡顿次数的变化。
我曾经遇到过一个案例:在WiFi下通话正常,但切换到4G后,视频经常卡住不动。后来用ATC模拟了4G的延迟和抖动,发现是拥塞控制算法对网络变化反应太慢。调整了 GoogCcNetworkController 的参数后,问题解决了。
这里给出一份常用的网络模拟参数表:
| 网络场景 | 带宽 | 延迟 | 丢包率 | 抖动 |
|---|---|---|---|---|
| 理想WiFi | 50 Mbps | 10 ms | 0% | 0 ms |
| 普通4G | 10 Mbps | 50 ms | 1% | 20 ms |
| 弱网3G | 1 Mbps | 200 ms | 5% | 100 ms |
| 极端丢包 | 2 Mbps | 100 ms | 15% | 50 ms |
日志系统:调试的最后一根救命稻草
单元测试和UI测试覆盖了正常流程,网络模拟测试覆盖了异常网络。但总有一些bug,是测试环境复现不了的——比如用户手机内存不足、系统杀进程、或者某个奇怪的Android厂商定制ROM。
这时候,日志系统就是你唯一的线索。WebRTC本身有丰富的日志输出,但默认是关闭的。你需要手动开启:
// 开启WebRTC内部日志
Logging.enableLogToDebugOutput(Logging.Severity.LS_INFO);
// 设置日志文件路径
Logging.setLogFile("/sdcard/webrtc_log.txt");
但光有WebRTC的日志还不够。我习惯在App层也打一套日志,记录关键事件的时间戳:
- 信令连接建立时间
- ICE候选收集完成时间
- 视频首帧渲染时间
- 网络切换事件
- CPU和内存使用率
这些日志配合WebRTC内部的 RTCStats,基本能定位90%的问题。RTCStats会输出每秒钟的发送码率、接收码率、丢包率、往返时间等指标。你可以把这些数据上传到后台,做可视化分析。
知识体系总览
下面这张图,把本章的测试调试体系串起来了。你可以把它当作一个检查清单,开发完一个功能后,对照着看看哪些环节还没覆盖到。
嗯,这套体系搭建起来后,你会发现bug的定位速度快了很多。以前可能要花半天去复现一个偶现问题,现在看看日志里的时间戳和RTCStats曲线,基本就能锁定是哪个环节出了岔子。
最后说一句:测试不是负担,是投资。你在这上面花的时间,会在项目后期十倍百倍地还回来。