QUnit系列 -- 1.介紹單元測試(上)


  大家都知道單元測試對於保證代碼質量的重要性,但是對客戶端代碼進行單元測試則要困難的多。一個比較棘手的問題是,因為JavaScript代碼和后台代碼或者html結合的比較緊密,他缺少真正單元的概念。例如對dom的操作,無論我們是借助jquery這樣的類庫,把js代碼單獨放在一個文件,還是直接使用內嵌代碼的實現方式,都沒有可以測試的單元。

  那么什么是單元呢。一般而言,單元就是一個功能函數,相同的輸入,輸出結果是一定的。這種情況的函數,做單元測試是相當簡單的,但有時候你需要處理一些特殊情況,例如對dom的操作。對於我們來說他仍然是有用的,我們可以指出哪些代碼可以構造到單元里面,然后對他做相應的測試。

 

  創建單元測試

  有了上面的指導思想,對於我們開始一項全新工作,並引入單元測試時相當簡單的工作。不過本文介紹的內容是,幫助你對已有代碼完善單元測試,我們需要解決下面的難題:提取現有代碼,對重要部分作測試;發現潛在問題並加以修復。

  提取現有代碼把他放到不同地方,而不影響現有功能,我們把這一過程稱為重構,重構是改善代碼設計相當有用的方式。任何對代碼的修改都有可能影響現有功能,這也就體現了單元測試的重要性,他會讓你的工作更有保障。而這時候我們還沒有單元測試,所以需要借助手工測試的方式來保證任何代碼的修改沒有產生新的bug。我們現在有了理論基礎,接下來要做的就是找個例子來實踐下。下面的代碼會找到所有包含 title 屬性的連接, 然后根據情況顯示過去了多少時間,例如: “5 days ago”:

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Mangled date examples</title>
    <script>
    function prettyDate(time){
        var date = new Date(time || ""),
            diff = (((new Date()).getTime() - date.getTime()) / 1000),
            day_diff = Math.floor(diff / 86400);
 
        if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )
            return;
 
        return day_diff == 0 && (
                diff < 60 && "just now" ||
                diff < 120 && "1 minute ago" ||
                diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
                diff < 7200 && "1 hour ago" ||
                diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
            day_diff == 1 && "Yesterday" ||
            day_diff < 7 && day_diff + " days ago" ||
            day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";
    }
    window.onload = function() {
        var links = document.getElementsByTagName("a");
        for ( var i = 0; i < links.length; i++ ) {
            if ( links[i].title ) {
                var date = prettyDate(links[i].title);
                if ( date ) {
                    links[i].innerHTML = date;
                }
            }
        }
    };
    </script>
</head>
<body>
 
<ul>
    <li class="entry" id="post57">
        <p>blah blah blah...</p>
        <small class="extra">
            Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z"><span>January 28th, 2008</span></a></span>
            by <span class="author"><a href="/john/">John Resig</a></span>
        </small>
    </li>
    <!-- 更多內容... -->
</ul>
 
</body>
</html>

  如果你運行代碼,你會發現不是所有的時間會被替換。代碼會查詢頁面中所有包含title屬性的連接,然后對title執行prettyDate函數,如果函數返回結果則更新鏈接的innerHTML屬性。

 

  讓代碼變得可測試

  問題在於對於大於31天的時間,prettyDate只是返回undefined,鏈接的內容不會發生變化。如果要看假定發生了什么,我需要硬編碼一個當前時間。

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Mangled date examples</title>
    <script>
    function prettyDate(now, time){
        var date = new Date(time || ""),
            diff = (((new Date(now)).getTime() - date.getTime()) / 1000),
            day_diff = Math.floor(diff / 86400);
 
        if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )
            return;
 
        return day_diff == 0 && (
                diff < 60 && "just now" ||
                diff < 120 && "1 minute ago" ||
                diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
                diff < 7200 && "1 hour ago" ||
                diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
            day_diff == 1 && "Yesterday" ||
            day_diff < 7 && day_diff + " days ago" ||
            day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";
    }
    window.onload = function() {
        var links = document.getElementsByTagName("a");
        for ( var i = 0; i < links.length; i++ ) {
            if ( links[i].title ) {
                var date = prettyDate("2008-01-28T22:25:00Z", links[i].title);
                if ( date ) {
                    links[i].innerHTML = date;
                }
            }
        }
    };
    </script>
</head>
<body>
 
<ul>
    <li class="entry" id="post57">
        <p>blah blah blah...</p>
        <small class="extra">
            Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z"><span>January 28th, 2008</span></a></span>
            by <span class="author"><a href="/john/">John Resig</a></span>
        </small>
    </li>
    <-- 更多內容... -->
</ul>
 
</body>
</html>

  運行實例

  現在鏈接應該顯示“2 hours ago”, “Yesterday”等,但是現在的代碼仍然不是實際可測試的單元。在沒有進一步改造代碼的前提下,我們能測試的只是DOM的改變結果。雖然這樣可以工作,但是對html代碼很小的修改都有可能打破測試,對於這種測試的效費比很差。

 

  重構:1

  我們需要對代碼重構,以使他變得可以單元測試。我們需要做兩點改變:1.以參數的形式向prettyDate函數傳遞當前時間,這樣程序里面就不需要使用new Date了;2.把函數抽到一個單獨的文件中,這樣他就變得可重用了。

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Refactored date examples</title>
    <script src="prettydate.js"></script>
    <script>
    window.onload = function() {
        var links = document.getElementsByTagName("a");
        for ( var i = 0; i < links.length; i++ ) {
            if ( links[i].title ) {
                var date = prettyDate("2008-01-28T22:25:00Z", links[i].title);
                if ( date ) {
                    links[i].innerHTML = date;
                }
            }
        }
    };
    </script>
</head>
<body>
 
<ul>
    <li class="entry" id="post57">
        <p>blah blah blah...</p>
        <small class="extra">
            Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z"><span>January 28th, 2008</span></a></span>
            by <span class="author"><a href="/john/">John Resig</a></span>
        </small>
    </li>
    <-- 更多內容... -->
</ul>
 
</body>
</html>

  prettydate.js代碼:

function prettyDate(now, time){
    var date = new Date(time || ""),
        diff = (((new Date(now)).getTime() - date.getTime()) / 1000),
        day_diff = Math.floor(diff / 86400);
 
    if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )
        return;
 
    return day_diff == 0 && (
            diff < 60 && "just now" ||
            diff < 120 && "1 minute ago" ||
            diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
            diff < 7200 && "1 hour ago" ||
            diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
        day_diff == 1 && "Yesterday" ||
        day_diff < 7 && day_diff + " days ago" ||
        day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";
}

  運行實例

 

  現在我們就有些東西可以測試了,我們來做單元測試:

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Refactored date examples</title>
    <script src="prettydate.js"></script>
    <script>
    function test(then, expected) {
        results.total++;
        var result = prettyDate("2008/01/28 22:25:00", then);
        if (result !== expected) {
            results.bad++;
            console.log("Expected " + expected + ", but was " + result);
        }
    }
    var results = {
        total: 0,
        bad: 0
    };
    test("2008/01/28 22:24:30", "just now");
    test("2008/01/28 22:23:30", "1 minute ago");
    test("2008/01/28 21:23:30", "1 hour ago");
    test("2008/01/27 22:23:30", "Yesterday");
    test("2008/01/26 22:23:30", "2 days ago");
    test("2007/01/26 22:23:30", undefined);
    console.log("Of " + results.total + " tests, " + results.bad + " failed, "
        + (results.total - results.bad) + " passed.");
    </script>
</head>
<body>
 
</body>
</html>

  運行實例(確保允許使用控制台,例如firebug或者chrome的web inspector)

  

  這樣我們就創建了一個特定的測試架構,使用控制台輸出結果。他不依賴DOM,所以我們可以在一個沒有瀏覽器的JavaScript環境中運行它,例如Node.js或者Rhino。測試失敗,會顯示出期望值和實際值。最后會顯示出失敗和成功總數。如果全部成功,結果應該類似於這樣:

Of 6 tests, 0 failed, 6 passed.

失敗的情況:

Expected 2 day ago, but was 2 days ago.

Of 6 tests, 1 failed, 5 passed.

  

  雖然我們可以使用特定的解決方案完成測試,但是借助已有的測試框架可以幫助我們更好的完成工作,他為我們提供了更好的結果展示,更多的命令等等。下節我們將介紹QUnit的使用。

 

文章來源:http://qunitjs.com/intro/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM