其它几篇的链接:
Google一年工作感受(1) Google一年工作感受(2) Google一年工作感受(4)这次我们继续来聊聊工作内容中的测试,这篇文章可能带有比较多的感情色彩,主要是情绪的宣泄,干货不多,don’t take it seriously。
工作内容
测试
像之前说的,而且谷歌非常看重代码质量,因此对测试也是非常重视。我个人感觉写测试对我来说蛮头痛的,每次都要花数倍于开发的时间,所以每次要写测试的时候就想拖着。这部分基本全是我对谷歌的吐槽,或者说对我所在的组的吐槽。我觉得测试头痛主要有几方面原因:
之前的测试质量不高(为什么前人写的代码质量这么差,到了我写就要严格要求了?)。经常改了这里,一些别的地方莫名其妙地崩了。于是就得修改好几个测试用例。
测试非常不容易debug。我觉得这点不能代表整个谷歌,应该只是少部分组的情况。我们组的很多代码不能用IDE打断点去调试。在遇到问题时有的时候只能用过一层层地加log才能发现问题。如果能打断点,在call stack每一层可以看到当前的变量值,如果不是当前函数调用出了问题,那就step over,看下一个函数调用的情况。由于整个过程只需要一次编译一次执行,很快,一两分钟,就能定位到问题所在。而一层层加log的问题还在于,刚开始是还不知道是哪个logic path出了问题,只能在当前函数中使用二分法找到有问题的函数调用,然后在该函数中再使用二分法找到上一层有问题的函数调用,以此类推,做个好几层才能最终定位问题:“是某个变量的true false不对,导致一系列的错误”。而这整个过程需要的编译和执行次数就等于函数调用的深度,which might be very deep。
测试时间很长。有的时候一个测试需要跑好几分钟甚至十几分钟。虽然跑一次的代价还可以接受,但是联系上一点,每次跑完发现哪里出了问题就得重新加log,继续跑,来来回回十几次定位问题花的时间就非常长了。做这种事情就得学会控制心态,有几次我一整天就在做这种事情,做着做着就觉得我今天什么进度都没有,陷入在这种重复无意义的事情里,而且还看不到头,不知道什么时候才能在call stack中找到源头,然后心态就渐渐烦躁,心中不断地再骂。而且更让人崩溃的是,大部分情况都不是业务逻辑写得不对(如果是业务逻辑不对通过两三次打log就能很快发现),而是测试环境和生产环境的天然不同所导致的问题,好多次打log都打到了测试框架的源码里才能发现,而代码库很庞大,函数调用栈很深,log打到框架源码里意味着起码得十几二十层。这一切的意思就是:你花了一整天想要修复的test case,是一个在生产环境绝对不可能出现的logic path,而你还不能不修,否则这个测试不通过代码都交不上去。于是得想各种办法绕过这个深层次的配置。有的时候这个过程又会产生新的unexpected behavior,然后再陷入新的一轮打log,生生不息,源源不绝,去修一个nonsense problem。
有的时候reviewer要求非常严格。之前的代码是别的组维护的,很多地方都测试不完全。有的时候我看到代码库里在某些地方没有加测试,于是我有时改了类似的地方就也想偷个懒 (当然是我个人也觉得测试不太重要,可有可无的地方,如果是那些critical logic path,我当然会自主地写测试),如果能拿到LGTM,那我这个CL就算是混过去了。然而大部分情况都会被抓住,然后花半天一天甚至更长的时间,加一个非常傻逼的测试用例。就相当于你写了个几行的简单操作,你需要花几百行的代码setup测试环境,mock、spy、还有相应的object initialization,然后还有可能会遇到(3)中的问题。毕竟这个测试之前没有,所有的坑没人踩过,你得走过(3)中的心路历程才能加好。我心中就会默默嘀咕:“你轻巧地随便一句话,我就要花一整天甚至几天的时间为你这句话买单,你要是相加倒是自己加啊!”更气人的是,有的时候我已经找到一个不错的方法绕过(3)中的问题,终于让你想要加的测试跑通了,reviewer看着觉得奇怪(废话,我花了这么久dig out的问题,然后想出的相应的walk around,岂是你短短几分钟review就能给参透的),不知道某个suggestion可不可行,轻巧地suggest,(在他来说是suggestion,在我看来是mandatory的,毕竟不照着改好他是不会给LGTM的),然后花了半天一天试好之后发现不行,还是得回到我之前的做法。顿时就对他很生气:“Suggestions from you are cheap and of less value, but I need to spend days to pay your shit off!”。
不建议用mock相关的(很方便的)测试框架。我一翻开以前的代码,全是mokito写的(为什么又到了我来这个组的时候,写测试就要砍掉这条左膀右臂)。除非没有啥别的好办法,不然你用mock的测试,肯定会被质问一番。原因据说是“安卓系统里的Context,Service等等都有很复杂的逻辑,如果mock了相关的object,很多地方的代码实际上就被跳过了,并不能很好地测试整个logic path,测试就不够具有代表性”。关于这点我其实是持有保留态度的,毕竟他们所说的这点应该是系统测试(System Test)的职责,对于单元测试(Unit Test),应该只关注与某个代码局部,其它和当前测试无关的地方就应该被隐藏。由于我人微言轻,而且也可能也有我理解不够全面的地方,就先不争论了。有了这个背景,于是很多本来很容易测试的地方就会变得束手无策,或者需要绕很大的弯才能完成。比如mokito里面有一个verify,可以验证某个函数是否被调用了。如果我只需要测试某个函数是否被调用,而不需要关心其执行结果的话,我就可以mock那个函数所在的object,最后再测视里调用verify方法就可以了。如果我不能用这个功能,我只能去看看那个方法调用会产生什么影响,再想相应的对策。比如说那个方法调用会设置某个变量,我只能在测试中取出那个变量,看看它的值有没有变话。而取出变量这个操作,只能通过加getter方法或者提升该变量的可见度为public来实现。最让我惊讶的一点是,提升变量可见度为public他们竟然有的时候会觉得ok,反而用mock是不ok的。关于这点,我真是完全不能苟同啊。
以上就是为什么我觉得在当前组里写测试是很头痛的原因。往往业务逻辑写一小时,测试可能需要花几天到一周,而且很多测试在我看来价值不大。
在腾讯的时候关于测试我也觉得很奇怪,只有黑盒测试,完全不用写任何单元测试。我当时一年一行测试都没写。可能当时更在意的是开发速度吧。。。毕竟赚钱才是KPI,代码质量是KPI?不存在的。不过这可能也不具有代表性,毕竟我不相信腾讯作为国内的老大哥,这么不在乎测试。可能跟我处在的游戏行业有关?
发布
又不知不觉写了俩小时了,下次再写吧。