单元测试101:你测试你的javascript吗?

 
 
做者:littlechang ,发布于2012-11-15 ,来源:CSDN
 

你固然是测试你的代码。没有写出至关数量的代码后不运行一下就直接丢到产品中。在本文中我对你是如何测试的进行质疑。若是你不是已经尽量的多的自动化测试,为生产力和信息提高作好准备吧。javascript

一句话的警告:我将在本文中谈论单元测试和测试驱动开发(TDD),若是你已经得出结论:下面的任何理由对你都不适合,那么请继续阅读,或者至少阅读从我为何要关心?到最后:java

  • 我使用一个库,如jQuery,它保证个人代码正确的工做
  • 测试是一个对专业人员的高级的实践,不适合我
  • 测试太费时间,我只想写产品代码

不一样的目的,不一样的测试jquery

测试意味着不少事,如何把测试作的最好依赖于一个详尽的测试目标。这里有一些可能会在你的应用中遇到的测试的例子:程序员

  • 易用性测试
  • 性能测试
  • 一致性/回归测试

在本文中,咱们专一于一致性和回归测试。换句话说,是那种保障代码作它应该作的事,而且没有缺陷。绝大多数状况下,不能证实绝对没有缺陷。咱们能作的就是保证有效的减小缺陷的数量,而且防止已知缺陷爬回到咱们的代码中。web

如何发现缺陷编程

大多的程序员都会面对按期查找和修改缺陷。过去,这个任务最经常使用的方法是在代码中散置一些alert调用(this task was most commonly carried out bysprinkling code with alert calls),并刷新浏览器监查变量的值,或者观察哪里出现了指望的流和脚本指望的流的一致(or to observewhere the expected flow of a script diverged from the expected flow)。浏览器

现在,大多浏览器都内建一个强大的控制台。那也不容易得到一个像Firebug Lite同样有用的工具。调试过程几乎都是同样的:在代码散置console.log调用,刷新浏览器,观察实际行为,并和预期行为进行人为比较。缓存

调试:一个例子服务器

例如一个调试session的例子,咱们来看一个jQuery插件,它指望一个元素具备一个datetime属性(如HTML5时间元素),或一个自定义data-datetime属性,包含一个日期字符串,用人类可读的、和当前时间对比的内容(如3小时以前)替换元素的innerHTML。网络

</span; style="font-size: 14px;">
1.	jQuery.fn.differenceInWords = (function () {
2.	    var units = {
3.	        second: 1000,
4.	        minute: 1000 * 60,
5.	          hour: 1000 * 60 * 60,
6.	           day: 1000 * 60 * 60 * 24,
7.	          week: 1000 * 60 * 60 * 24 * 7,
8.	         month: 1000 * 60 * 60 * 24 * 30
9.	    };
10.	 
11.	    function format(num, type) {
12.	        return num + " " + type + (num > 1 ? "s" : "");
13.	    }
14.	 
15.	    return function () {
16.	        this.each(function () {
17.	            var datetime = this.getAttribute("datetime") ||
18.	                             this.getAttribute("data-datetime");
19.	            var diff = new Date(datetime) - new Date();
20.	 
21.	            if (diff > units.month) {
22.	                this.innerHTML = "more than a month ago";
23.	            } else if (diff > units.week) {
24.	                this.innerHTML = format(Math.floor(diff / units.week), "week") + " ago";
25.	            } else {
26.	                var pieces = [], num, consider = ["day", "hour", "minute", "second"], measure;
27.	 
28.	                for (var i = 0, l = consider.length; i < l; ++i) {
29.	                    measure = units[consider[i]];
30.	 
31.	                    if (diff > measure) {
32.	                        num = Math.floor(diff / measure);
33.	                        pieces.push(format(num, consider[i]));
34.	                    }
35.	                }
36.	 
37.	                this.innerHTML = (pieces.length == 1 ? pieces[0] :
38.	                                  pieces.slice(0, pieces.length - 1).join(", ") + " and " +
39.	                                  pieces[pieces.length - 1]) + " ago";
40.	            }
41.	        });
42.	    };
43.	}());
</span>  

该代码首先处理两种特殊的状况:差值大于一个月表示成“超过一个月(more than a month)”,差值大于一个星期时显示星期数。函数而后差值收集准确的天数、小时数和秒数。当差值小于一天时,忽略天数,依此类推。

代码看上去很合理,可是使用它时,立刻就会发布有些不太对劲。"Humanizing"一个8天的日期,返回“"and undefined”。使用console.log调试策略,咱们应该开始并记录初始的中间值来断定什么错了。如记录初始差值提醒咱们实际获得的顺序是错误的。好了,咱们修正它:

</span; style="font-size: 14px;">
 1.	var diff = new Date(datetime.replace(/\+.*/, "")) - new Date();
</span>

获得正确差值解决了问题,咱们如今获得了咱们指望的“"1 week ago”。而后咱们把这个插件放到产品中并指望它做为产品的一部分也能工做的很好(So we toss theplugin into production and keep happily hacking on some other part of theapplication.)。

次日,有人温和的通知咱们那个“三天,80小时,4854分钟和291277秒”("3 days, 80 hours, 4854minutes and 291277 seconds" )是不可接受的时间戳格式。结果咱们在测试日期小于一周时失败了。键入console.log,咱们乱丢包括记录语句的代码(可能可能引入一些咱们刚刚清除的记录语句)最后发现剩下的差值不该该每次都从新计算:

<span style="font-size: 14px;">1.	if (diff > measure) {
2.	    num = Math.floor(diff / measure);
3.	    diff = diff - (num * measure); // BUG: This was missing in our first attempt
4.	    pieces.push(format(num, consider[i]));
5.	}
</span>

一旦咱们定位而且修正该故障,咱们清除全部的console.log调用,避免代码在没有定义console对象的浏览器中崩溃。

分步调试器

Firebug和相似的工具使调试javascript比过去更容易。可是不少人彷佛认为console.log是比原始的alert更高级的工具。的确,console不阻塞UI而且更少可能让你强制关闭浏览器,可是console.log调试和alert调试是同样的优雅或不优雅。

一个稍微复杂的方法是使用像Firebug同样的工具使分步调试成为可能。

使用分步调试,你能够经过设置一些断点和检查全部有效值而不是记录每一个你想查看的变量的值来节省一些时间。

Console.log的问题

Console.log风格调试有一些问题:首先,console.log有讨厌的引入自身缺陷的风险。若是在演示或部署以前忘记移除最后记录语句,你知道我在说什么。悬浮的记录语句会使你的代码在不支持console对象的浏览器上崩溃,包括Firebug不可用时的火狐。“可是JavaScript是动态的”,我听到你说,“你能够定义你本身的无操做的console,而后问题就会消除”。的确,你能够这样作,但那就像是用刷漆解决你的汽车生锈的问题。

若是悬浮的console.log调用是不可接受的,咱们当即认识到下一个问题:它是不可重复的。一旦调试会话结束,你去除了全部的记录语句。若是(当)新问题出如今代码的相同部分时,你又回到了起点,从新采用巧妙的记录语句。分步调试也一样是暂时的。特设调试(Adhoc debugging)是费时的、容易出错的和不可重复的。

更有效的发现缺陷

单元测试是查找缺陷和验证正确性的方法,而且不须要面对调试器临时性和人为console.log/alert调试。单元测试还有其余大量的优点,我将经过这篇文章介绍。

什么是单元测试

单元测试是你的产品代码按照预期结果的可执行部分。例如,假如咱们以前在 jQuery.fn.differenceInWords中发现有两个错误没有修正,并试图用单元测试找到它们:

1.	var second = 1000;
2.	var minute = 60 * second;
3.	var hour = 60 * minute;
4.	var day = 24 * hour;
5.	 
6.	try {
7.	    // Test that 8 day difference results in "1 week ago"
8.	    var dateStr = new Date(new Date() - 8 * day).toString();
9.	    var element = jQuery('Replace me');
10.	    element.differenceInWords();
11.	 
12.	    if (element.text() != "1 week ago") {
13.	        throw new Error("8 day difference expected\n'1 week ago' got\n'"+
14.	                        element.text() + "'");
15.	    }
16.	 
17.	    // Test a shorter date
18.	    var diff = 3 * day + 2 * hour + 16 * minute + 10 * second;
19.	    dateStr = new Date(new Date() - diff).toString();
20.	    var element = jQuery('Replace me');
21.	    element.differenceInWords();
22.	 
23.	    if (element.text() != "3 days, 2 hours, 16 minutes and 10 seconds ago") {
24.	        throw new Error("Small date difference expected\n" +
25.	                        "'3 days, 2 hours, 16 minutes and 10 seconds ago' " +
26.	                        "got\n'" + element.text() + "'");
27.	    }
28.	 
29.	    alert("All tests OK!");
30.	} catch (e) {
31.	    alert("Assertion failed: " + e.message);
32.	} 

上面的测试用例处理具备已知具备时间属性的元素,并在获得的人性化的结果字符串不是咱们指望的结果时抛出异常。该代码能够保存到独立的文件或在加载该插件的页面中包含。在一个浏览器中运行会当即让我获得“全部测试正常”或一个指示什么错了的消息。

用这种方法调试你的代码好像很笨拙。咱们不只要写记录语句来帮助咱们监测代码,并且咱们还不得不用程序建立元素和经过插件运行它们来验证产生的文本。但这种方法有至关多的好处:

  • 该测试能够在任什么时候间,任何浏览器上重复运行。
  • 不管什么时间当咱们改变代码,咱们都要记得运行该测试,它能够极大的保证一样的缺陷不会从新回来。
  • 适当的清理,这些测试提供了代码的文档。
  • 测试是自我检查的。不管咱们添加了多少测试,咱们仍然只有一个页面来验证是否有错误。
  • 测试和产品代码没有冲突,所以不会在做为产品代码的部分发布时带入内部alert和console.log调用的风险。

写该测试带来稍多的初始化效果,但咱们只写一次,咱们很快的会在下次须要调试一样的代码时节省时间。

使用单元测试框架

刚才咱们写的测试包含至关多的套路。幸运的是,已经有不少的测试框架来帮助咱们。使用测试框架让咱们减小不得不嵌入到测试中的测试逻辑的数量,它进而也减小测试自身的缺陷。框架也能够给咱们更多的自动测试和显示结果的选项。

断言

断言是一个特殊的方法,它执行对它的参数给定的验证,或标识一个错误(一般抛出一个相似AssertionError 异常),或什么也不作。最简单的断言是它指望参数是“真”。断言一般也可接受一个在失败时用于显示的消息。

1.	assert("Small date difference expected\n '3 days, 2 hours, 16 minutes and " +
2.	       "10 seconds ago' got\n'" + element.text() + "'",
3.	       element.text() == "3 days, 2 hours, 16 minutes and 10 seconds ago");

断言以第一个参数为消息。这个主意是要首先说明你的预期,而断言像是用消息来讲明(原文:The idea is thattesting is about stating your expectations upfront, and the assertion resemblesa specification with the leading message.)。

你的所有须要一般像上面那个简单断言就能知足,大多的测试框架都附带选择自定义断言的机会。上面咱们真正作的就是验证计算值与预期值的对比。绝大多数的测试框架都针对这种状况提供多种重载的assertEquals。(Most testframeworks have something along the lines of assertEquals for thisspecific use case.)

1. assertEquals("3 days, 2 hours, 16 minutes and 10 seconds ago", element.text());

注意咱们再也不指定一个说明。assertEquals 知道咱们指望是第二个计算的值和第一个值相等,因此它能够为咱们生成一个适当的消息。

测试用例,setUp 和 tearDown

在咱们手工的单元测试中,咱们有两个独立的测试。当使用测试框架时,一般在一个测试用例中指定为独立的函数。一个测试用例是一组测试相关功能的测试的集合。为了使测试报告更容易查看,测试用例一般都有一个名字。下面的例子使用JsTestDriver测试用例来组织前面咱们手工的单元测试。

1.	var second = 1000;
2.	var minute = 60 * second;
3.	var hour = 60 * minute;
4.	var day = 24 * hour;
5.	 
6.	TestCase("TimeDifferenceInWordsTest", {
7.	    "test 8 day difference should result in '1 week ago'": function () {
8.	        var dateStr = new Date(new Date() - 8 * day).toString();
9.	        var element = jQuery('Replace me');
10.	        element.differenceInWords();
11.	 
12.	        assertEquals("1 week ago", element.text());
13.	    },
14.	 
15.	    "test should display difference with days, hours, minutes and seconds": function () {
16.	        var diff = 3 * day + 2 * hour + 16 * minute + 10 * second;
17.	        dateStr = new Date(new Date() - diff).toString();
18.	        var element = jQuery('Replace me');
19.	        element.differenceInWords();
20.	 
21.	        assertEquals("3 days, 2 hours, 16 minutes and 10 seconds ago", element.text());
22.	    }
23.	}); 

每一个测试以前的注释都转换为测试函数的名称,比较转换为断言。咱们甚至能够经过把建立日期对象提取到一个特定的setUp方法调用中来使每一个测试更整洁,setUp会在每一个测试函数执行以前调用。

1.	TestCase("TimeDifferenceInWordsTest", {
2.	    setUp: function () {
3.	        this.date8DaysAgo = new Date(new Date() - 8 * day);
4.	        var diff = 3 * day + 2 * hour + 16 * minute + 10 * second;
5.	        this.date3DaysAgo = new Date(new Date() - diff);
6.	    },
7.	 
8.	    "test 8 day difference should result in '1 week ago'": function () {
9.	        var element = jQuery('Replace me');
10.	        element.differenceInWords();
11.	 
12.	        assertEquals("1 week ago", element.text());
13.	    },
14.	 
15.	    "test should display difference with days, hours, minutes and seconds": function () {
16.	        var element = jQuery('Replace me');
17.	        element.differenceInWords();
18.	 
19.	        assertEquals("3 days, 2 hours, 16 minutes and 10 seconds ago", element.text());
20.	    }
21.	}); 

setUp 方法还有一个对应的tearDown 方法,在每一个测试以后执行。这个例子不须要tearDown 方法,但你能够在任何你须要在每一个测试以后执行清理时建立一个tearDown 。假想你测试使用localStorage实现缓存一些数据的代码。为了防止测试相互之间干涉,你可能想在每一个测试以后清除写进localStorage 中的全部数据。

另外,对代码和测试,你须要指定某种实际运行测试方法。大多的JavaScript单元测试框架须要一个简单的HTML文件来按正确的顺序加载正确的文件(包括测试框架自身)。这个HTML文件而后能够加载到浏览器中。一般全部的测试经过为绿色,有失败的测试时转为有威胁的红色。

自动化,自动化,自动化

经过把基于日志的调试工做转到单元测试,咱们确信咱们的经验是重复的和自验证的。这样作可节省花费大量的手工劳动,但还有改善的余地。在浏览器中运行包含测试的HTML文件是至关无关痛痒的,但如你注意到的,今天web开发不能在一个浏览器中简单测试就完事。依据你的环境,你可能不得不在至少在3个以上平台的5个以上的浏览器的2个以上最新版本上测试。忽然,运行那个HTML文件也是有一点工做量的。

如前所述,上面的测试用例对象是为JsTestDriver写的,一个从谷歌出来的Javascript测试框架和测试运行器。把JsTestDriver从(产品)包中分离出来才是运行测试的正路。相比于标准的HTML文件加载源和测试,JsTestDriver运行一个能够帮你同时当即在多个浏览器上执行测试的服务器。理解它是如何工做的最有效的办法就是看清的行动。

假设该jQuery插件在src/difference_in_words.jquery.js,测试用例在test/difference_in_words_test.js。为了运行这个测试,咱们在项目的根目录添加一个配置文件jsTestDriver.conf。它包括下面的内容:

1.	server: http://localhost:4224
2.	 
3.	load:
4.	  - src/*.js
5.	  - test/*.js 

如今下载JsTestDriver.jar的最新版。同时须要安装Java。而后在命令行中执行以下命令(若是是Windows,就是cmd.exe):

1. java -jar JsTestDriver-1.2.2.jar --port 4224

如今你就已经在你机器上打开了一个JsTestDriver服务器。下一步是打开一个连接为http://localhost:4224/capture的浏览器,它让浏览器转入懒测试运行从属(which will turn the browser into an idle test runningslave)。在你全部能用的浏览器上作一样的事。而后打开一个命令行,cd进入项目目录并键入:

java -jar JsTestDriver-1.2.2.jar --tests all

很快你应该可以看到一些输出:JsTestDriver在全部可用浏览器上运行的两个测试,并显示是否经过。恭喜你,你已经在多个浏览器上自动测试了!若是你的机器能够经过网络使用其余设备访问,你也可使用这个服务器测试其余平台(OS X, Windows,Linux),你的iPhone, Android电话和其余移动设备。而且你只要在一个命令行就能够所有验证它们。多么使人激动呀!

JsTestDriver不是你自动化测试的惟一选择。若是你不喜欢它的断言框架,你也能够运行用QUnit, YUI Test 和 Jasmine写的测试。另外,雅虎YETI,一个只对YUI的相似的工具, Nicholas Zakas最近发布了YUI TestStandalone,包括了基于SeleniumWeb Driver的相似的运行器。

可测试性:用测试改善你的代码

如今,你可能但愿开始实现大量节省时间的单元测试就能够了,特别是对一般预期在多个环境运行得很好的JavaScript。单元测试不只相比手工调试和猴子补丁(monkey patching)节省大量的时间,并且能够提升你的信心、快乐和生产力。

如今已经决定开始写单元测试,你可能想知道如何开始。明显的答案是为现有的代码写一些测试。不幸的是,那结果每每是现实很困难。部分缘由是写测试须要实践,并且前几个(测试)一般很难正确,甚至只是输入(正确)。然而,为何为现有的代码写测试很困难经常还有另一个缘由:代码不是和测试思想一块儿写的,一般不是很测试友好的。

可测试性的例子:计算时间差

“可测试性”是特定接口的测试友好方面的度量。一个测试友好的接口使全部对它关注的部分能方便的从外部存取,不须要为测试任何一个给定部分的API而创建无关的状态。换句话说,可测试性是和良好设计有关的,松耦合、高内聚的,这只是花哨方法说对象不该该依赖于太多其余对象而且每一个对象/函数只作好一件事。

做为一个可测试性的例子,咱们再来看咱们的jQuery插件。在前两个单元测试中,咱们但愿确保对8天前的日期使用插件,结果是字符串“1 weekago”,而且另外一个日期的结果是一个更详细的字符串表示。注意,这两个测试没有任何DOM元素操做,虽然咱们不得不建立一个对象以测试日期差计算和人类友好的描述字符串。

这个jQuery插件明显比它原本难以测试,主要缘由是它作了不止一件事情:计算日期差,生成两个日期差的人类易读的描述,而且从DOM节点的innerHTML抓取日期和更新它 。

要解决这个问题,考虑下面的代码,它是一样的插件的另外一种实现:

1.	var dateUtil = {};
2.	 
3.	(function () {
4.	    var units = {
5.	        second: 1000,
6.	        minute: 1000 * 60,
7.	          hour: 1000 * 60 * 60,
8.	           day: 1000 * 60 * 60 * 24,
9.	          week: 1000 * 60 * 60 * 24 * 7,
10.	         month: 1000 * 60 * 60 * 24 * 30
11.	    };
12.	 
13.	    function format(num, type) {
14.	        return num + " " + type + (num > 1 ? "s" : "");
15.	    }
16.	 
17.	    dateUtil.differenceInWords = function (date) {
18.	        // return correct string
19.	    };
20.	 
21.	    jQuery.fn.differenceInWords = function () {
22.	        this.each(function () {
23.	            var datetime = this.getAttribute("datetime");
24.	            this.innerHTML = dateUtil.differenceInWords(new Date(datetime));
25.	        });
26.	    };
27.	}()); 

和前面的代码相同,只是从新整理了。如今有两个公开函数:jQuery插件和新的接受一个日期并返回一我的类可读的描述多长时间以前的一个字符串的dateUtil.differenceInWords。还不完美,但咱们已经把它分红了两个关注点。如今jQuery插件只负责用人性化的字符串替换元素的innerHTML ,而新函数只负责计算成正确的字符串。虽然旧的测试仍然能经过,但测试应该针对新接口简化。

1.	TestCase("TimeDifferenceInWordsTest", {
2.	    setUp: function () {
3.	        this.date8DaysAgo = new Date(new Date() - 8 * day);
4.	        var diff = 3 * day + 2 * hour + 16 * minute + 10 * second;
5.	        this.date3DaysAgo = new Date(new Date() - diff);
6.	    },
7.	 
8.	    "test 8 day difference should result in '1 week ago'": function () {
9.	        assertEquals("1 week ago", dateUtil.differenceInWords(this.date8DaysAgo));
10.	    },
11.	 
12.	    "test should display difference with days, hours, minutes and seconds": function () {
13.	        assertEquals("3 days, 2 hours, 16 minutes and 10 seconds ago",
14.	                     dateUtil.differenceInWords(this.date3DaysAgo));
15.	    }
16.	}); 

如今,在咱们的测试中没有了DOM元素,而咱们能更有效的测试生成正确字符串的逻辑。一样的,测试这个jQuery插件的问题是确信文本内容被替换。

为何为测试而修改代码?

每次我向别人介绍测试和解释可测试性的概念,老是听到关于“难道你不只让我用更多的时间写这些测试,并且我还得为了测试改变个人代码吗?”的说词。

来看咱们刚才为人性化时间差而作的改变。改变是为了方便测试的目的,但你能说只有测试受益吗?偏偏相反,改变使代码更易于分离无关行为。如今,若是咱们晚点决定执行如Twitter反馈到咱们的页面,咱们能直接使用时间戳调用differenceInWords 函数,而不是经过DOM元素和jQuery插件的笨拙的路线(Now, if we later decide to implement e.g. aTwitter feed on our pages, we can use the differenceInWords functiondirectly with the timestamp rather than going the clumsy route via a DOMelement and the jQuery plugin.)。可测试性是良好设计的固有特性。固然,你能够有可测试性和很差的设计,但你不能有一个良好的设计而不具备可测试性。考虑做为一个小例子的状况的测试—你的代码的例子—若是测试很困难,也就意味着使用代码很困难。

先写测试:测试驱动开发

当你在现有的代码中使用单元测试时,最大的挑战是可测试性问题。为了持续提升咱们的工做流程,咱们能作什么?这引出了一个让可测试性直接进入产品代码灵魂的万无一失的方法是先写测试。

测试驱动开发(TDD)是一个开发过程,它由一些小迭代组成,而且每一个迭代一般由测试开始。直到有一个失败的单元测试须要,不然不写产品代码。TDD使你关注行为,而不是你下一步须要什么代码。

比方说,咱们被告知那个计算时间差的jQuery插件须要计算任意两个时间的差,而不仅是和当前时间的差值。你如何使用TDD解决这个问题?好了,第一个扩展是提供用于比较的第二个日期参数:

1.	"test should accept date to compare to": function () {
2.	    var compareTo = new Date(2010, 1, 3);
3.	    var date = new Date(compareTo.getTime() - 24 * 60 * 60 * 1000);
4.	 
5.	    assertEquals("24 hours ago", dateUtil.differenceInWords(date, compareTo));
6.	} 

这个测试假想该方法已经接受两个参数,并预期当比较两个传过去日期刚好有24小时的差异时,结果字符串为"24 hours ago"。运行该测试不出所料的提示它不能工做。为让测试经过,咱们不得不为该函数添加第二个可选参数,同时确保没有改变函数使现有的测试失败。下面是一个实现的方法:

1.	dateUtil.differenceInWords = function (date, compareTo) {
2.	    compareTo = compareTo || new Date();
3.	    var diff = compareTo - date;
4.	 
5.	    // ...
6.	}; 

全部的测试都经过了,说明新的和原来的需求都获得知足了。

如今咱们接受两个日期,咱们可能但愿方法能描述的时间差是过去或未来。咱们先用另外一个测试来描述这个行为:

1.	"test should humanize differences into the future": function () {
2.	    var compareTo = new Date();
3.	    var date = new Date(compareTo.getTime() + 24 * 60 * 60 * 1000);
4.	 
5.	    assertEquals("in 24 hours", dateUtil.differenceInWords(date, compareTo));
6.	} 

让这个测试经过须要一些工做量。幸运的是,咱们的测试已经覆盖(部分)咱们以前的要求。(两个单元测试很难构成良好的覆盖,但假想咱们已经有针对该方法的完整的测试套件)。一个强大的测试套件让咱们不惧怕改变代码,若是咱们打破它了,咱们知道会获得告警。个人最终实现是这样的:

1.	dateUtil.differenceInWords = function (date, compareTo) {
2.	    compareTo = compareTo || new Date();
3.	    var diff = compareTo - date;
4.	    var future = diff < 0;
5.	    diff = Math.abs(diff);
6.	    var humanized;
7.	 
8.	    if (diff > units.month) {
9.	        humanized = "more than a month";
10.	    } else if (diff > units.week) {
11.	        humanized = format(Math.floor(diff / units.week), "week");
12.	    } else {
13.	        var pieces = [], num, consider = ["day", "hour", "minute", "second"], measure;
14.	 
15.	        for (var i = 0, l = consider.length; i < l; ++i) {
16.	            measure = units[consider[i]];
17.	 
18.	            if (diff > measure) {
19.	                num = Math.floor(diff / measure);
20.	                diff = diff - (num * measure);
21.	                pieces.push(format(num, consider[i]));
22.	            }
23.	        }
24.	 
25.	        humanized = (pieces.length == 1 ? pieces[0] :
26.	                     pieces.slice(0, pieces.length - 1).join(", ") + " and " +
27.	                     pieces[pieces.length - 1]);
28.	    }
29.	 
30.	    return future ? "in " + humanized : humanized + " ago";
31.	}; 

注意,我没有碰jQuery插件。由于咱们分离了无关的部分,我能够彻底自由的修改和提高人性化字符串的方法,而不改变个人网站中jQuery使用人性化字符串的方法。

持续集成

TDD实践中,咱们须要及时的反馈。反馈来自咱们的测试,这意味着测试须要运行的轻松快速。JsTestDriver已经使测试运行的容易而快速,但总有局限性。限制来自多浏览器的形式。JsTestDriver能如你所愿在多个浏览器上容易的运行测试,因如下两个缘由,这对TDD工做流这样作是不便的:

  • 每一次从多个浏览器获得测试报告,使它更难看到发生了什么,并失去了TDD给你带来的便利。
  • 一些较弱的浏览器,而一般是重要的测试对象,是缓慢的。个人意思是慢的足以毁灭TDD流程。(And I mean slow.Slow ruins the TDD flow.)

解决这个问题的一个方案是持续集成。持续集成是自动和常常进行产品质量控制的实践。这时应该包含进来一些工具,如JsLint,而它固然应该包含运行测试。

一个持续集成(CI)服务器能够确保全部开发者的工做能够正确的组合,而且负责在指定的多个浏览器是执行测试。一个构建的CI服务器一般由版本控制系统触发,如Git或Subversion,而且通常提供当发现问题时给项目成员发送邮件的功能。

我最近写了为JsTestDriver建立Hudson CI服务器指南。使用Hudson和JsTestDriver,很容易建立一个高效高质量的工做流程。对我本身而言,我基本是作什么都是TDD,一般我在本机的Firefox上运行测试,它是我发现具备最好错误信息和跟踪信息的浏览器。每次我完成一个功能,一般很小,我把它放到代码库中。这时,Hudson检出我刚提交的变化并在普遍的浏览器上运行全部的单元测试。若是有测试失败,我会收到一个说明发生了什么的邮件。此外,我能够随时访问Hudson服务器查看项目构建视图,看我的的控制台输出等等。

结论:为何我要关心

若是,在阅读完这篇文章以后,你还不确信单元测试是一个很值得作的实践,让咱们再重述一下一些常见误解。

我使用一个库,如jQuery,它确保个人代码正确的工做。

Ajax库,如jQuery,在帮助你处理跨浏览器问题上走了很远。实际上,在不少状况下,这些库彻底抽象掉了全部这些讨厌的DOM缺陷,甚至是核心JavaScript的差别。然而,这些库没有,并且不能,保护你的错误的应用逻辑,而单元测试能够。

测试是对专业人员的高级实践,不适合我

个人立场是不管你认为你写代码的过程是哪一种方式,你都在测试它,例如,经过刷新浏览器来验证是否是按它应该的方式工做。你简单的选择了不参与自动化和提升你的测试过程,而且在长时间运行(或不那么长时间的运行)中,你花费时间在猛击你的浏览器刷新按钮,而我花费时间写测试,而后我能够今天、明天或明年愉快的运行它。

像任何新技术同样,测试须要实践,但不须要一个“忍者”去作。测试由大量简单的语句组成,他们使用你的代码并对它作假设(真很差表达,原文:ests consistlargely of dirt simple statements that exercise your code and make assumptionsabout it.)。困难的部分是良好设计的代码并确保它是可测试的。换句话说,困难的部分是提升你的编程技巧并写以前思考你的代码。不管是专业人员或初学者,任何人没有缘由不想提升

测试太花时间了,我只想写产品代码

手工和自动化测试都花时间。可是,不用花一两个小时“评估”单元测试和/或TDD,而后决定它是在浪费时间。单元测试和TDD须要的是实践,像其余任何科目同样。没有办法几个小时内作到擅长良好的自动化测试。你须要练习,而一旦掌握,你就会认识到我这里说的好处,而且认识到手工测试是多么的浪费。此外,若是你写了单元测试,并花一些时间严厉测试你的代码,你会选择什么呢?失败的真快,或能成功吗?

调整你的需求

从这篇文章中你可能获得这样的印象,我以为各我的都应该采用个人工做方式。我没有那种感受。但我感受认真对待应用的质量和正确性是很重要的,而且我认为单元测试是实现的完整部分。(I do think thatunit testing is an integral part of that equation.)

这里TDD更可能是一个可选部分,但个人经验告诉我,TDD极大的简化单元测试。在我实现功能以前,它帮助我提高代码设计,帮助我只实现那些必须实现的代码。固然你能够采用其余的方式也很好的实现这个目标,可是对我来讲,TDD是个完美的方案。

如今开始实践吧!

相关文章
相关标签/搜索