2013/11/12

Mozart - HTML5 音樂指揮家遊戲

今年我們在 Node Knockout 2013 上面做了一個音樂指揮家的遊戲,可以用手機瀏覽器控制電腦瀏覽器播放音樂的節奏,並且實驗性的支持不同電腦發出不同聲部的音樂,就像交響樂團一樣!下面是我們的 DEMO 影片:



製作這個遊戲用到了以下技術:
  • Device Motion event: 用來偵測手機的加速度,[按這邊]偵測你的手機跟瀏覽器有沒有支援
  • WebSocket: 用來把手機的加速度資訊從手機傳到 node.js server 再傳到電腦端的網頁
  • MIDI.js: 播放 midi檔案,據我所知我們應該進行了大量的 patch XD
  • Audio API: 這邊不是我做的所以不是很清楚,不過就我所知有把 midi 音樂切分成不同聲部並且在不同的電腦播放。
  • Canvas: 在電腦端繪出加速度曲線,並且偵測到超過門檻值記錄時間來找到節拍
 晚點我們整理好後會釋出 source code,請拭目以待 :-)

另外如果覺得我們的點子很有趣,請到 [這個網址] 玩遊戲,並且按下左上角的 [VoteKO] 投給我們一票!


2013/09/03

Customize Your Firefox OS

禮拜六在風雨交加下到了高雄的 KSDG 講了 "Customize Your Firefox OS",從 Firefox OS 介紹、內建客制化能力以及如何透過修改源碼更深度的客制化。台灣有許多 OEM 廠商透過不同的 OS 來建制自己的解決方案,希望這個分享可以讓大家可以瞭解到如何從內建的客制化機制以及修改源碼來建制自己的 Firefox OS。


2013/07/15

Sinon.JS - unit test 斬斷相依性的利器

這兩天跟上周抽出一些時間正在弄 Firefox OS 中 system app 的 unit test,然後這次好好讀了 sinon.js 之後開始試著用他來處理一些相依性問題感覺還真不賴。

是這樣的,unit test 主要的目的是檢測特定的 unit 的工作是否正常運作,在這樣的狀況下我們僅測試該 unit 的邏輯與功能,至於跟它相依的部分通常會在另外一個 unit test 或是 integration test 的時候再測試。

但是問題來了,我想測試的 function 就是有用到其他外部 Object,你總不可能要我整個 Javascript 都沒用到 document.getElementById 吧(這還是有可能啦...)?

Sinon.JS 的其中一個功能就是可以隔絕 unit 對於其他 Object 的相依性,讓開發者可以單獨測試單一 unit 邏輯的好幫手。剛好我最近正在寫 system app 其中 ScreenManager 的 unit test,讓我們來看看 ScreenManager.init() 這個函式的相依性關係:



ScreenManager.init() 除了 call 外部的五個 Object 以外,還調用了自己的三個 function。而這些相依性都可以透過 Sinon 的功能隔絕他們。

在這之前先介紹一下 Sinon.JS 的三大物件:

Spy: 可以把一個 object/function wrap 起來,可以用來監看該 object/function 被呼叫的狀況。舉個例來說,假設我們要測試某個條件下 init() 就會呼叫到 turnScreenOn(), 我們可以用 spy 把 turnScreenOn wrap 起來:

var spyTurnScreenOn = sinon.spy(ScreenManager, 'turnScreenOn');
ScreenManager.init();
assert.isTrue(spyTurnScreenOn.called);

上面這段的意思是如果執行了 init() 之後,ScreenManager.turnScreenOn 會被執行到就代表正確。如果不僅執行到,而且傳入的參數一定要是特定值(比如說 true)也可以這樣用:

spyTurnScreen.calledWith(true);

除了上面提到這兩個 API 以外,還有幾個都還蠻實用的如 calledCount, calledTwice 等等,詳情請見 Spy API

Stub: 有 spy 的所有功能。但是造出 stub 之後,原本的 function 就不會再被呼叫了。我通常都會用 stub 把所有的相依性全部切掉。一樣是 turnScreenOn() 在 init() 會被呼叫的例子,如果使用 stub 代替 spy,不一樣的地方是 spy 還是會去呼叫原有 function,而 stub 則不會呼叫原有 function。

Mock: 有 Spy 跟 Stub 所有功能,但是還可以透過在跑之前設定期望值,最後再檢查 Mock 後的物件是否有照期待的執行。舉個 Sinon.JS 官網上面的例子,下面這樣的寫法可以讓你確認 jquery.ajax 最少被呼叫到了兩次,最多呼叫五次:

sinon.mock(jQuery).expects("ajax").atLeast(2).atMost(5);
...
jQuery.ajax.verify();

跟 Spy, Stub 不一樣的是 Mock 可以在測試還沒開始前就先預先指定期望值,而不是像 Stub 一樣需要等到呼叫後才能檢測條件是否成立。不過我目前測試的部分都可以用 stub 做到,Mock 暫時還沒用到,等到真的有用到之後再跟大家分享。

接下來我們先來看一下相對簡單的例子要怎麼測試:ScreenManager:toggleScreen():

toggleScreen: function scm_toggleScreen() {
  if (this.screenEnabled) {
    // Currently there is no one used toggleScreen, so just set reason as
    // toggle. If it is used by someone in the future, we can rename it.
    this._screenOffBy = 'toggle';
    this.turnScreenOff();
  } else {
    this.turnScreenOn();
  }

這個 function相當的簡單,只要 screenEnabled 是 true,_screenOffBy 就會是 toggle,並且 turnScreenOff 會呼叫到。所以只要利用 sinon.stub 分別作出 turnScreenOff 跟 turnScreenOn 的 stub,並且確認會不會正確的呼叫到即可。

suite('toggleScreen()', function() {
  var stubTurnOff, stubTurnOn;

  setup(function() {
    stubTurnOff = sinon.stub(ScreenManager, 'turnScreenOff');
    stubTurnOn = sinon.stub(ScreenManager, 'turnScreenOn');
  });

  teardown(function() {
    stubTurnOff.restore();
    stubTurnOn.restore();
  });

  test('if screenEnabled is true', function() {
    ScreenManager.screenEnabled = true;
    ScreenManager.toggleScreen();
    assert.equal(ScreenManager._screenOffBy, 'toggle');
    assert.isTrue(stubTurnOff.called);
    assert.isFalse(stubTurnOn.called);
  });

  test('if screenEnabled is false', function() {
    ScreenManager.screenEnabled = false;
    ScreenManager.toggleScreen();
    assert.isTrue(stubTurnOn.called);
    assert.isFalse(stubTurnOff.called);
  });
});

所以我在 5-6 行的地方用 sinon.stub() 分別對兩個 function 做了 stub,這樣一來當 toggleScreen() 呼叫這兩個 function 的時候,就會呼叫到假的 function 了。比如說 15-19 行的測試,當 screenEnabled 是 true 時,turnOff 要被呼叫到,而 turnOn 則不會被呼叫到。sinon.stub() 在這種種狀況就可以協助我們把相依性切除,只專注在測試目標 function 的邏輯。

但是我們的測試環境是將 Firefox OS 在瀏覽器上面運行,這時候有些 Object 很可能是不存在的。比如說 mozTelephony,所以就不能用上面 sinon.stub(Object, propertyName) 的方式造假物件,還是要利用 sinon.stub() 造一個新的物件。正是因為這樣我設計了 switchProperty/restoreProperty 的 helper function 用來替換掉可能不存在的物件。

再來看看一些比較進階的例子:如果你有一個 function 只希望在某種狀況下,才使用 stub 指定的回傳值,而其他狀況則是調用原本的 function 該怎麼寫呢?init() 的 setup() 正好有這樣的狀況:

var stubById = sinon.stub(document, 'getElementById').withArgs('screen')
    .returns(document.createElement('div'));

這段的意思是我們為 document.getElementById 做了一個假物件,他會直接回傳一個 div DOM 元件,但是只有在傳入參數為 screen 的時候才會發生,所以說:

document.getElementById('screen');

這個時候就會直接回傳一個假的 div,而不會真的到 DOM 裡面查詢一個 #screen 的元素,而且如果你查的是任何其他的參數,就還是可以正常運作!這樣可以確保你的 stub 只在你想要的參數被觸發!

還有一個很常需要對付的狀況:如果你的測項是需要被 callback 呼叫才能測該怎麼辦呢?舉例:你用了一個 addEventListener('click', callback),所以說一定要 click 才能呼叫 callback 該怎麼辦呢?用下面的 function:

sinon.stub(window, 'addEventListener').callsArgWith(1, evt)

如果一呼叫 addEventListener 就會直接呼叫第一個參數('click' 是 0, callback 是 1),這樣就可以測試更深層的狀況了。

總而言之,Sinon.JS 準備了一拖拉庫的造假工具給你用,Javascript Unit Test 有了像 Sinon.JS 這樣強大的工具,就可以切斷相依性,測試的更徹底!

另外這邊有一個 Firefox OS 裡面的範例:screen_manager.jsscreen_manager_test.js,有興趣的可以參考參考。

2013/07/04

Gaia Development Workflow

這是 Firefox OS - Gaia 開發時的 workflow,沒時間寫文章,先放張圖。


2013/06/30

Running Firefox OS Gaia on Windows


你永遠不知道下一個要解的 Bug 是什麼 :-)

上週送了一個 Pull Request 到 Gaia,Reviewer 非常好心的跟我說我的 patch 在 Windows 上面不會動,這時候才第二次意識到我們還是需要在 Windows 上面測試(上一次是我修 customization 的時候遇到的)。總之我這次很老實地把 Windows 的環境搞定了,在我等待 fetch pull request 的時候就來說說怎麼設定吧。

首先下載 Firefox Nightly

接著你需要 MinGW,寫這篇文章的時候我裝的是 mingw-get-inst-20120426.exe,或許你看到這篇文章的時候已經有新版出來了。安裝的時候記得要選下面這兩個:
  • MSYS Basic System
  • MinGW Developer ToolKit(這個我不確定要不要)
接下來安裝 git,Git 直接到官方網站下載就好了。安裝的時候選擇 "Run Git from the Windows Command Prompt",這樣可以直接在 MinGW 的環境使用 git。

然後接下來是裝 Python,一樣到官網下載就好了。 但是你需要把 Pyhton 的路徑加入 PATH 這個環境變數裡面,在 My Computer 按右鍵,選 properties -> Advanced -> Environment Variables,找到 Path 把 C:\Python27 加入。

最後的一個步驟,安裝一些 build gaia 會用到的指令:

$ mingw-get install msys-wget
$ mingw-get install msys-zip
$ mingw-get install msys-unzip

這樣就搞定啦!

打開 MinGW Shell 找個風水吉地 git clone gaia,進入目錄後用下面指令 build 出 profile 檔:

$ DEBUG=1 make

第一次會需要久一點,主要是下載一些 xulrunner 之類的東西。好了之後用下面的指令啓動你的 Nightly。依據你 clone Gaia 的地方會有些不一樣,自己摸索一下吧。

$ /C/Program\ Files/Nightly/firefox.exe -profile /C/MinGW/msys/1.0/home/IEUser/gaia/profile-debug/

這樣就可以在 Windows 的 Nightly 上面啓動 Gaia 囉,在裡面收信也沒問題喔 LOL


2013/06/25

關於辦 COSCUP 的那些美好回憶

下面是一個可口可樂的廣告,幾年前我看到這個廣告的時候就很感動,先跟大家分享一下。




不知道這個 Blog 的觀眾多少人辦過研討會?從 2005 年準備要跟 ICOS 分成兩個研討會開始我就陸陸續續地參加 COSCUP 的工作人員,從聽眾、場務、講者、主持人到最近連續四年擔任記錄組與議程組成員。

今年我擔任了議程組長。

我記得在去年慶功宴的時候,我跟 Bob 說,『Hey, 議程組我混熟了,明年我來當議程組長吧』我壓根沒想到過了一季之後,我們決定要在 TICC 台北國際會議中心辦 COSCUP。

更沒想到的是半年後,我們不只要在 TICC 辦,我們準備籌辦八軌議程。

親愛的觀眾,八軌議程阿,這可不是鬧著玩的。

兩年前我擔任紀錄組長,在約八百人規模,四軌議程下我都累得跟狗一樣了。 然而今年 COSCUP 就像是個小型社會,高度壓力,密集溝通,工作追蹤。當然工作人員之間臉紅脖子粗的爭吵也少不了。我再度的累得跟狗一樣,我想要跟金凱瑞在電影『一個頭兩個大』一樣,自己賞自己一拳,然後再給自己一個過肩摔。

我對著躺在地上的那個 Loser 說:嘿你看吧,你這個魯蛇,你自找的。我就像是躺在大雨裡的泥濘,雨滴落在地上的聲音就像是那些邊拍手邊笑彎了腰的圍觀人群嘲笑我。

在這種夜裡,我常常會想起這個廣告的片段。

『大部份的人會告訴你,你選擇了一個最糟糕的時刻。 ... 我們正經歷一場危機,這不是什麼好事』

白天的工作已經很忙了,晚上回家繼續默默地回著根本不可能看完的信件。

『可是這會讓你更堅強』

讓我想到 Bob 在某年的慶功宴舉杯的畫面

敬你。

這也是很艱難的一年啊,那年我們把報名系統弄炸了(笑

你說我還記得多少有多辛苦?我心裡只記得當所有結束之後,那些快樂,震撼還有為了結束的那一點惆悵。

今年我完成了我從來沒想過的目標,以及從未想過的困難。來自那些 1,500 人入場的壓力,來自八軌議程的壓力,還有截稿前兩週只有三十個人投稿的壓力。

這些背在肩膀上的事情讓人咬牙的覺得痛苦。

但是這些終究會過去的。

當拉開布幕後,當我們像螞蟻一樣在會場穿梭後,當大家在舉杯致敬後, 我很確定那些所有的辛苦都會在落幕之後煙消雲散。而我們現在唯一要做的,就是為了拉開序幕的那刻做好準備。

今年夏天你打算做什麼呢?

我打算辦一個台灣最大的開放源碼研討會 ;-)


BTW, [工商服務] 個人贊助活動仍在招募中,歡迎個人贊助喲。



2013/01/22

威能的 Firefox OS unit testing

今天在謀智台客發表了篇文章,主要是講 Firefox OS 在 unit testing 有個不錯的機制,就是設定妥當後,當你在任何編輯器或 IDE 按下儲存後,unit testing 就會自動開始測試跟你剛剛儲存的那個 javascript 相關的測項,最後用 Mac 的 notification center 或是 Linux 的 libnotify 告訴你測試結果,像下面這樣:



可以讓你隨時都知道自己有沒有把任何東西搞爆了。

有興趣的可以看一下

缺它不可!靈活運用 Firefox OS Gaia 的單元測試

2013/01/02

travis + jsdom: Javascript unit test 的最後一塊拼圖

在昨天晚上出去跨年前,我把自己之前練習寫的 weather app 重新整理過了一次,主要是把存取 DOM 的部分聚集起來然後寫 unit test 以及開始用 travis 測試。

travis 是好幾個朋友跟我提過但是我都沒認真看怎麼玩,終於在上次的 Hacking Thursdaykanruczchen 討論了幾番之後決定還是自己來試試看比較有感覺。

travis 提供的服務就如 Jenkins 一樣,是一個 CI (continuous integration) 的服務 -- 但是不用自己架 Jenkins 對我來講實在太棒了,我是真的很怕麻煩還要自己維護 CI service。Travis 是直接跟 github 整合的 CI service,所有在 github 的專案都可以很簡單地用 travis hook 透過一個 .travis.yml 來指定 build 以及測試的方式。

jsdom 則是一個在 node.js 上面的 DOM 實作,意思就是說可以利用它在 node.js 裡面操作 window, document 等元件。這個對我來說真是大大的福音。因為 mobile-weather 大多數的邏輯操作都是跟 DOM 相關的,如果剔除這些其實也沒什麼邏輯好測試的。jsdom 讓我可以在 node.js 裡面操作 DOM 等於我就可以驗證一些跟 DOM 相關的邏輯。

jsdom 真的是 javascript unit test 的最後一塊拼圖,有他之後我們就可以測試跟 DOM相關的部分了 :D

最後我在 unit test 採用的是 mocha, chai,前者是 node.js 上面的 unit test framework,後者是 assertion library。

首先第一個步驟是寫 package.json。因為我們的 unit test 會跑在 node.js 裡面,所以我們要先寫 package.json 來交代要用到哪些 library,並且我們可以在裡面指定當執行 "npm test" 的時候要如何執行測試。



4 - 8 行的部分是指定 devDependencies 要使用 mocha, chai 跟 jsdom。9 - 11 行做的是執行 npm test 的時候事實上會執行 node_modules/.bin/mocha -u tdd test/weather_test.js。這之後如果換了 unit test framework 也可以很輕易地從這邊換掉測試方式 :-)

至於 mocha -u tdd 是 mocha 同時有提供 BDD 跟 TDD 的 interface 可以置換,我這邊是採用 TDD。

接下來就可以開始來寫 unit test 了。

目前的 unit test 測試的項目還不多,先把架構弄好之後再慢慢補上。第三行是把 index.html 轉成字串儲存下來。在 7 - 12 行的地方利用 jsdom 生成 window 跟 document,並且每次都重新生成 Weather object 跟重新初始化,主要的目的是讓每個 test case 不會互相影響。test case 我們挑在 20-25 行的 'updateWeekday' 來看。我們知道 2013/1/1 是禮拜二,而 updateWeekday 裡面就會把 day0.textContent 設定為 'T' (Tuesday),接下來依序設定。


所以我們就可以假定 day0.textContent 是 T, day4.textContent 是 S 這樣的方式來測試。寫完 test cast,下達 npm test 就可以進行測試囉。

這樣 unit test 就告一個段落,最後就是把它移到 travis 上面就大功告成了!先在根目錄開個 .travis.yml,內容是你的 travis 組態:

這邊我們指定採用 node.js,並且只測 0.8 這個版本。其實我們只有在 unit test 的時候會用到 node.js,基本上他還是一個一般的 browser app。所以不需要測試多個 node.js 版本。把這邊全部都 push 上 github 後,用你的 github 帳號登入 travis。然後在 account 的頁面找到你的 project 把右邊的 switch 推到 on。

當下次你 commit & push 的時候,就會看到 travis 開始測試你的 project 囉!

並且有每一次的 build log:


超棒的吧,不用在自己架 Jenkins 囉!新年快樂!

目前的計劃是如果我們 fork jsdom 然後把 Firefox OS 有用到的 API 加進去,我們就可以開心的丟掉 Browser 測試 Firefox OS app 了 :D