2014年12月5日 星期五

故事爸爸的十五分鐘

上週到國小一年級的女兒教室當故事爸爸,同時也有另外一位(應該是學務處的志工)故事媽媽,我們在早自習時段各講了一個故事給女兒班上的小鬼頭們聽。看到小鬼頭的反應,讓我很有想法。

首先上場的是故事媽媽,她講了一個海馬故事 —— 海馬爸爸和海馬媽媽生了一堆小海馬(這裡穿插了一些海馬知識的介紹,例如公海馬有育兒袋),其中一隻特別不愛練習變色(繼續穿插海馬小知識,海馬靠變色隱身在環境中躲避天敵),有一天大難來臨,海馬都被大魚吃了,但不愛變色的小海馬卻剛好躲在石堆裡睡覺沒被吃(!),還救出了全家,最後被海馬公主看上,稀哩呼嚕居然變成駙馬。


credit: snaulkter  CC 2.0

故事媽媽講得認真,長得又漂亮,連講帶演,最厲害的是她居然會模仿海馬游泳的樣子,我下巴都快掉下來了。小朋友的反應呢?還好而已,並不是很熱烈,有些人托腮,有些人打呵欠。我倒是沒看到有人吃雞腿和滑手機,還好還好!

為什麼?

因為海馬故事本身一點都不有趣啊!一個不有趣的故事,表演得再生動再誇張,也沒有辦法牢牢吸住小朋友。



我講的是『好想吃榴槤』,我女兒指定我講的故事 —— 有隻天真的小老鼠沒吃過榴槤,於是跑去問他的好朋友獅子、山羊、河馬榴槤倒底是什麼味道。獅子、山羊、河馬根本沒吃過,但都胡謅一通,硬是說榴槤超好吃,吃起來像芭樂鳳梨與西瓜。小老鼠聽得傻了,口水都流出來了,於是...................... 哈哈,賣個關子。

這本故事書確實也是我最喜歡的一本,故事就是有趣,圖畫又可愛,每次看到裡面河馬張大嘴巴大喊:「當然吃過榴槤啊!味道跟芭樂一樣!」我就覺得他真的是我小時候認識的某個朋友,沒心機的不懂裝懂又愛吹牛,但很可愛。看到最後一頁大結局,又會覺得這群動物真的太好笑太可愛了,好想每一隻都抓起來捏一捏。

小朋友的反應最直接,每一雙眼睛都牢牢地盯著我,露出好奇的神情,到最後的開懷大笑,這就是故事吸引力的差別,

畢竟故事本身有趣才是一切!


 credit: Hafiz Issadeen CC 2.0


2014年12月4日 星期四

AngularJS Unit Testing

最近換工作以來突然寫Web UI的機會變多了,原本的UI是陳年老code了,多用 jquery 搭配各式 componant 再加上各種 event handler 刻出來的,程式裡到處都在操作 DOM,頗難維護。之前在W公司時就看到專業的前端工程師使用 backbone.js 這種 MVC framework,當時就頗想偷師。現在就趁此機會撥亂反正一下,把 MVC 架構帶入目前產品中,讓 UI code 更好維護。

我研究了一下最近常聽到的 javascript MVC framework,依照“要用就要用最潮的技術”的原則引領之下,選擇了 angularjs 這個 model-view-whatever framework。先不論 angularjs 與其他 framework 相比有哪些優缺點,光看 tutorial 裡每一節都有 unit test 該怎麼寫就讓身為 TDD 愛用者的我小高潮了。

接下來就分享一下我這一個月來學習 angularjs 與 unit test 的心得感想吧。

angularjs + jasmine + karma


javascrip unit testing 的工具非常多,因為太多了,我都採用 angularjs tutorial 裡面使用的 tool: jasmine test framework + karam test runner.

Test Framework - jasmine


jasmine 是個 javascript testing framework,在單元測試中的角色如同寫 c++ / java 時用到的 cppunit 與 junit 一樣,讓你方便地寫出 test cases、組合成 test suite、比較預期結果與實際結果、回報執行結果。jasmine 比較特別的是用 it() 來定義 test case,引導你要把要測試的行為用完整句子寫出來。如果你的作文能力不錯的話,真的能把 test case 寫得像文章一樣好讀。

describe('FizzBuzz', function() {

    it('outputs the original number if the input number is not multiple of 3 or 7,
        function() {
            expect(fizzbuzz(1)).toBe("1");
            expect(fizzbuzz(2)).toBe("2");
            expect(fizzbuzz(4)).toBe("4");
    });

    it('outputs "fizz" if the input number is multiple of 3', function() {
            expect(fizzbuzz(3)).toBe("fizz");
            expect(fizzbuzz(6)).toBe("fizz");
            expect(fizzbuzz(9)).toBe("fizz");
    });

    it('outputs "buzz" if the input number is multiple of 7', function() {
            expect(fizzbuzz(7)).toBe("buzz");
            expect(fizzbuzz(14)).toBe("buzz");
            expect(fizzbuzz(28)).toBe("buzz");
    });

    it('outputs "fizzbuzz" if the input number is multiple of 7 and 3',
       function() {
            expect(fizzbuzz(21)).toBe("fizzbuzz");
    });
});

我的作文能力還OK嗎?

Test Runner - karma


以前用 C++ / C# / java 時都比較不會注意到 test runner 這件事,反正你用什麼 testing framework,就是要用專用的 test runner,IDE 裡也許有內建,所以無感於 test runner 的存在。但是做 UI javascript testing 時有個大不同,UI 上的 javacript 是要丟到瀏覽器上面跑的,怎麼丟給瀏覽器?要丟給哪些瀏覽器?Test runner 就是幫我們搞定這些事情。

我這次選用的 Karam test runner 很厲害,可以跟各種 testing framework 與瀏覽器整合,無論用 jasmine、mocha 或是其他 framework,要在 chrome、firefox、safari、ie 執行測試,karma 都可以搞定。karma 還有個很貼心的服務—偵測到檔案變更時它會自己重新執行所有 test cases,對 TDD 更方便了。

karam 一次幫我搞定四個瀏覽器,非常盡責



安裝 karma 請參考這裡,安裝完可以用 karma init 產生預設的 config 再來修改。

module.exports = function(config) {
  config.set({

    basePath: '../',

    // 使用 jasmine test framework
    frameworks: ['jasmine'],

    // 哪些 file 要載入測試。陷阱注意:順序很重要
    files: [
        'test/phantomjs-fix.js',
        'js/angular.min.js',
        'test/angular-mocks.js',
        'js/volume_dialog.js',
        'test/spec/*spec.js'
    ],

    // 產生 progress 訊息與 junit report
    reporters: ['progress', 'junit'],

    // junit report 檔案名稱
    junitReporter: {
        outputFile: 'test-results.xml',
        suites: ''
    },

    port: 9876,

    colors: true,

    logLevel: config.LOG_INFO,

    autoWatch: true,

    // 要在哪些瀏覽器上面執行
    browsers: ['Chrome', 'Firefox', 'Safari', 'PhantomJS'],

    singleRun: false
  });
};


設定裡頭有兩個陷阱,一是一定要把 angular-mock.js 加入到 files 裡,二是 files 是有順序性的,angular.js 在前,angular-mock*.js 要在後。搞這個就搞死我了。

Angularjs Unit Test 到底怎麼寫


angularjs developer's guide 浮光掠影的解釋了 unit testing 要怎麼做。但看完一定沒法馬上動手寫,最有效的方法還是去找人做一遍給你看,哈!啪一下額頭就是 youtube,這個 Introduction to angularjs unit testing 很適合。


整合到 CI - 沒有 GUI怎麼辦?


寫了測試就一定要整合到 CI 裡,否則就是白寫。ㄟ .... 只是測試都是跑在瀏覽器上面,我的 CI server 沒有 GUI 沒有瀏覽器怎麼辦?就是 PhantomJS 出場的時機了。PhantomJS 也是個以WebKit 為基礎的瀏覽器,只是他不真的把畫面呈現出來,若想操作存取網頁,必須透過 PhantomJS 提供的 javascript API。

要用 PhantomJS 執行 unit test,除了安裝 PhantomJS 外,還需要 karma-phantomjs-launcher。執行 karma 時可以用 karma start --browsers PhantomJS 指定只要用 PhantomJS

用 PhantomJS 執行測試通常也很容易撞到這座牆:PhantomJS 不支援 Function.bind() method。解法就是自己實做吧!


// phantomjs-fix.js
// phantomJs does not support bind().
// See https://github.com/ractivejs/ractive/issues/679
Function.prototype.bind = Function.prototype.bind || function (thisp) {
    var fn = this;
    return function () {
        return fn.apply(thisp, arguments);
    };
};

整合到 CI - 產生 junit report


最後,在安裝 karma-junit-reporter 並設定產生檔案的名稱就能產出xml report.