WebDesign Dackel

karma+mocha+power-assertでDOM操作を含むユニットテストをES6で書く

Hatena0
Google+0
Pocket0
Feedly0

テスト周りは以前にmocha+chaiQUnitを少し触った程度であまり詳しくなく、少しずつ慣れていかなきゃなぁと感じていました。

そこで今回は簡単なモジュールを、コード本体とテストコードの両方をES6で書いてみました。

タイトルにもありますが、主要なライブラリは以下を使っていきます。

  • karma 0.13.14 : テストランナー
  • mocha 2.3.3 : テスティングフレームワーク
  • power-assert 1.1.0 : アサーション

ちなみに、この記事はscrollable-elements.jsという、ES6用のモジュールを作った時の内容となります。
指定したセレクタの中からスクロール可能な要素を取得するという、なんともニッチな内容のモジュールです。笑

興味のある方は以下リポジトリよりご確認いただけたらと思います。

tsuyoshiwada/scrollable-elements

方針など

モジュール、テストを書くにあたって以下の方針に沿っていきたいと思います。

  • CLIからテストの実施可能
  • DOM(UI)のユニットテストができる
  • モジュールをES6で実装
  • テストコードもES6
  • babelで変換するけど、srcからdistへ、みたいな一時ファイルを生成したくない (直接実行したい)

今回テーマとするモジュール

以下の様にして、指定した要素の属性値を設定する簡単なモジュールをテストする前提で進めていきます。

import setAttributes from "./set-attributes"

const el = document.getElementById("sample");
setAttributes(el, {"title": "タイトル"}); //title属性に"タイトル"を設定する

テストを書くための環境を整える

何はともあれ、npm initから始めていきます。
作業ディレクトリに移動して、sample-testディレクトリを作成して初期化します。

$ mkdir sample-test && cd $_
$ npm init

とりあえず全部Enterしておきました。

それぞれのファイルを用意

とりあえず空の状態でモジュール、テスト用のファイルを作成しておきます。

$ mkdir test
$ touch set-attributes.js test/set-attributes.spec.js

ここまでで以下のような構成となっています。

.
├── package.json
├── set-attributes.js #モジュール用のファイル
└── test
    └── set-attributes.spec.js #テスト用のファイル

karmaのインストールと設定

テストを走らせるために、まずはkarmaをインストールしていきます。

$ npm install --save-dev karma

インストールが終わったら、設定ファイルを作ります。
karma initを実行すると、対話形式で設定ファイルを作ることができます。 わぁ便利っ!

$ ./node_modules/.bin/karma init

それぞれの質問に対して、上下+Enterなどのキーボード操作で回答していきます。

Which testing framework do you want to use ?
Press tab to list possible options. Enter to move to the next question.
> mocha

Do you want to use Require.js ?
This will add Require.js plugin.
Press tab to list possible options. Enter to move to the next question.
> no

Do you want to capture any browsers automatically ?
Press tab to list possible options. Enter empty string to move to the next question.
> PhantomJS
> 

What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
> 

Should any of the files included by the previous patterns be excluded ?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.
> 

Do you want Karma to watch all the files and run the tests on change ?
Press tab to list possible options.
> yes


Config file generated at "/path/to/set-attributes/karma.conf.js".

これで、karma.conf.jsの雛形が生成されました。

テストを実施するブラウザですが、TravisCI上で簡単に動かしたかったのでPhantomJSとしていますが、ChromeFirefoxなどお好みのブラウザで大丈夫なはずです。 (TravisCIChromeなどのブラウザも動かせるようですが、いちいち設定するよりPhantomJSの方が手軽なので 笑)


ここまでで設定ファイルの生成と同時に、mocha+PhantomJSのインストールまで行ってくれています!

最低限の準備が出来たので、package.jsonscriptsを以下のように書き換えて、npm testを実行した時にテストが走るようにしておきます。

{
  // ...省略
  "scripts": {
    "test": "karma start"
  },
  // ...省略
}

power-assert周りのライブラリをインストール

アサーションにpower-assertimport/exportを使うことになるのでbrowserifyも含め、いくつかのライブラリをインストールします。

$ npm install --save-dev babel-plugin-espower babelify@6.4.0 karma-browserify power-assert

インストールしたのは以下。

※babelifyのバージョンに注意

この記事を書いた時点(2015.11.2)で気をつけないといけなかった点がbabelifyのバージョンです。
6.x系にしないとテストを走らせた時に、以下の様なTypeErrorが出てしまいます。

TypeError: undefined is not a function while parsing file: /path/to/set-attributes/test/set-attributes.spec.js

詳しくは調べられていないのですが、babel本体の6.xでのプラグインAPI変更の影響のようでした。
ただ、プラグイン側の対応が追いつけば気にしなくても平気そうです。

karma.conf.jsを編集

インストールしたbrowserifypower-assertなど設定していきます。
コメントアウトや、空行を削除していますがベースは最初に作った雛形のままです。

module.exports = function(config) {
  config.set({
    basePath: "",
    frameworks: ["mocha", "browserify"], //"browserify"を追加
    files: [
      "test/**/*.spec.js" //テストコード用のファイル
    ],
    exclude: [],
    preprocessors: {
      "test/**/*.spec.js": "browserify" //frameworkと同じく
    },
    // browserifyの設定
    browserify: {
      debug: true,
      transform: [
        ["babelify", {plugins: ["babel-plugin-espower"]}]
      ]
    },
    reporters: ["progress"],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ["PhantomJS"],
    singleRun: false,
    concurrency: Infinity
  })
}

ここまででとりあえず、テスト自体が走るかどうかを確認しておきたいので、./test/scrollable-elements.jsに以下の様なテストを書きます。

import assert from "power-assert"

describe("テストの実行", () => {
  it("1+1は2であるべき!", () => {
    assert(1 + 1 === 3);
  });
});

明らかに失敗するテストですね。
そしたら、これを実行してみます。

$ npm test
テストの実行 1+1は2であるべき! FAILED
    AssertionError:   # test/set-attributes.spec.js:5

      assert(1 + 1 === 3)
               |   |     
               2   false 

      [number] 3
      => 3
      [number] 1 + 1
      => 2

power-assertの親切な画面に出会えました…!

そもそもちゃんとテストを書く習慣がなかったので、power-assertは今回初めて使ったのですが、噂通りすっっっごく分かりやすかったです。
細かいアサーションメソッドを覚えなくても、これ以上無いくらい分かりやすくメッセージを返してくれるところに惚れ惚れでした。

DOMテスト用にライブラリをインストール

基本的なテストの準備整いましたが、このままだとDOMに関するテストには不十分そうです。

テストコードとは別にfixture(HTML)を用意して書いていきたいので、必要なライブラリと設定を行っていきます。

$ npm install --save-dev karma-fixture karma-html2js-preprocessor

インストールしたのは以下。

インストールが終わったら、fixture用のディレクトリとファイルを作っておきます。

$ mkdir test/fixtures
$ touch test/fixtures/set-attributes.html

fixturesの中にある.htmlmochaへ渡すために、karma.conf.jsを編集します。

module.exports = function(config) {
  config.set({
    basePath: "",
    frameworks: ["mocha", "browserify", "fixture"], //"fixture"を追加
    files: [
      "test/fixtures/**/*.html", //.htmlへのパスを追加
      "test/**/*.spec.js"
    ],
    exclude: [],
    preprocessors: {
      "test/**/*.html": "html2js",
      "test/**/*.spec.js": "browserify"
    },
    // ...省略
  })
}

こうすることで、mochaのテスト内で次のように、window.__html__["HTMLへのパス"]で、HTMLの内容を文字列で取得できます。

console.log( window.__html__["test/fixtures/set-attributes.html"] ); //...set-attributes.htmlの中身

テストを書く!

テストを書く前にset-attributes.jsに空の関数だけ定義しておきます。

export default function setAttributes() {}

test/fixtures/set-attributes.htmlにはHTMLを用意しておきます。

<div id="sample"></div>

そしたら、test/set-attributes.spec.jsにテストを書いていきます。

import assert from "power-assert"
import setAttributes from "../set-attributes"

describe("setAttributes()", () => {

  // fixturesのHTMLを描画
  before(() => {
    document.body.innerHTML = window.__html__["test/fixtures/set-attributes.html"];
  });

  // HTMLをクリアしておく
  after(() => {
    document.body.innerHTML = "";
  });

  it("指定した属性値が適用される", () => {
    const el = document.getElementById("sample");

    setAttributes(el, {
      "class": "test",
      "title": "サンプル"
    });

    assert(el.getAttribute("class") === "test");
    assert(el.getAttribute("title") === "サンプル");
  });
});

ここで、テストが通らないことを確認してみます。

$ npm test

AssertionErrorがエラーが出ればOKですね。
テストの監視が続いているので、次はモジュールの実装をしてみます。

モジュールの実装

set-attributes.jsに空の状態で定義していたsetAttributes関数を実装します。

export default function setAttributes(el, params = {}) {
  Object.keys(params).forEach((key) => {
    const value = params[key];
    el.setAttribute(key, value);
  });
}

ファイルを保存すると、自動でテストが再実行されます。
以下のようにSUCCESSが表示されたら無事テストが通っています。 やった!

Executed 1 of 1 SUCCESS (0.009 secs / 0.001 secs)

おまけ

ちょいちょい書き足すやりかたで書いてきましたので、設定ファイルや構成など一通り書いておきます。

最終的なファイル構成

.
├── karma.conf.js
├── node_modules
├── package.json
├── set-attributes.js
└── test
    ├── fixtures
    └── set-attributes.spec.js

karma.conf.js

module.exports = function(config) {
  config.set({
    basePath: "",
    frameworks: ["mocha", "browserify", "fixture"],
    files: [
      "test/fixtures/**/*.html",
      "test/**/*.spec.js"
    ],
    exclude: [],
    preprocessors: {
      "test/**/*.html": "html2js",
      "test/**/*.spec.js": "browserify"
    },
    browserify: {
      debug: true,
      transform: [
        ["babelify", {plugins: ["babel-plugin-espower"]}]
      ]
    },
    reporters: ["progress"],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ["PhantomJS"],
    singleRun: false,
    concurrency: Infinity
  })
}

package.json

{
  "name": "sample-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "karma start"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-plugin-espower": "^1.0.0",
    "babelify": "^6.4.0",
    "karma": "^0.13.14",
    "karma-browserify": "^4.4.0",
    "karma-fixture": "^0.2.5",
    "karma-html2js-preprocessor": "^0.1.0",
    "karma-mocha": "^0.2.0",
    "karma-phantomjs-launcher": "^0.2.1",
    "mocha": "^2.3.3",
    "phantomjs": "^1.9.18",
    "power-assert": "^1.1.0"
  }
}

参考サイト

さいごに

ここまでやってきて、一回設定すればあまり難しくなく始められそうだし、今更ではありますが、これからは少しずつテストを書くことを習慣化していきたいと思いました。


全然関係ありませんが、来週開催される東京Node学園祭2015に参加します。Node学園自体が初めてなのですごく楽しみっ!