JScriptなWSHをQUnitでユニットテストする

# この記事は何か

Windowsのスクリプティング環境としては未だ根強い人気のWSH（Windows Scripting Hosts）をJScriptで書くときにもユニットテストしたいので、QUnitを使って何とかする話です。

# TL;DR;

- 責務分割、モジュール化を行う
    - 処理本体は適宜必要なjsファイルに分けて定義
    - WSH起動ファイルをwsfファイルにしてjsファイルをインポートして利用する
- QUnitでは各種jsファイルを読み込んでテストする
    - ActiveXObjectを利用するため、ブラウザーにはIEを利用する
        - ［インターネットオプション］－［セキュリティ］－［ローカル イントラネット］のレベルをカスタマイズする
            - ［ActiveX コントロールとプラグイン］－［スクリプトを実行しても安全だとマークされていない ActiveX コントロールの初期化とスクリプトの実行］を［ダイアログを表示する］に変更
- WScript.Echoは使えないので、デバッグ出力用関数オブジェクトを外部から注入する
    - QUnitからはconsole.logを実行
    - wsfからはWScript.Echoを実行

# 問題点

普通にべた書きしたJScriptなWSHをユニットテストしようとしても次のような問題点があります。

1. そもそもテスティングフレームワークがあるのか
2. ブラウザからはActiveXObjectを使ってPCリソースにアクセスできない
3. ファイルの先頭から処理が書いてあるからテストできない

# 解決法

これらの問題点を、本記事では次のように解決します。

## 1. そもそもテスティングフレームワークがあるのか

WSH用のテスティングフレームワークとしては、実は[ScriptUnit](http://xt1.org/scriptunit/)というものがあります。しかし、JUnit風の専用のテストランナーで実行する形であり、デバッグ実行などもできないため、取り回しがあまり楽ではありません。さらに、何か問題があっても絶対的に情報量が少ないです。

そもそもJScriptはJavaScriptの一派です。JavaScriptならすでに多くのテスティングフレームワークがあります。これを利用しない手はありません。

そこで、本記事ではブラウザから利用するJavaScript用テスティングフレームワークの古株である[QUnit](https://qunitjs.com/)を使います。もちろん、本記事の「キモ」はQUnitを使うことではないので、たぶん[Jasmine](https://jasmine.github.io/)などでも同様に対応できるはずです。

## 2. ブラウザからはActiveXObjectを使ってPCリソースにアクセスできない

しかし、ブラウザからJScriptを実行しようとすると一つ問題があります。それはActiveXObjectを用いたファイル操作など、PCリソースへのアクセスに制限がかかることです。

この問題を解決するため、QUnitのテストページはみんな大好き**IE（Internet Explorer）**で表示しましょう（Microsoft EdgeはActiveX使えないのでダメ）。

なお、VS付属のIISExpressなど開発サーバーを使って表示する際は、ActiveXObjectが無効になってしまうので、以下の手順でローカルイントラネットゾーンについてIEのセキュリティレベルを少し落とす必要があります。

### 設定方法

1. IEを起動して［インターネット オプション］を表示する

2. ［セキュリティ］タブのゾーンから［ローカル イントラネット］を選び、［レベルのカスタマイズ(C)...］をクリックする
![ローカル イントラネット ゾーンのレベルのカスタマイズ](https://qiita-image-store.s3.amazonaws.com/0/12039/cf476904-9ff1-dcfc-a575-661e7bdae900.jpeg)


3. ［ActiveX コントロールとプラグイン］－［スクリプトを実行しても安全だとマークされていない ActiveX コントロールの初期化とスクリプトの実行］を［ダイアログを表示する］に変更する（予期せぬスクリプト実行を防ぐため、［有効にする］にはしないこと）
![ActiveXObjectを使ったスクリプトを実行可能にする]
(https://qiita-image-store.s3.amazonaws.com/0/12039/5a97715e-ce5a-8452-8f23-4ad6a15a38a9.jpeg)


これで、JScript中にある次のようなActiveXObjectを使ったコードをIEを使ってQUnitから実行する際、確認ダイアログが表示され、［はい］をクリックすれば実行できるようになります。

```javascript:ActiveXObjectを使ったJScript
QUnit.test("Scripting.FileSystemObject", function (assert) {
  var fso = new ActiveXObject("Scripting.FileSystemObject");
  assert.ok(fso.FileExists("C:\\pagefile.sys"));
});
```

![ActiveXObjectを使ったスクリプトの実行確認ダイアログ](https://qiita-image-store.s3.amazonaws.com/0/12039/56d8f20f-0d52-b8ab-7bf3-d714aa65c859.jpeg)

## 3. ファイルの先頭から処理が書いてあるからテストできない

WSHはスクリプティング環境であり、ついjsファイルを作ってファイルの先頭からべたべた処理を書いてしまいがちです。しかし、こうやってしまうとどうあがいてもテスト不能になってしまいます。我々はプログラマですので、キチンと責務分割とモジュール化をやっていきましょう。

そのために必要なのは以下の手順です。

- 処理本体は個別のjsファイルにまとめる
- WSHの起動はwsfファイルで行う

### 処理本体は個別のjsファイルにまとめる

当たり前のことですが、テスト対象のコードは個別のjsファイルに切り出しましょう。こうすることで、QUnitでは対象のコードのみが含まれたjsファイルだけをインポートすればよくなります。

そして、出来ればjsのクラスを使うようにしましょう。そうすることで、`function`単位で処理を記述するよりも圧倒的にユニットテストしやすいコードになります。もちろんJScriptにはES2015のようなクラス構文はないので、ES5の方法でやりましょう（参考：[【javascript】やさしいクラスの作り方 - Qiita](http://qiita.com/varmil/items/99fd6f2a886fdde4b7fa)）。

```js:ConfigLoader.js
var ConfigLoader = (function() {
  var ConfigLoader = function(filepath) {
    this.filepath = filepath;
  };

  ConfigLoader.prototype.load = function() {
    var fso = new ActiveXObject("Scripting.FileSystemObject");
    var stream = fso.OpenTextFile(this.filepath, ...);

    // ... テキストファイルを呼んでitemsオブジェクトに設定 ...

    var config = {};
    config.hoge = items["hoge"];
    return config;
  };

  return ConfigLoader;
})();
```

```html:test.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>QUnit Example</title>
  <link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.1.1.css">
</head>
<body>
  <div id="qunit"></div>
  <div id="qunit-fixture"></div>
  <script src="https://code.jquery.com/qunit/qunit-2.1.1.js"></script>
  <script src="../js/ConfigLoader.js"></script>
  <script type="text/javascript">
    QUnit.test("load config", function (assert) {
      var configLoader = new ConfigLoader("テスト用のコンフィグファイルへのパス");
      var config = configLoader.load();

      assert.equal("bar", config.hoge);
    });
  </script>
</body>
```

### WSHの起動はwsfファイルで行う

折角個別のjsファイルに処理を分割したのですから、今度はそれを利用するWSHを書かなくてはなりません。そのために利用できるのが、wsfファイルです。

[Windows スクリプト ファイル (.wsf) を使用する](https://msdn.microsoft.com/ja-jp/library/cc392507.aspx)

wsfファイルではHTMLと同じようにXML形式でWSHスクリプトを書くためのファイルで、これを使うと外部のスクリプトファイルを読み込むことができます。

```html:execute.wsf
<?xml version="1.0" encoding="utf-8" ?>
<job>
  <script language="JScript" src="./js/ConfigLoader.js"></script>
  <script language="JScript">
    var configLoader = new ConfigLoader("プロダクション用のコンフィグファイルへのパス");
    var config = configLoader.load();

    // configを利用するスクリプトを続ける 
  </script>
</job>
```

# フォルダー構成

最終的には次のようなフォルダー構成になります。

```
root/
  |
  +-js/
  | |
  | +-ConfigLoader.js
  | |
  | :
  |
  +-test/
  | |
  | +-test.html
  |
  +-execute.wsf
```

そして、配布時は`test`フォルダーを取り除いて配布すれば一丁上がりです。

# おまけ

## WScript.Echoの扱い

WSHで簡単にメッセージを表示するための`WScript.Echo`メソッドは当然QUnitからは実行できません。WSHの中で利用したい場合、メッセージ出力用functionを外部から注入するようにすると良いです。

```js:メッセージ出力が必要な処理
var SomeClass = (function() {
  var SomeClass = function(log) {
      this.log = log;
  };

  SomeClass.prototype.hello = function() {
    this.log("Hello!");
  };

  return SomeClass;
})();
```

```js:QUnit
var someClass = new SomeClass(function (message) {
  alert(message); // ブラウザにメッセージボックスを表示する場合
  // console.log(message); // ブラウザのコンソールログに出すならこちら
});

someClass.hello();
```

```js:wsfファイル
var someClass = new SomeClass(function (message) {
  WScript.Echo(message);
});

someClass.hello();
```

## WSHの補完

WSHスクリプトをJScriptで書く際、やはりコード補完機能が欲しくなるものです。Visual Studioを使うと[WSH-vsdoc.js](https://github.com/jsakamoto/WSH-vsdoc)を利用することでインテリセンスが有効になり、お勧めです。

[\[動画あり\] Visual Studio の強力な JavaScript インテリセンス機能の活用 - WSHスクリプトエディタに最適! : @jsakamoto](http://devadjust.exblog.jp/15766499/)