Test-driven programming is the key to many dilemmas while failing to do that is the recipe for many disasters.
这句话是我说的。
最初接触TDD
测试驱动编程的理念是这样的:在你写每一行新程序之前你都要写测试程序。先写一个fail掉的test case, 然后写足够让这个case通过的代码。再写新的会fail掉的case,如此循环往复地实现feature.
最初接触这个概念是感觉太烦,要写这么多测试程序,而我自己对要实现的功能都没有什么自信心,我对测试程序又有什么信心呢?如果要测试驱动的话,那就是用自己不吃得准的代码要被自己更吃不准的测试代码来测试,这岂不是天下大乱?
这种intuition有两层含义,一个是对新事物的恐惧,还有一个就是懒得学,觉得自己知道的东西已经够多了。今年春天听到一些人聊测试驱动,但每次听到这么说,我都觉得是天方夜谭,更自己很遥远。
如何走上TDD之路
好吧,虽然我一开始是拒绝的,但我还是走上这条不归路了,为什么呢?我来细细讲解。
首先是看了一本书,叫做《编程整洁之道:专业程序员的行为准则》,英文版的一本书,专门有一章是在讲测试驱动编程。看完认识到了一个优点,就是test case可以作为天生好理解的documentation存在在项目中。之前写好的类,以后再想使用的话你就成了它的一个client,用户需要manual,而我又不可能每写一个类都附带很多manual,那种加comment的方法很没有效率。但是如果你测试驱动的话,你不用写很多comment,未来的你自己,和其他的开发者都能够很好地理解类中代码的意图。test case作为documentation,是一种最practical的方法,而程序员就是需要这种practical的东西。
书里第二个吸引我的地方是有test case的project更容易维护。书里提到,一个完全测试驱动的project,如果出了bug,在locate到代码以后可以很快找到对应的test case, 开发者可以研究这一类的错误是否被cover到,如果cover到,那问题可能在别处,可以是这个类的client传入了错误的参数。如果没有cover到,那就可以很明确地从这个方向去增加test case. 增加的方式依然是基本TDD套路,先写test case, 然后写足够多的代码来让test case 通过,如此循环前进。我觉得这非常make sense. 于是我就这么做了,受益匪浅。
TDD给我带来的好处
首先,就如上一段讲的,代码更容易维护,出了错误也更好locate 和 fix.
实习期间终于接触到了实战项目,实战项目有一个特点就是战线很长,一般学校项目最多一学期就结束了,绝大部分都不会超过一个月,而实战项目可能整个实习期都要不停地开发、维护和测试。测试驱动在这样的项目中就非常有优势,类一多很容易就忘记之前写的代码是什么意思,这种时候就依靠test case来回顾使用方法,然后开发新的模块。
代码写得一多经常会遇到一种情况,回顾到一段之前写的代码的时候,感觉它特别扯淡,立马想到一种好的可以替换掉原来的bad design的方法,但是如果你没有测试驱动的话,你怎么知道你改过以后不会带来新的bug,你怎么make sure你的改动不会影响其他的类?如果你要用show call hierarchy 来一个个看的话效率就太低了,效率最高的方法还是用自动化测试。改动以后,一键run all tests,如果仍然通过,那么就说明你的改动没有破坏原有的结构,绿色的通过条很醒目,我觉得每个程序员都会喜欢这种设计的。
没有TDD带来的烦躁
当项目走向web的时候,我本来稳健的TDD步伐受到了一些挑战。web的开发需要用浏览器去看,前端的模块是否是想象中那样排列着。慢慢开发着我就陷入了写一段代码、部署、打开浏览器看看的开发模式,从而忘记了测试驱动。
前端的HTML本身就很难像Java一样稳健地测试到,我咨询了一些公司里的同事,问她前端的uitest是怎么进行的,她就说是通过ui去测试后端的模块,所以我就得出结论:前端开发很难像Java开发一样稳健地测试驱动。我一直觉得我很正确,所以任由自己不测试驱动,这也引发了很大的后患。
最大的问题就是前端一些JavaScript出问题的时候很难去debug. 最近出了问题以后我习惯性地想去查看test case, 却发现非但前端不能测试的没有test case, 后端一些api 我也没有写test case. 于是解决这个bug的方法就是从后端开始给每个相关的api写test case, 这些case不会和之前的run all tests放在一起,因为他们需要在deploy以后再进行测试,使用方法上有一些出入。
今天花了很多时间研究JavaScript的TDD, 下载了Node.js, 安装karma来做单元测试,稳健的开发又似乎回来了,心里很高兴。