iT邦幫忙

6

Travis CI 與自動化 JavaScript 單元測試

<span style="font-family: 微軟正黑體;">
單元測試在軟體開發流程中是很重要的一環,它能<span style="color: red;">協助開發、也能避免後續改版而造成的錯誤</span>。但網站開發者通常不太注重單元測試這個議題,這或許是因網站開發有很大的彈性:有 Bug 可以隨時修正、也不需使用者重新安裝,或許會認為導入單元測試沒太大必要。若講到 JavaScript 單元測試、更因其<span style="color: red;">執行環境複雜無比、自動化門檻超高</span>,過去寫 JavaScript Test Case 的人更是少之又少。

這兩年網站技術日新月異,尤其是 JavaScript 越來越複雜龐大,其實更需要單元測試來確保品質。藉由 PhantomJS、Travis CI 等工具… 寫 JavaScript 單元測試變得輕鬆許多。希望能用這篇文章帶領新手一同進入自動化單元測試的世界,一起提昇 JavaScript 的品質!
</span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 23px;">基礎介紹</span></span>

<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">擔心部分新手沒接觸過這個議題,在此先簡單介紹「CI」、「單元測試」這兩個主題。若有經驗者可跳過此章節。</span></span>

<span style="font-family: 微軟正黑體;"><span style="font-size: 18px;">單元測試 - Unit Test</span></span>

<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">我們常寫一些工具類的 JavaScript Function、甚至包裝成元件、以方便未來重複使用。單元測試 (Unit Test) 是<span style="color: red;">另外一份程式碼、用來檢查這些程式碼的正確性</span>。例如過去我們得替自己撰寫 trim 的方法、以達成去除頭尾的多餘空白,你可以替它寫以下的 Unit Test 做檢查。</span></span>

    // 以下的程式碼得搭配一些工具才能執行,這邊僅用來示意。
    TestCases = {
        "應該要移除左側的空白": function () {
            var value = "     Awoo~", // 輸入值
                expected = "Awoo~";  // 預期得到的結果          
            Assert.areEqual(expected, trim(value)); // 經過 trim 後兩者必須相同
        },
        "應該要移除右側的空白": function () {
            var value = "Awoo~    ", // 輸入值
                expected = "Awoo~"; // 預期得到的結果           
            Assert.areEqual(expected, trim(value)); // 經過 trim 後兩者必須相同
        }
    };

<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">單元測試一句話解釋就是:「<span style="color: red;">檢查 Input(參數) 是否會得到預期的 Output(回傳)</span> 」。不管程式碼是如何實作的,只管最後的結果是否正確。在軟體開發流程中,單元測試<span style="color: red;">相當於規格書、需要在實際的程式碼撰寫前就準備好</span>。好處是什麼?對於開發者來說,省下了手動刻測試介面的時間,只需專注於程式的開發、重複地跑單元測試來做 Input/Output 驗證、直到通過所有的 Test Case。此即為 Test-driven Development (測試驅動開發模式)。</span></span>

<span style="font-family: 微軟正黑體;"><span style="font-size: 18px;">什麼是 CI?</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
CI 的全名是 Continous Integration,中文是「持續整合」。軟體開發常見的問題就是不同的開發人員不斷地增加 Code,但卻缺乏規範及完整測試,所以經過一段時間的累積程式往往變的難以辨認及維護、其他人也沒辦法接手;或者由於規模變大必須要對程式做翻新以因應需求。所以我們最常有的想法就是「打掉重練」,常常因此這樣浪費了許多的時間。

CI 的主要思路就是<span style="color: red;">將程式碼拆解成小且易維護的片段</span>,藉由每天開發人員的 Commit,CI 系統會持續自動抓取一份來做專案建置 (Build),並且去<span style="color: red;">跑各式各樣的測試與檢查</span>、產出報告寄送給相關人。以確保程式碼的品質、也方便未來持續成長。

市面上常見的 CI 系統有 HudsonJenkins 這兩套軟體,你可以建置在公司內部的伺服器上。若你的專案是 Open Source,則可以使用我們今天要講的 Travis CI:一個別人已經建置好、可直接使用、免費的 CI 系統。
</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 18px;">單元測試 + CI = 自動化</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
其實就是<span style="color: red;">讓 CI 系統幫忙自動地做單元測試</span>。這樣一來不管開發者是否有乖乖使用 Unit Test 來檢驗正確性,系統都會忠實地幫你檢查,若有測試失敗則發信告訴相關人。
</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 23px;">JavaScript 做自動化測試的困難點、如何解決?</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
「讓 CI 自動做單元測試」聽起來很簡單,但 JavaScript (Node.js 除外) 跟其他程式語言不太一樣的地方在於:<span style="color: red;">開發常會牽涉到瀏覽器、得透過 DOM API 做事件或節點的存取</span>。若要執行我們寫好的單元測試,勢必<span style="color: red;">得打開瀏覽器才能做</span> 看看傳統的 JavaScript Unit Test)。 這讓我們想利用 CI 達成自動單元測試、增加非常高的難度。CI 系統得要很聰明:得自動打開瀏覽器、能執行你的單元測試、還要能得存取測試結果。要串起整個流程有太多事情要做,這也是過去 JavaScript 單元測試先前非常不普及的主因。

不過,軟體開發就是不斷地累積,3 年前做不到的事情,今天已經可以用多個工具串接解決!
</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 18px;">PhantomJS</span></span>

<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
對瀏覽器,我們原本的認知都是有個視窗介面,讓人去看並操作,。而 PhantomJS 徹底顛覆了這樣的思維:讓開發者可以<span style="color: red;">開啟一個看不見的 Webkit 瀏覽器、並用它設計好的 API 介面 去載入任何 URL、獲得資料</span>。通常會用於網頁效能的檢測(ex. 取得大量 HAR 檔做分析)、以及我們目前在討論的自動測試。
</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 18px;">Grover</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
雖然 PhantomJS 能夠載入我們的測試網頁,但<span style="color: red;">它並不知道該如何執行 Test Cases、也不知道執行後到底是成功還是失敗</span>。要自己寫嗎?這樣實在太累人了 :p

我所使用的 Unit Test 工具是 YUI Test (非 YUI 的 JavaScript 專案也可用),它可搭配一個叫 Grover 的 Node.js 命令行工具使用。他是<span style="color: red;">YUI Test 與 PhantomJS 之間的橋樑</span> ,可以替你執行並取得結果!用法如下:
</span></span>

    grover <Unit Test 路徑> --server 

<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
Grover 會以當前所在目錄為 DocumentRoot、在本機啟動一個簡單的 HTTP Server、載入你的 Unit Test 所在網頁、最後取得測試的結果。
</span></span>

<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
See? 本來得用瀏覽器執行的 Unit Test,現在已經完全可在命令行中跑完啦!
</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 18px;">Travis CI</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
因為我們已經克服了主要問題:命令行即可跑 Unit Test。接下來要達成自動化,其實不管是哪一套 CI 工具都可以達成。但要找台 Server、設定一堆東西把 Hudson 或 Jenkins 跑起來仍然還是個苦差事 :p
</span></span>

<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
Travis 是個<span style="color: red;">提供給 Open Source 社群使用的免費 CI 服務</span>,他很慷慨地捐出頻寬、CPU 運算,讓所有的 Open Source 專案都能<span style="color: red;">無痛使用 CI</span>、大幅減少設置時間,真是佛心來著!當然唯一的限制就是你的專案得是公開的,不然有商業機密的程式碼交給 Travis… 後果自負啊 XD
</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 23px;">開始實作</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
困難點都已經克服了,剩下的就是實作的細節。
</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 18px;">第 1 步:撰寫 Unit Test</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
其實 Unit Test 的撰寫並不容易,尤其像 JavaScript 又會牽扯到許多使用者行為、非同步、與資料交換...。對於想入門的人可以參考 Nicholas Zakas 所撰寫的 Writing Effective JavaScript Unit Tests with YUI Test,它雖然掛了 YUI Test 的字樣,但實際上卻有許多非常值得參考的 Unit Test 心法。

這邊的例子是:同事小莊開發了一個 YouTube Iframe 的元件,主要目的是讓 YouTube Iframe Player API 跟我們的其它 Player 有一致的 API 介面。而負責 Reviewer 的我,就撰寫了 17 條 Test Case 來驗證這個元件的正確性 (緣起)。

* 在瀏覽器上試跑 Unit Test
* Unit Test 的原始碼

能用 CLI 命令行執行是自動化成功的關鍵,幾行指令就可以輕鬆達陣(系統必須有 Git 與 Node.js):
</span></span>

    # 安裝 PhantomJS
    sudo npm install phantomjs -g
   
    # 安裝 Grover
    sudo npm install grover -g
   
    # 下載 YouTube Iframe 元件
    git clone git://github.com/josephj/youtube-iframe.git
    cd youtube-iframe
    
    # 執行 Unit Test
    grover tests/unit/index.html --server

<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
是不是有感覺得多?但目前為止都還是手動測試,接下來我們要讓 Travis CI 替我們自動執行!
</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 18px;">第 2 步 - 登入</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
請先連至 https://travis-ci.org,點選 Travis CI 網站右上角的 "Sign in with Github",它會將你導到 GitHub Auth 頁,允許一些權限後它會重新將你導回 Travis 首頁。你會看到目前正在進行 Build 的專案。
</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 18px;">第 3 步 - 啟用 GitHub Service Hook</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
大多數的版本控制系統都有 Hook 的機制,<span style="color: red;">讓開發者可以在這個時間點去用程式去做一些事情</span>。像 miiiCasa 就是在 Commit 前 (pre-commit) 去執行 PHPCS 與 JSLint 確保開發者都有按照規範來寫 Code,不改好就不給 Commit。而 <span style="color: red;">Travis 則是利用 post-receive (每次 Push 後) Hook 去驅動自動建置的流程(抓取你的程式碼、Build、測試)</span>。

你需要手動去啟用這個 Hook(一次性),請點選右上角你的大頭貼,接著就會列出你所有可讀寫的 Repos:
</span></span>

<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
針對你想自動測試的專案(以小莊的 youtube-iframe 為例),點一下設定為 On:
</span></span>

<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
如此一來,在你每次執行 git push 到 GitHub 的這個 Repo 中時,就會自動觸發 Travis CI 來做事情了!
</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 18px;">第 4 步:增加 .travis.yml 檔</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
.travis.yml 是 Travis CI 設定檔,你需要透過此檔告訴 Travis CI 這是何種程式語言的專案、Build 前後該做哪些事情等,格式如下:
</span></span>

    language: node_js
    node_js:
        - "0.8"
    branches:
        only:
            - master
    notifications:
        email:
            - <通知人 Email 1>
            - <通知人 Email 2>

<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
以下是各個欄位的定義:

* language: 指定專案是為何種程式語言,你一定會覺得很奇怪,為什麼這邊要指定 Node.js 呢?小莊的 YouTube Iframe 跟 Node.js 一點關係也沒有啊。這其實是因為 Travis CI 的支援程式語言的列表上,它的 JavaScript 是指 Node.js、而不是指所有的 JavaScript 都可以使用… 但請放心地指定 node_js,<span style="color: red;">只要可以用來驅動測試的流程不就好啦</span>?
* node_js: Node.js 執行環境的版本編號,可指定多個。對我們來說其實也沒直接關係,就指定比較穩定的 0.8 吧!
* branch: 哪些 git branch 有修改時需要被 Travis 處理?這邊只處理 master 的。
* notification: 執行完畢的報告要寄給哪些 Email?
* 更多屬性請參考 Build a Node.js Project
</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 18px;">第 5 步:增加 package.json 檔</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
Node.js 的 Build 流程是透過一個叫 NPM (Node.js Package Management) 的東東。而前面一步我們指定專案為 node_js,是因為 NPM 也包含了測試的部分,只要在專案下執行這個指令,它就會去幫你做測試:
</span></span>

    npm test

<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
它怎麼知道你要去做什麼測試呢?其實 NPM 會讀取專案根目錄的 package.json 檔案,Node.js 開發者最常用的就是指定此專案的 Dependencies,以及實際測試會執行到的指令:
</span></span>

    {
        "name": "youtube-iframe",
        "devDependencies": {
            "grover": "*"
        },
        "scripts": {
            "test": "grover ./tests/unit/index.html --server"
        }
    }

<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
* devDependencies : 由於我們會需要 grover 以進行測試,請在這邊指定,會在 Build 時自動安裝此套件。(PhantomJS 是內建的,不需特別安裝)
* scripts > test : 需要指定我們前面利用 grover 在命令行跑 Unit Test 的指令。

Travis CI 在得知是 Node.js 專案後,自然就會使用 NPM 進行建置 (npm install .) 與測試 (npm test)。
</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 18px;">第 6 步:執行看看</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
設置到此已經全部完成,你接著可以用 git push 把修改過後的程式推到 GitHub 上,它會自動觸發 Travis CI 去建置與測試你的專案(不會立刻做,有時得排隊等個一兩分鐘)。做完後它會寄通知信到你 .travis.yml 所設定的信箱:

錯誤訊息:

成功訊息:

能夠看到後面綠色的訊息,表示你的修正並沒有影響到任何 Test Case。
</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 18px;">結語</span></span>
<span style="font-family: 微軟正黑體;"><span style="font-size: 16px;">
單元測試一開始寫起來是很辛苦的一件事,但它會對長遠的品質有很大的幫助。同事 Hawk 講過,老闆可能會問程式設計師改完後會不會出包,通常我們的把握程度可能不到八成。但如果已經具備了充足的單元測試 Case、跑過也沒問題,你就真的有十足的把握跟老闆講沒問題(這樣感覺好有 Guts!)

這邊文章是自動測試的一個入門,其實 Travis CI 也支援了 Chrome、Opera、Firefox 等瀏覽器,我們更能整合 Selenium 做 Functional Testing。測試整個網站的功能更是我未來希望努力的目標。

工程師的使命就是在自動化,讓機械去取代昂貴又沒效率的人工,我們才能有更多的時間投入在有意義的事情上。
</span></span>


0
clonn
iT邦新手 3 級 ‧ 2013-04-11 09:55:08

優質好文章,淚推!

0
Albert
iT邦高手 1 級 ‧ 2013-04-11 10:48:00

「持續整合」
不是 [因為想] 持續整合 [所以就可以] 持續整合
[持續整合]
是因為有 [framework] 有 [MDA] 核心

[單元測試]
不是有測試就能整合
是因為 [測試] 整合的 [framework] 有 [MDA] 核心
..........................................
台灣軟體軟趴趴..除了: 有[framework] 有 [MDA]一直都是趴在地上
..........................................

CI 的全名是 Continous Integration,中文是「持續整合」。軟體開發常見的問題就是不同的開發人員不斷地增加 Code,但卻缺乏規範及完整測試,所以經過一段時間的累積程式往往變的難以辨認及維護、其他人也沒辦法接手;或者由於規模變大必須要對程式做翻新以因應需求。所...(恕刪)

Albert iT邦高手 1 級‧ 2013-04-11 10:50:44 檢舉

軟體開發常見的問題就是不同的開發人員不斷地增加 Code,但卻缺乏規範及完整測試

一直改 framework
還是一直改 app

把事最對很好
作對的事更好

0
ted99tw
iT邦高手 1 級 ‧ 2013-04-11 11:22:58

哭哭哭

當程式人員真是辛苦,還好我沒怎麼在寫程式,因為都在忙coding...毆飛

0

我要留言

立即登入留言