單元測試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是個完美的方案。

如今開始實踐吧!

相關文章
相關標籤/搜索