我们的测试驱动开发经验
你应该听过或读过不少关于测试驱动开发的讨论,测试驱动开发往往被比喻为神奇的独角兽,它能帮你照看你的软件,让大家乐得其所。于是,在写了18.000行“神奇独角兽”代码后,我想把我们的体验从整体的角度阐述一下。
真相其实是,测试驱动开发真TMD太痛苦了。写那些没完没了的测试需要很强的自律,远不是你想象的那么简单容易。
但你知道什么更让人讨厌吗?是缺少这些测试而出现的麻烦。
让我澄清一下。我并不是想来劝说你去实施测试驱动开发。我想做的只是要给你一个真实的认识,让你明白TDD是如何工作的,以及你能从中得到哪些好处。
对软件开发来说,我基本上把测试驱动开发当做一种保险。它能保证你的软件有一个预设的质量关卡,大多数是依据bug的数量和软件变更的风险度来判定。它同时能降低万一你的主要开发人员被车撞后造成的损失,以及你的软件所依赖的平台上的API遭遇类似事件所带来的危害。
然而,在我们所生活的这个星球上,仍然有成百万的人每天过着没有保险的生活,很显然,达到目的的方式并不是只有一条途径。这完全依赖于你的责任心和你能或你想怎么去做事情。
在Transloadit公司,我们 100%的代码都是由测试驱动开发的。我们这样做的主要原因是我们的系统用了node.js,这种技术目前来看在某种程度上仍然是有很高的风险的技术,也许这正是我们最大的麻烦,但同时也是我们最大的优势所在。另外一个很大的风险是我们使用的第三方工具包,例如ffmpeg和image magick,这些东西在升级时出奇的危险,因为它们动不动就改变API和接口运行方式。而最后一个,但绝对不是最不重要的一个,是我们的某些东西太复杂了。
现在,让我解释一下我们如何利用测试驱动开发来避免这些风险的。首先,在开发所有的新功能前先写系统测试。所有的系统测试都是利用整套的REST service,它产生真实的HTTP请求,评估响应的信息,同时也检查这个过程中产生的文件。我们通过文件的元数据信息来确认文件,对于图片我们使用特定的设计进行可视化比较。对于视频我们使用截屏图像进行可视化对比。
一旦写出来测试程序(也许是失败的测试),我们就开始写单元测试。让我来解释一下我们所说的“单元“。对于我们来说,一个单元是我们能测试的一个最小粒度的功能片段。只要存在函数调用,类的创建,甚至闭包的回调,我们都会把它挑出来。所有的测试都是孤立的、分离的、能够自动的运行处理。你可以想象的出,这部分工作是痛苦的,需要勇气。
可是,产生的结果却是不可否认的漂亮。写这些测试感觉就像是在验证数学理论。每一个步骤都是能够分解的最小的逻辑单元。所有的步骤都是基于之前的一步。没有必要每次都测试所有的 1 – 1000000个功能点,除非你有逻辑上的需求,需要一个特殊结果。
很显然,在这个过程中你有很多机会去做傻事,或者迷失了原始方向。这时系统测试就来拯救你了。任何当你不知道下一步该做什么的时候,只要运行一下系统测试,它就会告诉你还少些什么要做。
完美的过程吗?不,在我们的产品中的这里或那里,多少都会有些bug,但不多,我十个手指头都能数过来。上周末,我们对我们的产品做了一个疯狂的改动:我们把底层的MySQL驱动(我们用的是PHP,别问我怎么做的)换成了node-mysql。我们已经在node-mysql上工作了,同样使用的是TDD-masochism,升级相当的平稳(目前为止)。这种成绩对于一个牵涉有6000行代码的改动来说很不错了。
这就是我们做的。并不会由于我们使用了TDD,我们公司的产品就必然的优于我们的竞争对手。但我们承担的风险要小的多,我们可以轻而易举的做心脏手术。这给了我们分配资金的信心,给了我们能力去把剩余有限的资源(我们做事情步步为营)使用在新功能的开发上,而不是花在解决我们软件里无处不在的缺陷上。
那么,TDD适合你吗?这要视情况而定。按TDD模式写程序需要很强的自律,独角兽不会一直陪在你身旁让你放心。想好了再去这样做。有时一些系统测试程序会用掉你80%的努力来测试只是20%的工作。而有些情况会让你把剩余的20%也搭上。我们很幸运能使用一种灵活的语言,我们大部分的接口都是使用的JSON。这使我们的测试成本更低。这些我们铭记在心。