JsTestDriver使い方メモ

JsTestDriver の使い方をメモする。
JsTestDriver とは、 Google が開発してるオープンソースの JavaScript 用テスティングフレームワークのこと。

#環境
##OS
Windows7 64bit

##Java
1.7.0_25

##JsTestDriver
1.3.5

#JsTestDriver のインストール
[ここ](http://code.google.com/p/js-test-driver/downloads/list) から `JsTestDriver-x.x.x.jar` をダウンロードして任意のフォルダに配置する。
仮に jar を配置したフォルダのパスを `%JSTESTDRIVER_HOME%` とする。

#テストを実行する
JsTestDriver でテストするときの手順はおおまかには以下の通り。

1. JsTestDriver サーバを起動する
1. テストしたいブラウザを JsTestDriver サーバに接続する
1. テストを実行する

##JsTestDriver サーバを起動する
コマンドプロンプトを開き、以下のコマンドを実行する。

```bat:サーバの起動
>java -jar %JSTESTDRIVER_HOME%\JsTestDriver-x.x.x.jar --port 4224
```

サーバが 4224 ポートで起動する（4224 ポートは JsTestDriver を使うときの慣例らしい）。

##テストしたいブラウザを JsTestDriver サーバに接続する
とりあえず Chrome で接続する。
Chrome を起動し、 `http://localhost:4224` にアクセスする。

![Chromeでアクセスした様子](https://qiita-image-store.s3.amazonaws.com/0/28302/2a9554d0-63fa-a8f0-6809-441ae5bab095.jpeg)

テストする時のページのDTD を「移行型」と「厳密型」のどちらにするかを選択する。
選択すると、以下のページに移動する。ブラウザの準備はこれで OK。

![ブラウザで接続](https://qiita-image-store.s3.amazonaws.com/0/28302/75fe0be9-c925-c321-4570-029e7e3fb939.jpeg)

##テストを実行する
###フォルダ構成
```text:フォルダ構成
D:\tmp\JsTestDriver
│  jsTestDriver.conf
├─src
│      TestTarget.js
└─test
        TestTargetTest.js
```

###各ファイルの内容
```js:src/TestTarget.js
function targetMethod(x, y) {
    return x + y;
}
```

```js:test/TestTargetTest.js
TestCase('TestTarget Test', {
    'test targetMethod': function() {
        // setup
        var expected = 51;
        
        // exercise
        var actual = targetMethod(11, 40);
        
        // verify
        assertEquals(expected, actual);
    }
});
```

```yaml:jsTestDriver.conf
server: http://localhost:4224

load:
    - src/*.js
    - test/*.js
```

###実行方法
コマンドプロンプトを開き、 `jsTestDriver.conf` のあるフォルダに移動して次のコマンドを実行する。
__※`jsTestDriver.conf` があるフォルダまでのパスに日本語が含まれるとエラーになる。__

```bat:テストの実行
>java -jar %JSTESTDRIVER_HOME%\JsTestDriver-x.x.x.jar --tests all
```

```text:テストの実行結果
setting runnermode QUIET
.
Total 1 tests (Passed: 1; Fails: 0; Errors: 0) (0.00 ms)
  Chrome 29.0.1547.76 Windows: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (0.00 ms)
```

###注意点
####テスト関数は、名前が `test` で始まっている必要がある。
```js:テスト関数の名前のルール
TestCase('TestTarget Test', {
    // 実行される
    'test function': function() {
        jstestdriver.console.log('test function');
    },
    
    // 実行されない
    'not test function': function() {
        jstestdriver.console.log('not test function');
    }
});
```

```text:実行結果
Total 1 tests (Passed: 1; Fails: 0; Errors: 0) (1.00 ms)
Chrome 29.0.1547.76 Windows: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (1.00 ms)

TestTarget Test.test function passed (1.00 ms)
[LOG] test function
```

####テスト関数名には日本語が使えるが、実行時に文字化けする
```js:日本語は基本文字化けする
TestCase('TestTarget Test', {
    'test 日本語': function() {
        jstestdriver.console.log('test 日本語');
    }
});
```

```text:実行結果
Total 1 tests (Passed: 1; Fails: 0; Errors: 0) (0.00 ms)
Chrome 29.0.1547.76 Windows: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (0.00 ms)

TestTarget Test.test ??{?? passed (0.00 ms)
[LOG] test ??{??
```

実行はできるけど、文字化けしてしまう。
テスト名は日本語で書きたいなぁ。。。

#その他使い方
##コンソール出力
`console` はブラウザによっては対応していないものがあるので、 JsTestDriver を使うときは JsTestDriver が提供してるコンソール出力用 API を使うのが良い。
具体的には、 `jstestdriver.console` というオブジェクトを使う。

`log` や `info`、 `debug` といった関数が用意されてる。

```js:コンソール出力
TestCase('TestTarget Test', {
    'test console': function() {
        var console = jstestdriver.console;
        
        console.log('log message');
        console.debug('debug message');
        console.info('info message');
        console.warn('warn message');
        console.error('error message');
    }
});
```

```text:実行結果
[LOG] log message
[DEBUG] debug message
[INFO] info message
[WARN] warn message
[ERROR] error message
```

C の printf 的な使い方もできる。

```js:フォーマット
TestCase('TestTarget Test', {
    'test console': function() {
        var string = 'string message';
        var obj = {
            string: 'Name',
            num   : 10,
            bool  : true,
            und   : undefined,
            nul   : null,
            func  : function() {
                alert('hoge');
            }
        };
        
        var console = jstestdriver.console;
        
        console.log('string = %s', string);
        console.log('obj = %o', obj);
    }
});
```

```text:実行結果
[LOG] string = string message
[LOG] obj = {"string":"Name","num":10,"bool":true,"nul":null}
```

`%s` で文字列形式、 `%o` でオブジェクトを JSON に変換した形式で出力できる（`undefined` と関数は出力されない）。

##事前処理・事後処理を実装する
JUnit の `setUp` と `tearDown` メソッドに該当するもの。
ずばりその名前で関数を定義すれば、各テストの前後で実行される。

```js:事前・事後処理
TestCase('TestTarget Test', {
    setUp: function() {
        log('setUp');
    },
    
    tearDown: function() {
        log('tearDown');
    },
    
    'test 1': function() {
        log('test 1');
    },
    
    'test 2': function() {
        log('test 2');
    }
});

function log(msg) {
    jstestdriver.console.log(msg);
}
```

```text:実行結果
TestTarget Test.test 1 passed (0.00 ms)
[LOG] setUp
[LOG] test 1
[LOG] tearDown

TestTarget Test.test 2 passed (0.00 ms)
[LOG] setUp
[LOG] test 2
[LOG] tearDown
```

各テストメソッドごとに、 `setUp()` 関数と `tearDown()` 関数が呼ばれる。

##各テスト関数で共通して使用する値を用意する
各テストで共通して使用したい値がある場合は、 `setUp()` 関数内で `this` にプロパティを追加すればいい。

```js:各テストで共通して使用する値の参照
var cons = jstestdriver.console

TestCase('TestTarget Test', {
    setUp: function() {
        this.value = 'common value';
    },
    
    'test 1': function() {
        cons.log('test 1 : this.value = %s', this.value);
    },
    
    'test 2': function() {
        cons.log('test 2 : this.value = %s', this.value);
    }
});
```

```text:実行結果
TestTarget Test.test 1 passed (0.00 ms)
[LOG] test 1 : this.value = common value

TestTarget Test.test 2 passed (0.00 ms)
[LOG] test 2 : this.value = common value
```

##１つの js ファイルに複数の TestCase を書く
JUnit4 の Enclosed みたいなイメージ。

```js:複数のTestCase
var cons = jstestdriver.console;

TestCase('TestCase 1', {
    'test 1': function() {
        cons.log('test 1');
    }
});

TestCase('TestCase 2', {
    'test 2': function() {
        cons.log('test 2');
    }
});
```

```text:実行結果
Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (1.00 ms)
Chrome 29.0.1547.76 Windows: Run 2 tests (Passed: 2; Fails: 0; Errors 0) (1.00 ms)

TestCase 1.test 1 passed (1.00 ms)
[LOG] test 1

TestCase 2.test 2 passed (0.00 ms)
[LOG] test 2
```

問題なく動く。事前処理が同じものでまとめるとコードが見やすくなるかな。

##テスト用の DOM を用意する
テストコード中に特殊なコメントを記述することで、テストでのみ使える DOM を用意することができる。

###DOM オブジェクトを作成する
```js:DOMオブジェクトの作成
TestCase('TestCase 1', {
    'test': function() {
        /*:DOC dom = <input value="test-value" />*/
        
        var value = this.dom.getAttribute('value')
        jstestdriver.console.log('dom.value = %s', value);
    }
});
```

```text:実行結果
[LOG] dom.value = test-value
```

```text:DOMオブジェクトを定義するときの書式
/*:DOC <変数名> = <HTMLタグ>*/
```

テスト実行時にコメント行を通過すると、以後 `this.<変数名>` で `<HTMLタグ>` に指定したタグの DOM オブジェクトを取得できるようになる。

####コメント行の前では DOM オブジェクトは取得できない
コメント行が処理される前だと、 DOM オブジェクトは生成されないので注意。

```js:コメント行の前ではDOMオブジェクトはまだできていない
TestCase('TestCase 1', {
    'test': function() {
        jstestdriver.console.log('dom = %s', this.dom);
        
        /*:DOC dom = <input />*/
    }
});
```

```text:実行結果
[LOG] dom = undefined
```

####各テスト関数で生成した DOM オブジェクトはその関数内でのみ有効
各テスト関数内で生成された DOM は、その関数内でのみ使用可能で、他のテスト関数からは参照できない。

```js:他のテスト関数で生成されたDOMオブジェクトは参照できない
var cons = jstestdriver.console;

TestCase('TestCase 1', {
    'test': function() {
        cons.info('test1');
        /*:DOC dom = <input />*/
        cons.log('dom = %s', this.dom);
    },
    
    'test2': function() {
        cons.info('test2');
        cons.log('dom = %s', this.dom);
    }
});
```

```text:実行結果
TestCase 1.test passed (0.00 ms)
[INFO] test1
[LOG] dom = [object HTMLInputElement]

TestCase 1.test2 passed (0.00 ms)
[INFO] test2
[LOG] dom = undefined
```

####各テスト関数から参照できる DOM オブジェクトを用意する
各テスト関数から共通で参照したい DOM オブジェクトがある場合は、 `setUp()` 関数にコメントを書けばいい。

```js:setUp関数でDOMオブジェクトを宣言する
var cons = jstestdriver.console;

TestCase('TestCase', {
    setUp: function() {
        /*:DOC dom = <input value="common-value" />*/
    },
    
    'test1': function() {
        cons.info('test 1');
        cons.log(this.dom.getAttribute('value'));
    },
    
    'test2': function() {
        cons.info('test 2');
        cons.log(this.dom.getAttribute('value'));
    }
});
```

```text:実行結果
TestCase.test1 passed (0.00 ms)
[INFO] test 1
[LOG] common-value

TestCase.test2 passed (0.00 ms)
[INFO] test 2
[LOG] common-value
```

####複数の DOM を作成可能
１つのコメントの中で、複数のタグを記述できる。

```js:複数のDOMを作成
TestCase('TestCase', {
    'test': function() {
        /*:DOC dom = <input value="test-value-1" /><input value="test-value-2" />*/
        
        var children = this.dom.childNodes;
        
        for (var i=0; i<children.length; i++) {
            jstestdriver.console.log('childNodes[%d].value = %s', i, children[i].getAttribute('value'));
        }
    }
});
```

```text:実行結果
[LOG] childNodes[0].value = test-value-1
[LOG] childNodes[1].value = test-value-2
```


###ブラウザにDOMを追加する
```js:ブラウザにDOMを追加する
TestCase('TestCase', {
    'test': function() {
        /*:DOC += <input id="test-dom" value="test-value" />*/
        
        var value = document.getElementById('test-dom').getAttribute('value')
        jstestdriver.console.log('value = %s', value);
    }
});
```

```text:実行結果
[LOG] value = test-value
```

```text:ブラウザにDOMを追加する場合のコメントの書式
/*:DOC += <HTMLタグ>*/
```

他のテスト関数から参照できないなどの動作は DOM オブジェクトを作成する場合と同じ。




#参考
- [js-test-driver - Remote javascript console - Google Project Hosting](http://code.google.com/p/js-test-driver/)
- [JsTestDriverで簡単テスト : アシアルブログ](http://blog.asial.co.jp/1051)
- [Amazon.co.jp： テスト駆動JavaScript: Christian Johansen, 長尾高弘: 本](http://www.amazon.co.jp/%E3%83%86%E3%82%B9%E3%83%88%E9%A7%86%E5%8B%95JavaScript-Christian-Johansen/dp/4048707868)
- [0-9, JsTestDriverのAsyncTestCase](http://0-9.tumblr.com/post/17020645713/jstestdriver-asynctestcase)