为了处理这些问题,留意下面的代码,它是同个插件的另一种实现:
var dateUtil = {}; (function () { var units = { second: 1000, minute: 1000 * 60, hour: 1000 * 60 * 60, day: 1000 * 60 * 60 * 24, week: 1000 * 60 * 60 * 24 * 7, month: 1000 * 60 * 60 * 24 * 30 }; function format(num, type) { return num + " " + type + (num > 1 ? "s" : ""); } dateUtil.differenceInWords = function (date) { // return correct string }; jQuery.fn.differenceInWords = function () { this.each(function () { var datetime = this.getAttribute("datetime"); this.innerHTML = dateUtil.differenceInWords(new Date(datetime)); }); }; }()); |
这个代码和之前是一样的,只不过是进行了一次重构。上面的代码含有两个公用的函数:jQuery插件和新的dateUtil.differenceInWords,这个函数调用了一个日期,然后返回一个对人有好的字符串,用于表示日期是多久之前的。还是不够完美,但是我们已经有两个分开的关注点。现在jQuery插件只负责替换元素的innerHTML为给定的友好字符串,新的函数则负责计算新的字符串。老的测试用例依然能检测通过,但是为新的接口写测试用例会变的更容易:
TestCase("TimeDifferenceInWordsTest", { setUp: function () { this.date8DaysAgo = new Date(new Date() - 8 * day); var diff = 3 * day + 2 * hour + 16 * minute + 10 * second; this.date3DaysAgo = new Date(new Date() - diff); }, "test 8 day difference should result in '1 week ago'": function () { assertEquals("1 week ago", dateUtil.differenceInWords(this.date8DaysAgo)); }, "test should display difference with days, hours, minutes and seconds": function () { assertEquals("3 days, 2 hours, 16 minutes and 10 seconds ago", dateUtil.differenceInWords(this.date3DaysAgo)); } }); |
现在我们的测试中已经不包含任何DOM元素,然后我们可以更加高效地测试生成字符串的逻辑。和之前的类似,测试jQuery插件只是一项确保文本内容是否正确替换的工作。
为什么要为测试修改代码?
每一次我给一些人介绍测试并解释可测试性的原理时,我总会听到一些质疑声,比如“你不仅仅想让我花时间来写测试,而且你还想为这些测试修改代码?”
看看我们在时间差只代码中所做的。改变的目的就是减轻测试的工作量,但是仅仅是对测试有帮助么?相反,我们的改变还能让代码更容易从不相关的行为中分离出来。现在,如果我们在后来决定在页面中实现比如一个Twitter的订阅功能,我们可以直接使用differenceInWords函数而不是通过DOM元素和jQuery插件寻找一个笨拙的方式。
可测试性是一个好的设计的固有特点。你可以同时保证可测试性和不好的设计,但是当可测试性差的时候,就一定不是一个好的设计。回想那些作为小例子的测试——使用你的代码的例子——如果测试比较困难,那么这也意味着你的代码难以使用。