测试驱动开发
测试驱动开发是一个很”古老”的程序开发方法,然而由于国内开发流程的问题——即开发人员负责功能的测试,导致这么好的一项技术没有在国内推广。
红-绿-重构
测试驱动开发的主要过程是: 红 —> 绿 -> 重构
- 先写一个失败的单元测试。即我们并没有实现这个方法,但是已经有了这个方法的测试。
- 让测试通过。实现简单的代码来保证测试通过,就算我们用一些作弊的方法也是可以的。我们写的是功能代码,那么我们应该提交代码,因为我们已经实现了这个功能。
- 重构,并改进功能代码,让它变得更加合理。
TDD 有助于我们将问题分解成更小的部分,再一点点的添加我们所需要的业务代码。随着这个过程的不断进行,我们会发现我们已经接近完成我们的功能代码了。并且到了最后,我们会发现我们的代码都会被测试到。
虽然说起来很简单,但是真正实现起来并不是那么容易。于我而言我只会在我自己造的一些轮子中使用 TDD。因为这个花费大量的时间,通常来说测试代码和功能代码的比例可能是1:1,或者是2:1等等。在自己创建的一些个人应用,如博客中,我不需要与其他人 Share 我的 Content。由于我使用的是第三方框架,框架本身的测试已经足够多,并且没有复杂的逻辑,我就没有对我的博客写测试。而在我写的一些框架里,我就会尽量保证足够高的测试覆盖率,并且在适当的时候会去 TDD。
通常来说对于单元测试我会采用 TDD 的方式来进行,但是功能测试仍会选择在最后添加进去。主要的缘由是:在写 UI 的过程中,元素会发生变化。这一点和我们在写 Unit 的时候,有很大的区别。div + class 会使得我们思考问题的方式发生变化,我们需要去点击某个元素,并观察某个元素发生的变化。而多数时候,我们很难把握好一个页面最后的样子。
不得不说明的一点是,TDD 需要你对测试比较了解后,才容易使用它。从个人的感受来说,TDD 在一开始是一件很难的事。
测试先行
对于写测试的人来说,测试先行有点难以理解,而对于不写测试的人来说,就更难以理解。这里假定你已经开始写测试了,因为对于不写测试的人来说,写测试就是一件难以理解的事。既然我们都要写测试,那么为什么我们就不能先写测试呢?或者说为什么后写测试存在一些问题?
依据 J.Timothy King 所总结的《测试先行的12个好处》:
- 测试可证明你的代码是可以解决问题的
- 一面写单元测试,一面写实现代码,这样感觉更有兴趣
- 单元测试也可以用于演示代码
- 会让你在写代码之前做好计划
- 它降低了 Bug 修复的成本
- 可以得到一个底层模块的回归测试工具
- 可以在不改变现有功能的基础上继续改进你的设计
- 可以用于展示开发的进度
- 它真实的为程序员消除了工作上的很多障碍
- 单元测试也可以让你更好的设计
- 单元测试比代码审查的效果还要好
- 它比直接写代码的效率更高
但是在我个人的感觉里,比较喜欢的是: 写出可以测试的函数。这是一个一直困扰着我的难题,特别是当我的代码里存在很多条件的时候,在后期我编写的时候,难度越来越大。当我只有一个简单的 IF-ELSE 的时候,我的代码测试起来也很简单:
if (hour < 18) {
greeting = "Good day";
} else {
greeting = "Good evening";
}
而当我有复杂的业务逻辑时,后写测试就会变成一场恶梦:
if (EchoesWorks.isObject(words)) {
var nextTime = that.parser.parseTime(that.data.times)[currentSlide + 1];
if (that.time < nextTime && words.length > 1) {
var length = words.length;
var currentTime = that.parser.parseTime(that.data.times)[currentSlide];
var time = nextTime - currentTime;
var average = time / length * 1000;
var i = 0;
document.querySelector('words').innerHTML = words[i].word;
timerWord = setInterval(function () {
i++;
if (i - 1 === length) {
clearInterval(timerWord);
} else {
document.querySelector('words').innerHTML = words[i].word;
}
}, average);
}
return timerWord;
} else {
document.querySelector('words').innerHTML = words;
}
我们需要重新理清业务的逻辑,再依据这些逻辑来编写测试代码。而当我们已经忘记具体的业务逻辑时,我们已然无法写出测试。
思考
通常在我的理解下,TDD 是可有可无的。既然我知道了我要实现的大部分功能,而且我也知道如何实现。与此同时,对 Code Smell 也保持着警惕、要保证功能被测试覆盖。那么,总的来说 TDD 带来的价值并不大。
然而,在当前这种情况下,我知道我想要的功能,但是我并不理解其深层次的功能。我需要花费大量的时候来理解,它为什么是这样的,需要先有一些脚本来知道它是怎么工作的。TDD 变显得很有价值,换句话来说,在现有的情况下,TDD 对于我们不了解的一些事情,可以驱动出更多的开发。毕竟在我们完成测试脚本之后,我们也会发现这些测试脚本成为了代码的一部分。
在这种理想的情况下,我们为什么不 TDD 呢?
参考资料
J.Timothy King 《Twelve Benefits of Writing Unit Tests First》