测试驱动之我思我为
写过长代码复杂逻辑的同学可能会意识到,在同一个类中进行业务逻辑堆积,承担的职责过重,那么很可能会造成方法调用链过长,条件分支过多,代码复杂,这会使我们对全局的掌控能力降低,思维负担加重,从而对程序的信心不足,甚至会对后期维护产生抵触情绪。
背景
最近参与了新系统的开发,写了一个功能,展示收银台,整个业务逻辑层1000+的代码量,业务复杂,依赖系统多,开发时间紧(一周)。
第一版,业务逻辑层全部写在了一个类中,几十个方法,主干代码分支众多,分支中又有系统调用,真是主干长,枝叶茂盛,看着费劲,由于急于上线(有单元测试覆盖率要求),单元测试简直瞎编乱造,这样的代码将来不管谁来维护都是噩梦。
第二版,决定重构,由于系统已经上线,代码更不敢随便修改(部分原因是单元测试写的不到位造成的),在不改变代码的前提,把当前的大类,进行了切分,方法搬移到了不同的类中,拆出了十几个类。每个类单独的进行单元测试,有了单元测试后,终于可以放心地对代码进行重构了,使用了门面模式进行分层设计、策略模式和责任链模式处理条件分支,最后整个代码清晰度有了很大的提升。
现在,回想起来,深刻的认识到:单元测试是造就编码自信的不二法门。
单元测试以方法为基本单元进行测试验证。针对一个被测试的单元,通过给定的相关输入,验证其输出及过程的正确性。
工程上的一个共识是,如果程序的每个模块都是正确的、模块与模块的连接是正确的、那么程序基本上就会是正确的。
单元测试的正面效应
拥有高质量的单元测试,将得到如下反馈:
- 明确代码在给定的条件下能够正常的工作,这个是单元测试的本质功效;
- 促进代码内聚,高度内聚的代码才能够简单的单元测试,单元测试反过来会促使高内聚的设计;
- 增强重构的意愿,谁要是把线上代码重构出了bug,绝对会收获一大拖的负面情绪,而单元测试能够保证重构后的正确性。
- 让你积累更多自信,点点滴滴量变产生质变。自信到底有多重要,搜索一下这个你看似很熟悉的名词,重要性绝对超出你的想象。
可以说,经过单元测试的代码,让人更放心,当然如果你是天生蜜汁自信的人另说。
什么样的代码难写单元测试?
-
有人说过长的方法,个人认为不尽然,方法的长短不是关键,关键在于方法承载的功能是否凝练,内聚,也就是说方法调用产生的结果会不会因为多个原因而改变。
-
外部调用太多,每个外部调用都是引起方法变化的原因,验证当前方法逻辑时,我们需要伪造出调用的结果,调用越多,逻辑就越复杂,伪造数据就越难,写测试用例就越费劲。
-
分支过多,条件分支会造成测试用例编写的难度成倍增加,想要对逻辑全面覆盖,就需要每个分支都运行到位,通常需要伪造合适的参数,然后写多份几乎相同的用例,用例代码copy来copy去,最后就是一锅粥。最后还要区分到底哪个参数走到哪个分支,头都搞晕,这样的用例以后几乎都难维护.
如何写出可测的代码呢?
思考过这个问题的同学,估计都有一套自己的方法论,下面是我个人的总结:
提炼!让功能逻辑内聚!
早写!写完功能代码或者写功能时,尽早思考接下来如何测试!
分离!把支线的分支逻辑分离到单独的类,用策略模式以及责任链模式处理分支。
分层!当功能逻辑复杂时,增加层次结构,让复杂的逻辑分散到下层不同的类中,降低单个类的复杂度。
总结下来,就是类要小、方法要内聚、if要少。
最后,设计是不断演化而趋向完美的,单元测试的难易程度某种程度体现了设计的复杂度,测试驱动设计的思想,个人理解就是通过编写单元测试,来暴露出设计上的缺陷,进而改进设计。