开发UT实现:朱浩禹
测试UT实现:韩小晴、余轶斐
FT其他参与者:陈智、熊琦楠
(名字均按首字母排序)
一、为何要做单元测试?
腾讯视频Mac项目研发质量较高,为了进一步提高代码质量,增强代码健壮性,从根源最早发现问题并及时解决,单元测试是个值得去实践的方向。
二、单元测试SDK选型
本次将范围锁定在Mac腾讯视频开源库XXXOpenSource,相比主工程,XXXOpenSource的优势在于
1)开源库工程,包含了所有腾讯视频依赖的第三方开源组件工程和源代码;
2)无依赖关系,不像mainProject相互依赖关系复杂;
3)独立编译,以framework形式存在。
本次选用XXXOpenSource里的pluginKit从开发侧做UI,弹幕SDK从测试侧做UT。
三、开发侧单元测试实践——PluginKit
开发UT最初问题:
1)覆盖率不全,缺少message相关ut case;
2)异常处理不足,只有通用参数的校验。
通过增加case和异常逻辑校验,最终覆盖率提升到80.3%:
四、测试侧UT
OC 单元测试整体流程梳理如下:
step 1)定义测试范围:
选型:本次测试侧做的单元测试选用了Mac腾讯视频项目里用到的一个独立的弹幕SDK进行单元测试,采用基于XCTest的测试框架。
选型原因:弹幕SDK 是一个灵活,轻量级的弹幕渲染库,是个独立的组件,和庞大的腾讯视频主工程没有依赖关系。
step 2)编写测试类和方法:
测试用例编写三部曲:
根据上面对代码的分析,有两个负责控制的类,一个主要对外提供接口,一个控制完成主逻辑。测试用例的编写先从这两个控制类入手,对公有函数设计测试case。
case举例:
1、danmuView:一条弹幕所在view ,check frame & origin 具体校验点如下:
1)高度:固定值
2)X坐标:小于canvasViewWidth
3)Y坐标:屏幕内
根据这三个校验点,写出对应的单测用例:
2、特殊弹幕时(如emoji表情,挂件),danmuView的状态 frame origin
构造特殊弹幕,针对上面三个检查点再进行一次检查。特殊弹幕举例:
3、检查activedHolders (当前活跃弹幕):
activedHolders 是字典类型,它的key是NSString类型,value是由Holder类型组成的数组,此数组和当前活跃弹幕一一对应。从activedHolders中获取当前正在活跃的弹幕view,对view的属性进行校验。
4、弹幕速度异常校验给弹幕速度设置正常值和异常值,在这两种情况下校验弹幕view的有效性。
五、过程问题与解决方案
1、如何更好的阅读开发代码
通过梳理调用关系,画出UML类图,便于理清代码结构和继承关系,理清思路。
2、如何选择testcase编写入手点
首先查看工程的对外接口public函数,再找到代码的核心控制类,从此类的接口函数入手。
3、测试覆盖率的提升:50%-75.7%-76.5%
本单测覆盖率的瓶颈在于:
1)UI操作 比如鼠标操作和单击、双击事件;
2)私有函数。
最初对外接口函数设计的用例检查只有50%的覆盖率,通过逐个分析没调用到的函数和语句,构造调用场景,将覆盖率提升到75.7%,最后继续深挖,构造分支条件,提高分支覆盖和条件覆盖 ,把整体覆盖率提升到76.5%。
4、设计case中的难点:解决单线程,没有回调和通知:加定时器
manager初始化设置定时刷新(1s 60次),每次刷新后触发回调:
控制类中触发更新:
需要针对刷新后的弹幕进行校验,但是由于初始化是单线程,定时器刷新后的结果在原代码里没有回调和通知,初始后就像脱缰的野马,线程内之前获得只能得到初始化时候的弹幕状态。
解决方法:
用例中加入定时器,3s后通过定时器调用一个新函数timerPoll,在新函数里完成检查。
设置总时间10s,10s后在当前线程结束runloop
5、设计case中的难点:多条case同时用NSTimer定时器会发生crash
多条TestCase中都启用了NSTimer定时器,在指定的时间内重复调用以实现循环生成danmu的逻辑,但各TestCase执行完循环任务后timer未释放掉,测试代码造成了必现崩溃。
timer不能同时重复调用,如果在不同case里都启用NSTimer,必须在各自case及时释放。
6、SDK本身需求不明确,UT发现的问题不好界定
明显的边界问题认为是bug,其他的异常处理不当认为是优化点,均可以通过单测运行结果进行判断。
六、发现的问题
【问题1】 speed为负数时,X坐标范围出现报错:
XCTAssertTrue(danmuViewFrame.origin.x < CanvasViewWidth);——Fail
调试后发现danmuViewFrame.origin.x远超过canvasViewWidth 。
【原因】下图框内需要传入时间参数,应为非负数,但当speed为负数时,参数整体为负。
【解决】开发需要对speed参数进行合法校验
【问题2】codereview的问题:
NSMutableDictionary中的元素赋值:
对于NSMutableDictionary要使用原生函数-setObject:forKey:而不是KVC-setValue:forKey 。它们的区别是,
setObject: forkey: 中 object 是不能够为 nil 的,不然会报错。setValue:的value可以为nil。
【解决】更推荐写 [dictionary setObject:holderArray forKey:identifier];
【问题3】函数可测性差,控制类接口函数很多返回空值,无法做有效校验
七、可测性的提升
通过本次的单元测试尝试,发现不是所有的代码都可以做单元测试,本次单测覆盖率的瓶颈之一——UI操作无法覆盖,这种是不适合做UT的。
另外,开发代码需要有足够的可测性,函数返回值要能够校验,且需要更清晰的代码架构和优美的设计模式,来提升可测性,要设计更严谨的接口函数,便于进行校验和有效断言。
后期我们会根据每个维度陆续写相关的测试文章,如果你有兴趣,请关注我们哦。
长按指纹识别图中的二维码,获取更多测试干货分享!
将我们公众号置顶
不会漏掉我们的原创干货哦!