about 5 years ago

Install

  1. npm install karma --save-dev (official instruction, --save-dev will register in dependency package)
  2. Karma 用 npm install -g 安裝後沒辦法直接輸入使用,要用路徑去執行,但是我改用 nvm 安裝 node.js 所以導致路徑很冗長,所以官網的安裝方式建議是直接針對你要進行測試的專案作個別安裝,這樣路徑也會比較短,我有在我的 .bashrc 中加入alias karma='./node_modules/karma/bin/karma'

Intro

karma 主要用來驅動測試,但還可以設定使用的 test framework (test runner: mocha, jasime,...etc.)、測試起始路徑 basePath(如果有設定的話,這個路徑會被加到 files list 中,然後在啟動的瀏覽器中加到 javascript 屬性 src 中作為 prefix)、設定要使用的瀏覽器、哪些檔案要被載入以便存取使用、哪些檔案只觀察變化用、哪些檔案要被排除、瀏覽器啟動時要用 port、預處理器...等等

Usage

  1. 建立 config 檔案:karma init <name>.config.js

my.config.js
// Karma configuration
// Generated on Wed Jan 07 2015 16:18:19 GMT+0800 (CST)

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

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',


    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['mocha'],


    // list of files / patterns to load in the browser
    files: [
      'test.js'
    ],


    // list of files to exclude
    exclude: [
    ],


    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
    },


    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['progress'],


    // web server port
    port: 9876,


    // enable / disable colors in the output (reporters and logs)
    colors: true,


    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,


    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,


    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    browsers: ['Chrome'],


    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: false
  });
};

2.啟動 karma 來進行測試工作:karma start <name>.config.js

karma-mocha-chai-sinon

Notice

karma 啟動瀏覽器來執行測試,會找不到 node.js 的 require(),它好像是用 RequireJS 來做為引入 module 的方法

stack-overflow

但是 sinon 和 assert 都要用 require 來引用模組啊!儘管用了 require.js ,還是會因為載入慢導致在啟動瀏覽器後來找不到 sinon

Chrome 39.0.2171 (Mac OS X 10.10.1) ERROR
  Uncaught Error: Module name "sinon" has not been loaded yet for context: _. Use require([])
  http://requirejs.org/docs/errors.html#notloaded
  at /Users/veck/Desktop/JSTest/5/node_modules/requirejs/require.js:141

注意到 npm 上有 karma-chai 和 karma-sinon 這兩個專案,做法都是安裝 plugin 後,只要在 config.js 的 framework 填入 'sinon' 和 'chai',就可以直接在 test 中 不用 require 就使用 sinon 和 chai 物件

karma-sinon

先來解決使用 sinon 的問題:

  1. npm install karma-sinon
  2. 修改 .config.js 的 frameworks:frameworks: ['mocha', 'sinon']
  3. 修改 test.js,不需要 var sinon = require('sinon') 了,這設計很方便的直接可以用
  4. karma start <name>.config.js !
Chrome 39.0.2171 (Mac OS X 10.10.1) mocha-chai-sino calls the original Functionn FAILED
    ReferenceError: assert is not defined
        at Context.<anonymous> (/Users/veck/Desktop/JSTest/test_karma/test.js:24:7)
Chrome 39.0.2171 (Mac OS X 10.10.1): Executed 1 of 1 (1 FAILED) ERROR (0.014 secs / 0.003 secs)

OK!解決了找不到 require 和 sinon 沒有載入的問題,剩下 assert

karma-chai

node.js 原本的 assert module 還是要 require 才能用,想起在學 chai 的時候,chai 的官網有說 chai 也有實現 traditional style assert,因此 assert 可以用 karma-chai 的 chai 來實現了

  1. npm install karma-chai
  2. 修改 .config.js 的 frameworks:frameworks: ['mocha', 'sinon', 'chai']
  3. 修改 test.js,不需要 var assert = require('assert') 了
  4. karma start <name>.config.js !
  5. YA!

最後整個 test.js:

/* function under test */
function once(fn) {
   var returnValue, called = false;
   return function () {
      if (!called) {
         called = true;
         returnValue = fn.apply(this, arguments);
      }
      return returnValue;
   };
}

/* test code */
describe("karma-mocha-chai-sino", function(){
   it("calls the original Functionn", function () {
      var callback = sinon.spy();// 建立 sinon.spy 物件
      var proxy = once(callback);

      proxy();

      assert(callback.called);
   })
});

解到此時,正好休息去洗了個澡,想說既然這 sinon 和 chai 的組合很常在 JavaScript unit test 中用到,應該可以寫個 karma 和 sinon 及 chai 的 plugin,就不用安裝和設定兩次 frameworks 了,先上網查了一下,果然也有人整了一個 karma-sinon-chai,config.js 那邊要用 sinon-chai 這個 framework name

如此一來,就解決 karma 是 browser-based 的問題了!

Refernce

 
about 5 years ago

Install

  • npm install sinon
  • 相依於專案

Intro

Sinon 主要是用來作 unit test 上測試案例實體的區隔,像是 mock object 這樣的應用,也就是建立測試會用到的實體(例如裝置、呼叫到的函數),如此一來測試時就可以斬斷相依性,單獨測試某個部分

Sinon 有三大物件:spy、stub、mock,分別是 test double 中的三種替身方法:Test Spy、Test Stub、Mock Object

  • Spy:把物件或函數包起來『監視』。例如:sinon.spy(math, "power"); 監視 math.power 這個 function,然後在後面當呼叫 math.power() 後,就可以查看函數的相關訊息:
sinon.spy(math, "power");  // 監視 math.power()

...呼叫過 math.power()...

math.power.callCount > 1; // 查看函數被呼叫的次數
math.power.withArgs("xxx").calledOnce; // 該函數是否被 xxx 呼叫過一次  
math.power.restore(); // 取消監視
  • Stub:主要用來切割相依性,讓要測試的單元不受其他單元影響。以下為例,我們只是要測試 math.power 的功能,是否能正確運作(例如正確返回值),如果還要去想說 math.power 可能需要給定什麼參數才能滿足其呼叫的其他函數,就被相依性困住了,因此 stub 這樣用就可以在測試 math.power 時忽略掉 math.power 實際上會呼叫到的其他函數,或使用到的其他物件
var stub = sinon.stub();                 // 建立 stub 物件 
var stub = sinon.stub(math, "power");   // 將 math.power() 替換成為 stub 物件
var stub = sinon.stub(math, "power", function(){
                                        // 用 function 替換
                                        }); 
    
stub.returns(10);   // stub() 總是 return 10
stub();             // 呼叫 stub()

stub.throws("xxx"); // stub() 總是 throw "xxx"
stub(); 

stub.withArgs(1).returns(10);   // 當 stub(1) 時 return 10
stub(1); 

stub.restore();  // 用 stub.restore() 或 math.power.restore() 還原物件

在 sinon 中,stub 也具有 spy 的功能,因此可以使用 spy 的方法,例如 callCount

  • Mock:= Spy + Stub + 可以在執行測試前設定預期結果,最後檢查 mock obejct 是否有照計畫執行。如下例子中,我們一開始建立了一個 math 物件,因為不像 spy 或 stub 是用我們真正要側的 math 物件,所以是 mock 的 object
/* 建立 mock object */
var math = { ...    };
var mock = sinon.mock(math);
    
/* 設定 math.power(10) 預期要有 3 次 */
mock.expect("power").atLeast(3).withArgs(10); 
    ...
mock.verify();  // 檢查現在的 math 是否與其條件
mock.restore(); // 復原物件

Usage

同一測試三種版本

Spies

1 // Function under test
2 function once(fn) {
3    var returnValue, called = false;
4    return function () {
5        if (!called) {
6            called = true;
7            returnValue = fn.apply(this, arguments);
8        }
9        return returnValue;
10    };
11 }
12
13 it("calls the original function", function () {
14    var spy = sinon.spy();
15    var proxy = once(spy);
16
17    proxy();
18
19    assert(spy.called);
20 });

這樣寫的目的,就是在 line 17 先呼叫一次 once,傳入的 spy 物件在 once 中被使用,因此可以在後面 line 19 去查看 once 是否有被呼叫過(在此 spy 被使用過等效於 once 被呼叫)

Stubs

1 // Function under test
2 function once(fn) {
3    var returnValue, called = false;
4    return function () {
5        if (!called) {
6            called = true;
7            returnValue = fn.apply(this, arguments);
8        }
9        return returnValue;
10    };
11 }
12
13 it("returns the return value from the original function", function () {
14    var stub = sinon.stub().returns(42);
15    var proxy = once(stub);
16
17    assert.equals(proxy(), 42);
18 });

這樣寫就是用 stub 替換掉實際要傳入 once() 的物件,並且設定 stub 總是 return 42

Mocks

1 // Function under test
2 function once(fn) {
3    var returnValue, called = false;
4    return function () {
5        if (!called) {
6            called = true;
7            returnValue = fn.apply(this, arguments);
8        }
9        return returnValue;
10    };
11 }
12
13 it("returns the return value from the original function", function () {
14    var myAPI = { method: function () {} };
15    var mock = sinon.mock(myAPI);
16    mock.expects("method").once().returns(42);  // set expect behavior
17
18    var proxy = once(myAPI.method);       // register
19
20    assert.equals(proxy(), 42);           // invoke and test return value
21    mock.verify();                        // verify expected behavior 
22 });

在 line 14-15 我建立了一個 mock object,這個 mock object 有一個 function 叫做 method,我們設定它預期應該要以 method 為參數執行 once 並且回傳 42

跟 stub 不同的是,我們可以在最後 line 22 的地方去驗證這個 mock object 是否真的照著預期的執行

Use with Mocha and Chai

跟 mocha 和 chai 一起用的方法如下範例:

/* describe 與 it 即 mocha 方法 */
describe("mocha-chai-sino", function(){
    it("calls the original function", function () {
        var callback = sinon.spy();     // 建立 sinon.spy 物件
        var proxy = once(callback);

        proxy();

         assert(callback.called);           // 使用 assert 方法,可替換成 chai 的 api
    })
});

完整簡單測試:

  var sinon = require('sinon')
  var assert = require('assert')
   
  /* function to be test */
  function once(fn) { 
     var returnValue, called = false;
     return function () {
        if (!called) {
           called = true;
           returnValue = fn.apply(this, arguments);
       }
        return returnValue;
     };
  }
  
  /* test code */
  describe("mocha-chai-sino", function(){
     it("calls the original Functionn", function () {
        var callback = sinon.spy();// 建立 sinon.spy 物件
        var proxy = once(callback);
        
        proxy();
        
        assert(callback.called); // 使用 assert 方法,可替換成 chai 的 api
     })
  });

測試結果:

  mocha-chai-sino
    ✓ calls the original Functionn 


  1 passing (6ms)
 
about 5 years ago

Install

  1. npm install chai, bundle with package
  2. 官網補充:dependency and mocha

Intro

在 mocha 中,我們有這樣一段程式:

describe('Array', function(){    
   describe('#indexOf()', function(){
      it('should return -1 when the value is not present', function(){  
         assert.equal(-1, [1, 2, 3].indexOf(5)) 
         assert.equal(-1, [1, 2, 3].indexOf(0))
      })
   })
});

其中 node 本身雖然也提供 assert,但是 chai 專門針對這部份提供更多 API,以下是官網的說明:

The assert style is exposed through assert interface. 
This provides the classic assert-dot notation, similiar to that packaged with node.js. 
This assert module, however, provides several additional tests and is browser compatible.

Usage

Assert

 var assert = require('chai').assert
  , foo = 'bar'
  , beverages = { tea: [ 'chai', 'matcha', 'oolong' ] };

describe("try chai with mocha", function(){
     it("typeOf", function(){
        assert.typeOf(foo, 'string', 'foo is a string');
     })
  
     it("chai's equal", function(){
         assert.equal(foo, 'bar', 'foo equal `bar`');
     })
  
     it("lengthOf", function(){
        assert.lengthOf(foo, 4, 'foo`s value has a length of 3');
     })
  
     it("lengthOf in array", function(){
        assert.lengthOf(beverages.tea, 3, 'beverages has 3 types of tea');
     })
  });

(因為是不同的 test case,所以一個 it 只會執行一種 assert,因此才要分開不同 it)

存檔成 test.js 後就可以用 mocha 來執行測試了:

 try chai with mocha
    ✓ typeOf 
    ✓ chai's equal 
    1) lengthOf
    ✓ lengthOf in array 


  3 passing (9ms)
  1 failing

  1) try chai with mocha lengthOf:
     AssertionError: foo`s value has a length of 3: expected 'bar' to have a length of 4 but got 3
      at Function.assert.lengthOf (/Users/veck/Desktop/JSTest/2_mocha_chai/node_modules/chai/lib/chai/interface/assert.js:890:37)
      at Context.<anonymous> (/Users/veck/Desktop/JSTest/2_mocha_chai/test.js:15:14)
      at callFn (/Users/veck/.nvm/v0.10.35/lib/node_modules/mocha/lib/runnable.js:251:21)
      at Test.Runnable.run (/Users/veck/.nvm/v0.10.35/lib/node_modules/mocha/lib/runnable.js:244:7)
      at Runner.runTest (/Users/veck/.nvm/v0.10.35/lib/node_modules/mocha/lib/runner.js:374:10)
      at /Users/veck/.nvm/v0.10.35/lib/node_modules/mocha/lib/runner.js:452:12
      at next (/Users/veck/.nvm/v0.10.35/lib/node_modules/mocha/lib/runner.js:299:14)
      at /Users/veck/.nvm/v0.10.35/lib/node_modules/mocha/lib/runner.js:309:7
      at next (/Users/veck/.nvm/v0.10.35/lib/node_modules/mocha/lib/runner.js:248:23)
      at Object._onImmediate (/Users/veck/.nvm/v0.10.35/lib/node_modules/mocha/lib/runner.js:276:5)
      at processImmediate [as _immediateCallback] (timers.js:354:15)

Expect

BDD(behavior driven development) 的開發模式產生了兩種常用的測試方法:expectshould

(以下省略 mocha 部分程式碼)

var expect = require('chai').expect
  , foo = 'bar'
  , beverages = { tea: [ 'chai', 'matcha', 'oolong' ] };

expect(foo).to.be.a('string');
expect(foo).to.equal('bar');
expect(foo).to.have.length(3);
expect(beverages).to.have.property('tea').with.length(3);

Should

shouldexpect 基本上一樣,差別在於方法的實作上,expect 是做成 global function,而 should 是 class property,也就是物件可以用 property 的方式呼叫 expect 的測試案例

還有一點就是,expect 引用是用 require('chai').expect,但 should 是用 require('chai').should()

NOTICE: This style has some issues when used Internet Explorer, so be aware of browser compatibility.

var should = require('chai').should() //actually call the the function
  , foo = 'bar'
  , beverages = { tea: [ 'chai', 'matcha', 'oolong' ] };

foo.should.be.a('string');
foo.should.equal('bar');
foo.should.have.length(3);
beverages.should.have.property('tea').with.length(3);

Plugin

Chai 可以透過下面的方法來取用外部工具

chai.use(_chai, util){
    // your helpers here
}
 
about 5 years ago

Install and use

  1. npm install -g mocha 安裝完以後,就可以直接用 mocha 指定來執行測試
  2. 執行測試,mocha 會自動執行檔名為 test.js 的檔案,如果沒有會出現錯誤,你可以用 mocha <testfile> 來執行測試

Intro

Mocha 是一個 JavaScript 的 test framework,它提供用來組織 unit test 的 API,並且執行 test

Usage

var assert = require("assert")
describe('Array', function(){
  describe('#indexOf()', function(){
    it('should return -1 when the value is not present', function(){
      assert.equal(-1, [1,2,3].indexOf(5));
      assert.equal(-1, [1,2,3].indexOf(0));
    })
  })
})

require

var assert = require('assert')

這個模組其實是 node.js 內建的,提供 assertion 相關 API

mocha 可以認得 describe()、it() 等函式,不需要 require

describe

describe(moduleName, testDetails)

形成一個測試區塊

moduleName 是要用到的測試模組名稱,那是測試人員隨便取的

testDetails 放置測試內容,以 callback 實作

it

it (info, function)

最基本的測試單元,通常一個 it 對應一個實際的 test case

info 是輸出訊息

function 放是這種 test assertion

Asynchronize

fs = require('fs');
describe('File', function(){
    describe('#readFile()', function(){
        it('should read test.ls without error', function(done){
            fs.readFile('test.ls', function(err){
                if (err) throw err;
                done();
            });
        })
    })
})

done() 用來指示抵達 callback 串的最深處,開始要一層層返回

这里可能会有个疑问,假如我有两个异步函数(两条分叉的回调链),那我应该在哪里加 done() 呢?实际上这个时候就不应该在一个 it 里面存在两个要测试的函数,事实上 "一个 it 里面只能调用一次 done ",当你调用多次 done 的话 mocha 会抛出错误。所以应该类似这样:(這部份是大陸網友加上去的,官網沒有提到這個,但很實用)

fs = require('fs');
describe('File', function(){
    describe('#readFile()', function(){
        it('should read test.ls without error', function(done){
            fs.readFile('test.ls', function(err){
                if (err) throw err;
                done();
            });
        })
        it('should read test.js without error', function(done){
            fs.readFile('test.js', function(err){
                if (err) throw err;
                done();
            });
        })
    })
})

像是以下的多個 assertion,因為 test case 不同,因此只有 typeOf 會被執行

describe("try chai with mocha", function(){
        assert.typeOf(foo, 'string', 'foo is a string');
        assert.equal(foo, 'bar', 'foo equal `bar`');
        assert.lengthOf(foo, 4, 'foo`s value has a length of 3');
        assert.lengthOf(beverages.tea, 3, 'beverages has 3 types of tea');
  });

要改成這樣才會每個都執行到:

describe("try chai with mocha", function(){
     it("typeOf", function(){
        assert.typeOf(foo, 'string', 'foo is a string');
     })
  
     it("chai'a equal", function(){
         assert.equal(foo, 'bar', 'foo equal `bar`');
     })
  
     it("lengthOf", function(){
        assert.lengthOf(foo, 4, 'foo`s value has a length of 3');
     })
  
     it("lengthOf in array", function(){
        assert.lengthOf(beverages.tea, 3, 'beverages has 3 types of tea');
     })
  });

Hook

describe('hooks', function() {
  before(function() {
    // 全部測試開始前就執行這裡
  })
  after(function(){
    // 全部測試結束後才執行這裡
  })
  beforeEach(function(){
    // 每個測試開始前都執行這裡
  })
  afterEach(function(){
    // 每個測試結束後都執行這裡
  })
  
  /* test cases */
})

Pending

就是空著 callback 函數,mocha 會 pass 這段測試,一般用在你是負責寫好框架讓別人去實作測試案例,或者測試細節還沒完全實作好(類似 mock object 的想法)

describe('Array', function(){
    describe('#indexOf()', function(){
        it('should return -1 when the value is not present', function(){
        })
    })
});

Exclusive & Inclusive

fs = require('fs');
describe('File', function(){
    describe('#readFile()', function(){
         /* skip 的 case */
        it.skip('should read test.ls without error', function(done){
            fs.readFile('test.ls', function(err){
                if (err) throw err;
                done();
            });
        })
        
        /* only 的 case */
        it('should read test.js without error', function(done){
        })
    })
})

這段程式碼只有 only 的會被執行,it.skip 會被忽略。每個函數中只能有一個 only,如果把 only 和 skip 一起用,因為 only 會遮蔽掉 skip 的效果,導致沒什麼效果

BDD & TDD

  • BDD: behavior driven development
  • TDD: test driven development

這兩者主要的差別就是在寫測試時的思考角度不同

mocha 預設是採用 Behavior Driven Develop (BDD),要想用 TDD 的測試需要加上參數,如:

mocha -u tdd test.js

BDD 就像上面的使用 describe() 和 it() 來構建測試程式碼,而 TDD 使用 suite() 和 test() 來組織測試

Final

基本 API 就是這樣,官網沒有過多文件,而 mocha 有些有趣的用法也請見官網

Reference

 
about 5 years ago

剛收到 TCP (Tablet Contribution Program)的平板
查看一下資訊
韌體版本是 Flatfish_20140112 的(末兩碼我忘了)
作業系統是 1.x.x.x 版本

要升級 OS
首先要照 https://wiki.mozilla.org/FirefoxOS/TCP/Flashing_the_Flatfish_bootloader 先刷 bootloader
我再 Mac OS X 10.10 上:

  1. 下載 LiveSuite for Mac
  2. 解壓縮後解開 LiveSuite 並執行
  3. 啟動 LiveSuite
  4. 下載 known-working build files (img files)
  5. 在 LiveSuite 中選擇 .img
  6. 關閉平板的電源,然後先將 USB 接上平板,但不要接上電腦
  7. 長按住音量鍵的 (-),然後將 USB 另一頭接上電腦,此時應該會看到一個彈跳視窗問你要 format 嗎?選擇 Yes (沒有的話重複按下電源鍵大約 10 次,我的沒有這個狀況)
  8. 接著等待 Process 跑完顯示 success,平板會自動重開機,此時你可以放開音量鍵並關閉 LiveSuite 了
  9. 平板畫面會停在 FASTBOOT,接著你就可以開始刷 OS (如果累了可以先將平板關機,大丈夫!重新開機還是會停在 FASTBOOT 字樣等你刷 OS,是不是反而覺得要是不會刷了就等於刷壞了這台...)

目前在 Mac 上還不能刷 OS,所以我還是找了一台 Windows 來用
https://wiki.mozilla.org/FirefoxOS/TCP/Flashing_your_device/

  1. 接上電腦後,有可能抓不到 Android 驅動,可以先按照這裡去下載驅動程式安裝

  2. 下載這包,解壓縮後,再到這裡下載最新的 build files (至目前為止最後更新為 20141212),解壓縮後的東西都跟前面的 ADB-Fastboot-and-script 內的東西放在同一目錄下面

  3. 雙擊執行 flash.bat 這個批次檔案,沒有錯誤的話正常來說需要等個幾分鐘,就會完成刷新 OS 的作業,並自動重新開機,進入 Firefox OS 最新版囉!


作業系統版本:2.2.0.0-prerelease
韌體版本:flatfish_20141212-0135
硬體版本:flatfish
Build Number:20141212

 
over 5 years ago

參考良葛格的簡報:http://www.codedata.com.tw/java/groovy-gradle-abc/

在 build.gradle 中可以加入這個 task:

task "create-dirs" << {
    sourceSets*.java.srcDirs*.each { it.mkdirs() }
    sourceSets*.resources.srcDirs*.each { it.mkdirs() }
}

然後就可以用 gradle create-dirs 自動化建立專案目錄

~/Desktop/gedo -->gradle create-dirs
:create-dirs

BUILD SUCCESSFUL

Total time: 4.926 secs
~/Desktop/gedo -->tree
.
├── build.gradle
└── src
    ├── main
    │   ├── java
    │   └── resources
    └── test
        ├── java
        └── resources

7 directories, 1 file

額外參考討論:https://issues.gradle.org/browse/GRADLE-1289

 
over 5 years ago
  1. mkdir first
  2. cd first
  3. vim build.gradle
  4. add apply plugin: 'java' into build.gradle, then save file and leave.
  5. gradle build, This will create directory .gradle and build:
     ~/Desktop/Gradle/first -->gradle build
    :compileJava UP-TO-DATE
    :processResources UP-TO-DATE
    :classes UP-TO-DATE
    :jar
    :assemble
    :compileTestJava UP-TO-DATE
    :processTestResources UP-TO-DATE
    :testClasses UP-TO-DATE
    :test UP-TO-DATE
    :check UP-TO-DATE
    :build
    
    
    

    BUILD SUCCESSFUL

    Total time: 3.712 secs



    上面依序是編譯和打包專案時會做的 tasks,可以用 gradle tasks 來查看有哪些 task 可以指定,這裡因為我們指定到 build,所以就依序做到 build
     ~/Desktop/Gradle/first -->ll
    total 8
    drwxr-xr-x  5 veck  staff  170 Nov  5 03:15 .
    drwxr-xr-x  6 veck  staff  204 Nov  5 03:13 ..
    drwxr-xr-x  3 veck  staff  102 Nov  5 03:15 .gradle
    drwxr-xr-x  4 veck  staff  136 Nov  5 03:15 build
    -rw-r--r--  1 veck  staff   22 Nov  5 03:14 build.gradle
     ~/Desktop/Gradle/first -->tree
    .
    ├── build
    │   ├── libs
    │   │   └── first.jar
    │   └── tmp
    │       └── jar
    │           └── MANIFEST.MF
    └── build.gradle
    

    可以見到,gradle 會自動打包成 jar 檔和建立 MANIFEST.MF 檔,不過正常應該是,要先建立好 java 專案,根據官方文件說明,gradle 預設你的 Java 專案結構長這樣:

    ├── build.gradle
    └── src
        ├── main
        │   ├── java
        │   └── resources
        ├── sourceSet
        │   ├── java
        │   └── resource
        └── test
            ├── java
            └── resources
    

    我先手動建立,Gradle 好像沒有自動化建立專案的功能(? 或是有工具?應該也可用 maven 去建立?),建立好以後,我們在 src/main/java 下建立 HelloWorld.java,簡單的加入 HelloWorld 的程式碼,並且在 build.gradle 中加入 apply plugin: 'java',最後的目錄結構會長這樣:

     ~/Desktop/Gradle/first -->tree
    .
    ├── build.gradle
    └── src
        ├── main
        │   ├── java
        │   │   └── HelloWorld.java
        │   └── resources
        ├── sourceSet
        │   ├── java
        │   └── resource
        └── test
            ├── java
            └── resources
    
    10 directories, 2 files
    

    接著在用 gradle build:

     ~/Desktop/Gradle/first -->gradle build
    :compileJava
    :processResources UP-TO-DATE
    :classes
    :jar
    :assemble
    :compileTestJava UP-TO-DATE
    :processTestResources UP-TO-DATE
    :testClasses UP-TO-DATE
    :test UP-TO-DATE
    :check UP-TO-DATE
    :build
    
    BUILD SUCCESSFUL
    
    Total time: 4.572 secs 
     ~/Desktop/Gradle/first -->tree
    .
    ├── build
    │   ├── classes
    │   │   └── main
    │   │       └── HelloWorld.class
    │   ├── dependency-cache
    │   ├── libs
    │   │   └── first.jar
    │   └── tmp
    │       ├── compileJava
    │       └── jar
    │           └── MANIFEST.MF
    ├── build.gradle
    └── src
        ├── main
        │   ├── java
        │   │   └── HelloWorld.java
        │   └── resources
        ├── sourceSet
        │   ├── java
        │   └── resource
        └── test
            ├── java
            └── resources
    
    18 directories, 5 files
    

    但是這樣子還不能夠直接下 java -jar first.jar 來執行測試我們的程式,需要在 build.gradle 中加入:

    jar {
        manifest {
            attributes 'Main-Class': 'HelloWorld'
        }
    }
    

    然後再 build 一次,最後進到 build/libs 中,就可以執行 java -jar first.jar 來執行程式了

    ~/Desktop/Gradle/first/build/libs -->java -jar first.jar
    Hello World!!!
    

    但是這樣很不方便,想要更自動化,可以向 maven 一樣在 pom.xml 中寫 ... 的話,Gradle 也可以,只要在 build.gradle 中加入以下的 task:

    task runJar(dependsOn:jar) << {
      javaexec { main="-jar"; args jar.archivePath } 
    }
    

    接著每次重新編譯完後,就可以值些輸入 gradle runjar 來執行編譯包裝後的 JAR 檔案了

    Note: 在 build.gradle 中加入 apply plugin: 'eclipse' 可以產生 eclipse 的專案,可以加在 apply plugin: 'java'

    References:

    1. http://www.gradle.org/docs/current/userguide/tutorial_java_projects.html
    2. http://www.gradle.org/docs/current/userguide/java_plugin.html#N12119
    3. http://blog.denevell.org/gradle-run-jar-file.html
 
over 5 years ago

簡單來說,加上 implicit 關鍵字的 class,在有效範圍內(scope)內,會自動建立一個 implicit primary constructor,拿官方文件的例子加以說明(為了方便對照區塊,排版做的調整):

object Helpers 
{
  implicit class IntWithTimes(x: Int) 
  {
    def times[A](f: => A): Unit = 
    {
    
      def loop(current: Int): Unit =    // 定義一個迴圈函數
        if(current > 0) 
        {
          f
          loop(current - 1)
        }
        
      loop(x)   // 呼叫迴圈函數
    }
  }
}

把宣告為 implicit 的部份抽出來:

implicit class IntWithTimes(x: Int){
    ...
}

這一段在編譯後會自動轉換成:

class IntWithTimes(x: Int){
    ...
}
implicit final def IntWithTimes(x: Int): IntWithTimes = new IntWithTimes(x)

其中最下面多的那一個 function 呼叫後會回傳一個同 Class 名稱的物件實例,也就是這個類別的 primary constructor,因為我們把這個 implicit class 宣告在 object class 內,所以我們可以直接 import 這個 class 並直接使用這個 implicit class 的方法:

scala> import Helpers._
import Helpers._
scala> 5 times println("HI")
HI
HI
HI
HI
HI

References:
[1] http://docs.scala-lang.org/overviews/core/implicit-classes.html
[2] http://stackoverflow.com/questions/11931623/how-to-use-scala-2-10-implicit-classes

 
over 5 years ago

String with Interpreter

scala> import scala.math.Pi
scala> Tau = 2 * Pi
scala> println(s"Happy $Tau Day")
     Happy 6.283185307179586 Day
scala> println("Happy $Tau Day")
     Happy $Tau Day
scala> val s1 = "$Tau"
     s1: String = $Tau
scala> val s2 = s"$Tau"
     s2: String = 6.283185307179586

所以 string 加上 s 是指要以 string interpreter 來處理這個字串

String in Quasiquote

而 string 前面加上 q 是 quasiquote,編譯器會自動將這個 string 變成一棵 tree
Quasiquote 是 Scala 2.11 以後的巨集工具,但預設不會載入模組
所以直接宣告 q"..." 會出現 value q is not a member of StringContext 的錯誤
解決方法:http://docs.scala-lang.org/overviews/quasiquotes/setup.html

在 Scala 2.11, in REPL,要先做些前置:

scala> val universe: reflect.runtime.universe.type = reflect.runtime.universe
scala> import universe._

接著就可以用了:

scala> val tree = q"i am { a quasiquote }"
tree: universe.Tree = i.am(a.quasiquote)

scala> println(tree match { case q"i am { a quasiquote }" => "it worked!" })
it worked!

在 IntelliJ 中,則要在檔案中引用:import reflect.runtime.universe._ 就可以了

 
over 5 years ago

object class 不能夠跨檔案 new,可能原因是 object 是 singleton, 這意味這整個專案就只能有一份 instance (?):
ex.
File A:object SampleClass{ ... }
File B:val sample = new SampleClass() => error: cannot resolve symbol SampleClass

但是可以直接存取使用:
ex.
File A:object SampleClass { def foo() = { ... } )
File B:val sample = SampleClass.foo() => passed!

如果使用 object class 來建立兩個物件:

val sample1 = SampleClass.foo()
val sample2 = SampleClass.foo()
print(sample1 == sample2)       => false

所以兩個實例是獨立的兩個物件

但是如果想要這樣:

val sample1 = SampleClass
val sample2 = SampleClass
print(sample1 == sample2)       => true
sample1.foo()
sample2.foo()

因為是直接指定 object 物件給新的變數,所以兩個指向的記憶體位置是相同的,因此比較結果是 true