张大胖和单元测试
敏捷运动
张大胖的公司正在掀起一场轰轰烈烈的敏捷运动。
似乎一夜之间, 每个人的嘴边都挂起了scrum, xp , tdd,user story 等敏捷词汇。
公司要求, 每个开发人员都必须掌握单元测试这个非常基本的敏捷实践,为此公司还专门组织了单元测试培训。
张大胖自然不能落后, 他热情满满地参与了培训, 在会议上了解了单元测试的各种好处, 学会了JUnit这个简单又强大的测试工具,理解了一个测试用例执行之前会调用"Setup"方法做必要的初始化, 执行完毕以后会调用 “teardown”做清理。
培训中还特别提到了如何做Mock对象, 这让张大胖印象深刻: 原来那些不存在的或者难于new 出来的对象可以使用Mock工具(EasyMock, jmockit等) 来“造假”啊!
公司还专门定义了什么是好的单元测试:
1. 单元测试是“白盒测试”, 应该覆盖各个分支流程、异常条件
2. 单元测试面向的是一个单元("Unit"), 是java中的一个类或者几个类组成的单元。
3. 单元测试运行一定要快!
4. 单元测试一定是可重复执行的
5. 单元测试之间不能有相互依赖,应该是独立的。
6. 单元测试代码和业务代码同等重要, 要一并维护。
培训结束了,张大胖信心满满: 写单元测试简直就是小菜一碟!
精明的项目经理趁热打铁、不失时机对大家提出了要求: 兄弟们,我听说隔壁组定了一个目标, 单元测试的代码覆盖率要达到70% , 我们一定要超过他们, 我们的覆盖率要达到 75% !
2
困惑
张大胖磨拳擦掌,准备大干一场 , 他打开了Eclipse, 开始查看自己之前写的代码,准备全部加上单元测试用例, 搞一个代码100%全覆盖, 勇夺覆盖率冠军!
可是第一个小模块就把张大胖给难住了, 你看看这代码, action调用service, service调用 dao, dao里都是sql, 简单的增删改查,这有什么可测试的?
唉,为了代码覆盖率,硬着头皮写吧,按照分层测试的原则, 测试action的时候把service给mock出来,测试service的时候把dao给mock出来......但张大胖总觉得不太对劲,总是觉得自己是在测试框架,而不是测试业务代码。
当然,张大胖也遇到了一些有一定业务逻辑的模块,但是这些模块患有重度依赖症,依赖10几个其他模块的接口,为了单独测试他们,张大胖废了九牛二虎之力,做了10多个mock对象才把依赖给解除开。
但是mock对象过多, 协调他们进入一致的状态来正确执行测试十分困难: 当接口1处于A状态,并且接口2处于B状态, 并且接口3处于C状态..... 接口10处于X状态时, 测试用来才能正确执行, 唉,真是不容易啊。
一天下来, 这个mock就把张大胖弄的头晕。
大胖感慨的想: 敏捷教练们大谈单元测试的好处, 可用来展示的都是非常简单的例子, 现实的代码要复杂的多啊。
第二天便发生了状况, 同组的小李改了业务代码,却忘记修改单元测试代码,导致很多单元测试失败,那一大片醒目的红色让人触目惊心。
小李去修改单元测试,可是怎么都读不懂大胖的测试用例,他不满的说: ”大胖,我觉得你的测试代码比业务代码都要复杂啊, 你是怎么写出来的?“
大胖委屈的说: “别说你了, 看看这么多的mock 对象, 我自己都头晕, 这该怎么办呢?”
小李也没辙, 这样下去, 别说业务代码了, 光是维护单元测试就把人给累死了。
3
讨论
他们俩人去找项目经理诉苦, 经理说: 有不少人都在说这个问题,我们开一个会议来讨论下吧!
项目经理召集了几个经验丰富的骨干专门来讨论这个问题。 他先做了一个开场白:
“我们现在的单元测试进行的如火如荼, 我们组做的还是非常好的, 其他组遇到的像”单元测试运行慢”, “单元测试不能重复执行, 换一台机器就出错”,"单元测试互相依赖" 等常见问题我们组基本没有, 我们遇到的主要问题有两个:
1. 张大胖和小李反映单元测试代码过于复杂, 难于维护, 张大胖那个mock了10多个接口的测试想必你们也看到了
2. 大家认为有些非常简单的增删该查没必要去做单元测试 。
如果单元测试维护成本越来越高, 我担心大家会慢慢的抛弃他们, 大家一块儿来想想办法吧”
老梁说: “我做单元测试的时间比较久了, 我认为如果测试代码需要很复杂的Setup 才能开始测试, 那就反映了一个问题:我们的业务代码接口设计有问题! ”
张大胖佩服的说: “老梁真厉害,一下子就看出了问题的本质, 我当时只是想着怎么把测试搞定,没想到是业务代码的问题”
老蔡也附和说: “说的没错, 简单的单元测试谁不会啊? 关键还是要处理现实中的遗留代码, 我们之前有些模块的API设计确实是有问题, 看来到了重构的时候, 我们趁着这次东风把一些不好的设计提升一下, 这样测试肯定会变的简单。 大胖, 小李, 重构的过程基本上就是一个重新设计的过程, 这可是个学习的好机会啊”
大胖说:“ 我也了解过一些重构, 正好练习一下。 ”
大家都表示同意, 只是项目经理为难的说: “重构可能会很费时间, 还有可以引入新的bug , 测试也要介入, 这样的话会不会影响我们的进度啊?”
老梁说: “这也是没办法的事情 ,如果不重构, 不要说单元测试了, 就连我们的代码都可能今天被贴个补丁, 明天再被贴个补丁, 慢慢的腐化下去, 越来越难以维护, 最后无人能懂, 无人敢改,维护成本可是天价了。 ”
大胖说: “没事, 为了学习 ,我愿意加班来做”
经理赞赏的看着大胖,心说: "这孩子不错,挺上进的, 年终考核的时候得倾斜一下"
“好吧,就这么决定” 经理说,“大胖,相关的重构你来做, 有问题的话请教老梁和老蔡吧”
“那增删该查到底要不要测试?”
老梁说:“我那天仔细思考了一下,这些代码没有逻辑, 就是一层调用一层, 我觉得单元测试必要性不大”
“如果不测试,怎么保证正确性呢? 我们的代码覆盖率也肯定达不到75%了” 大胖说
“没有必要特别追求代码覆盖率, 要不这样” 老蔡说,“对于这样的代码, 咱们就不要写单元测试了, 还是通过自动化的功能测试来覆盖得了!”
“嗯,我觉得这样可行, 功能测试可以有开发写, 也可以由测试来写” 项目经理说
老梁说: “同意, 还有一点建议是, 之前我们都是程序员在自己机器上跑单元测试, 以后咱们要把运行的过程加入到自动化的Build当中, 包括单元测试和功能测试,作为重要的质量保证。”
4
一年以后
经过团队艰苦的努力, 张大胖的项目组通过单元测试和功能测试编织起来了一张密密麻麻的安全大网,不管是多么微小的变动, 都有测试用例做回归测试, 现在大家需要改动起代码时比原来自信多了。
更重要的是, 关键的核心代码做了重构,接口API变的越来越好,代码易读易维护,没有了脏代码的羁绊, 新需求实现起来也更加容易。
张大胖感慨的说: “实现了自动化的单元测试, 我们确实变得更敏捷了。”
当别人问他是怎么做单元测试的, 张大胖说: “告诉你吧, 关键就在于如何处理遗留代码。”
后记: 我在文章中描绘了一个实现自动化单元测试的理想项目组, 但实际情况下, 很多公司都是浅尝辄止,没有精力和时间去完成代码的重构, 单元测试变成了一个鸡肋, 最后还是被废弃掉,回到老路子上去了。
你看到的只是冰山一角, 更多精彩文章,参见《码农翻身2016文章精华》
有心得想和大家分享? 欢迎投稿 ! 我的联系方式:微信:liuxinlehan QQ: 3340792577
公众号:码农翻身
“码农翻身”公众号由工作15年的前IBM架构师创建,分享编程和职场的经验教训。