架构之路之单元测试
作者:网络转载 发布时间:[ 2015/12/21 14:34:57 ] 推荐标签:软件测试 单元测试
在带队的过程中,性能的问题还比较好解决,消极的想法,“好啊,多一事不如少一事,你让我不管还不简单?”,但要求写测试代码,那炸锅了!以我的经历,“测试驱动”是一个具争议的话题,没有之一。吹捧者和反对者泾渭分明,而且都有大量的论据和证明。记得博客园曾经有一篇文章,大意是:“公司付钱给你不是让你写测试代码的”,下面一片狂赞。
在我自己的项目开始的时候,我是放弃了测试驱动的(呵呵,还找到了原文),里面总结得很准确,大的原因是“懒”。但后让我下定决心开始“测试驱动”实践的,是我一次花了两天一夜都没调出一个Bug,垂头丧气筋疲力尽之后,无可奈何的接受了这个现实:测试还是很有用的——即使是自己写的代码。我之前的系列博客,也已经反复的强调,架构是一种“无奈”,是现实是问题驱使你去做一些其实你本来不想做的事情。你无法理解一些看起来像“脱了裤子放屁”一样的行为,通常只是因为你没有遭遇过那些现实那些问题。(看看,大学能教你这些东西么?)
即使你没有多少开发经验,你也应该能够想象,单元测试大的问题,是它需要花时间花精力去写,那么这个花费是否值得呢?这还是由你架构的目标决定的,或者你的需求决定的。如果系统是一次成型交付使用,此后几乎不会更改的,那么一次性的手工测试够了;但如果你的系统是会被“千锤百炼”的不断折腾修改的,那么这个测试是很有必要的。简单的考虑:每一次更改,我都要手工测试一次;那还不会如我多花点时间,弄个“自动化”的东西出来。单元测试,其实可以理解为一种自动化的测试工具。
但是“自动化”的理由还远远不够。因为你马上想到的,每一次需求变更代码调整,测试代码也得相应的改呀?没有测试代码,我只需要改开发代码;现在有了单元测试,我还得再改测试代码。本来我只维护一套代码,现在我凭空增加了一套代码也需要维护,这不是增加了维护成本,不是和你“可维护性”的架构目标背道而驰了么?是一套代码好维护呢,还是两套代码更好维护?
这是一个非常好的问题,适用于很多情景(比如分层架构,你说分层解耦,实际上还不是一改得从UI层改到数据库,每一层都得改?)。我能给出的回答大概有:
一、无论有无单元测试,开发代码进行修改之后,是不是都要进行测试?没有单元测试,并不代表你的代码不需要测试了,只不过是你手工的去测试了一遍而已。切记:你的工作并不只是把代码写出来而已!
二、进行手工测试,和更改单元测试,两者的耗费比,会根据测试重用的次数而变化。一次手工测试可能需要5分钟跑完,更改单元测试代码可能需要20分钟,但如果这测试会跑100遍,单元测试完胜手工测试。
三、你说,哪里哟?什么功能会改100遍?我没说你的功能会改100遍,我说的是测试会跑100遍。有区别么?你可能还在犯迷糊,是吧?好吧,我们讲个故事。
有一个小伙子,他很不情愿写测试代码。老板拿他没辙啊,也没那么多精力和他磨牙,于是老板自己写单元测试。这小伙子的代码提交之前要review,老板总能一次次的找出它代码的问题。他改的是登录,老板告诉他积分系统被他改出了问题;他又去改积分,老板又告诉他消息通知系统被他改坏了;他又去改消息系统,老板告诉他登录还是有问题……于是他崩溃了,“这TM什么一个烂系统”?终他终于回过神来了,为什么老板总能知道这里的改动会影响那里呢?老板的思维有这么严谨?老板躲在一旁偷笑,不告诉你,“其实我是跑了一遍单元测试而已”。
这个老板是我。我故意的,不一次性的告诉他所有的问题,要这样一次次的折磨他,让他的痛苦能刻入骨子里去。后,我还要问他:
你现在对你的代码是不是还那么自信?
如果没有我的review(我也是靠单元测试),你能不能发现这些问题?
如果我们的项目已经部署到生产环境,而且你的改动带来的破坏没有被发现上线了,会带来什么样的后果?
这一次,他服气了。后来他用NUnit用得麻溜麻溜的。每一次改动,如果有意想不到的未通过test case,他都会很激动的给我张截图,顺便发发牢骚。我微笑不语,那种满屏绿灯通过的踏实,和意外爆出红灯之后的惊喜,没有经历过的人,是无法体会的。
所以其实当对象间的关系变得越来越错综复杂,像一张密密麻麻的网一样之后,一个局部的改动很有可能会触发极其复杂的连锁反应。所以为了保险起见,所有可能相关的组件都应该进行测试(所谓的“回归测试”)。这时候如果只有纯粹的手工测试,会面临两个问题:
难以确定测试的边界(那些部分可能会被影响),这得我们脑袋凭空硬想啊,兄弟!
极大的测试耗费。而且这种耗费是相当的无聊繁琐伤人心的——没人愿意做这种事。据说所知,现在很多公司测试人员的工资已经比开发人员还高了。为什么?简单枯燥无聊,没人愿意做啊!
好的,我假设你已经认识到了单元测试的重要性,并开始摩拳擦掌,跃跃欲试。接下来我得给你泼一大瓢冷水:单元测试不是那么好写的!从某种程度上讲,写单元测试比写开发代码还难。难得我工作的所有公司,没有一家有过成功的案例。
大概是几年前,我在公司修bug,老大告诉我,“你这个功能比较核心,跑一下单元测试吧”。
“哇塞!我们有单元测试?”一种高大上的感觉迅速弥漫全身,终于见到传说中的Unit了!
捣鼓了一会,能跑了,试试看——我的个妈呀?怎么这么多红灯?我真被吓住了,这都是我的改动造成的?
老大是老大,不慌不忙,“数一下有多少个通不过?”
“啊?”我以为我听错了,数多少个通不过有什么用?得把他们全部弄通过啊?!
搞了一会儿,才终于弄明白了,把我改动前后的代码分别跑一遍,对照一下通过失败是不是一样的,只要是一样的,OK了。比如,以前是8个通不过,现在还是8个通不过,这样可以了!
我一直不明白,为什么不把那8个通不过的单元测试给弄成通过呢?这样摆着究竟算什么?直到我自己开始写单元测试。坑爹啊!到处都是坑,跳出小坑进大坑,大坑下面还连着小坑,前面是坑后面是坑,一堆一堆的连环坑……
单元测试写出来容易跑过难!而且跑不过的原因还不是你的开发代码逻辑错了,而是测试环境/数据出问题。要测试,一定要有数据,这个数据的构建,完全不是我们所想象的那么简单。以我们创业家园项目里的积分系统为例,假设一个简单的需求:博客被点赞,博客的作者应该获得一定积分,该积分数量是由点赞人目前所有的可用币转换而得来的(已简化,具体可参考文档:积分)。要准备的数据有:博客一篇,要有作者,作者已有积分;点赞人一名,有一定数量可用币。如果只是这样,还可以接受,但其实下面会有一堆的问题:
作者的积分从哪里来?我们的开发代码,出于封装的考虑,用户的积分是只读的,你单元测试怎么设这个值?
要么写代码,模拟作者通过其他行为(发布文章回答问题等)获得积分,这将开启新一轮噩梦;
如果用Mock或者反射强行设置,事实上省略了作者获得积分的历史,所以用户“积分历史”为null,之后对其“加积分”时,会报异常。
更坑的是,你以为你什么都处理好了的时候,你突然悲哀的发现,这个博客得首先“被发布”,而博客一经发布,其作者获得了一定数量的积分,所以你以前设置的积分又变了!
……
点赞人的可用币,同样可能遇到类似的问题。可用币怎么设置,设置之后会不会在跑测试时被意外更改?
点赞的行为,被封装成一个方法,运行这个方法,会检查点赞人之前是否已经对该文章点过赞,所以还应该有一个“点赞历史记录”,哪怕是空的,都得new一个,否则空异常
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11