Help us understand the problem. What is going on with this article?

イマドキのJSテスト - karma + karma-html2js-preprocessorでブラウザ/DOMを絡めたUIテストを実行する編 〜 JSおくのほそ道 #031

More than 3 years have passed since last update.

こんにちは、ほそ道です。

前回のJasmineモジュールテストに引き続きテストを掘り下げていきます。
今回はUIテストをやってみたいと思います。前回分も読んでおいていただくと理解度シナジーが起こって良い感じになると思います。

目次はこちら

今回扱う範囲/扱わない範囲

  • まずはブラウザ上でJavaScriptを実行しJasmineと絡めて実行結果をテストします。
  • 次にブラウザ上のDOMの状態(スタイル含む)をテストします。
  • 今回のテスト内容にとりあえず遷移というかページ(リ)ロードは含めません。これが絡むと非常に厄介さが増すので遷移は別途取り上げたいと思います。

Karmaを実行する

まずはKarmaの実行から。
Karmaは実ブラウザでJavaScriptコードを実行できる実行環境で、テストフレームワークと組み合わせてテストを行う事ができます。
エミュレータやヘッドレスブラウザではなく実ブラウザというところがミソです。ただPhantomJSの起動にも対応しています。
ブラウザはPCにインストールされている必要があるのでMacで気軽にIE立ち上げたりはできません。

インストール

それではプロジェクトディレクトリを作っていつものインストールから始めましょう。

コンソール
npm init
npm i -D karma karma-jasmine karma-chrome-launcher karma-firefox-launcher

これでkarma --versionコマンドが通ればインストールは問題ありません。
うまく実行できない場合は別途下記のコマンドを叩いてCLIをグローバルインストールしましょう。

コンソール
npm i -Dg karma-cli  

conf.js

今回も被テストファイルはsrcディレクトリに、テストファイルはspecディレクトリに設置します。
上記ディレクトリが作成できたらKarmaの設定ファイルを出力しましょう。
下記のコマンドをたたきます。

コンソール
karma init karma.conf.js  

下記のように質問が出てくるので答えましょう。終わるとkarma.conf.jsが出力されます。

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

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.
> Chrome
> Firefox
> 

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.
> src/*.js
> spec/*.js
> 

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

設定ファイルは手で作っても構いません。下記のようになります。

karma.conf.js
module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine'],
    files: ['src/*.js', 'spec/*.js'],
    exclude: [],
    preprocessors: {},
    reporters: ['progress'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome', 'Firefox'],
    singleRun: false
  });
};

テストの実行

それでは簡単なJSファイルを設置してテストしてみましょう。
ここまでの構成は下記のようになります。

プロジェクト構成
.
├── karma.conf.js
├── node_modules
├── package.json
├── spec
│   └── sampleSpec.js
└── src
    └── sample.js
  • まずは被テストファイルから
src/sample.js
function add(x, y) {
  return x + y;
}
  • 次にテストファイルです
spec/sampleSpec.js
describe("function-add", function() {
  it("2つの引数の和を返すべし", function() {
    expect(add(10, 20)).toBe(30);
  });
});
  • package.jsonにテストコマンドを記述しましょう。
package.json
{
  
  "scripts": {
    "test": "karma start karma.conf.js"
  },
  
}
  • 実行しましょう
コンソール
npm test
  • 実行結果
    スクリーンショット 2015-05-30 19.09.52.png

ポコポコっとChromeとFirefoxのブラウザが起動してきて、TOTAL: 2 SUCCESSの表示が出れば実行・テスト成功です。
テストコードはブラウザ上で実行されたことになります。

  • テストファイルがrequireしてないことにも注目です。コンフィグファイルのfiles: ['src/*.js', 'spec/*.js']の設定は自動的に相互参照を可能とします。
  • コンフィグファイルのautoWatch: truesingleRun: falseの設定が効いていてファイルの内容を書き換えてセーブすると自動的にもう一回テストが流れるようになっています。

これじゃあただのjasmine-nodeモジュールテストとと変わらない?

と、最初は思っちゃいました。
ただ、この時点でもほそ道的には下記2点メリットはあると思うんですが、どうですかねー?

  • ブラウザごとにで使用不能なメソッドの呼び出しを行っていないかテストできる
  • isomorphicなJSモジュールになっているかテストできる

HTMLを絡めたテスト

環境準備

さて、HTMLを絡めましょう。わしゃあブラウザでテスト走らせるならHTMLテストがしたいんじゃー!!
先ほどのプロジェクトにkarma-html2js-preprocessorを追加インストールしましょう。

コンソール
npm i -D karma-html2js-preprocessor

つづいてkarma.conf.jsに設定を追加します。
htmlファイルも実行対象に入れる。
htmlファイルにはkarma-html2js-preprocessorをカマす。
という設定です。

karma.conf.js
module.exports = function (config) {
  config.set({
    
    files: [
      '*.html',
      'src/*.js',
      'spec/*.js'
    ],
    
    preprocessors: {
      '*.html': 'html2js'
    },
    
  });
};

WEB画面の構成

次にindex.html, src/appendLi.js, spec/appendLiSpec.jsの3ファイルを追加します。
さっき使ったsrc/sample.jsspec/sampleSpec.jsは消しちゃって構いません。
プロジェクト構成は下記のようになります。

プロジェクト構成
.
├──index.html
├── karma.conf.js
├── node_modules
├── package.json
├── spec
│   └── appendLiSpec.js
└── src
    └── appendLi.js

それでは各ファイルの中身を見ていきましょう。
まずはsrc/appendLi.jsファイルからです。
#listなul要素にli要素を追加します。

src/appendLi.js
function appendLi() {
  var ul = document.getElementById('list'),
      li = document.createElement('li');
  li.innerHTML = 'karma!';
  li.style.fontSize = '20px';
  ul.appendChild(li);
}

次にindex.htmlです。body部だけ。
src/appendLi.jsを参照していて、クリックされるとappendLi関数をコールします。

<body>
<input id="btn" type="button" value="click!" onclick="appendLi()">
<ul id="list"></ul>
<script src="src/appendLi.js"></script>
</body>

この時点でも下記のようにブラウザで動作が確認できます。

スクリーンショット 2015-05-30 22.09.22.png

テストコード

さて、テストコードとなるspec/appendLi.jsです。
ボタンをクリックして期待通りの動作をするか。
ポイントはbeforeEach内でHTMLの初期化をしているところです。
karma-html2js-preprocessorを使うと
window.__html__['index.html'];のような記述でhtmlファイルをJavaScriptで扱える文字列として呼び出せます。

spec/appendLi.js
describe("function-appendLi", function() {

  beforeEach(function() {
    document.body.innerHTML = window.__html__['index.html'];
  });

  it("追加されたli要素のテキストは'karma!'となるべし", function() {
    var btn, appendedLi;
    btn = document.getElementById('btn');
    btn.click();
    appendedLi = document.getElementById('list').firstElementChild;
    expect(appendedLi.innerText).toEqual('karma!');
  });

  it("追加されたli要素のテキスサイズは20pxとなるべし", function() {
    var btn, appendedLi;
    btn = document.getElementById('btn');
    btn.click();
    appendedLi = document.getElementById('list').firstElementChild;
    expect(Number(appendedLi.style.fontSize.replace('px', ''))).toEqual(20);
  });

  it("li要素は複数追加できるべし", function() {
    var btn, liCnt;
    btn = document.getElementById('btn');
    btn.click();
    btn.click();
    btn.click();
    liCnt = document.getElementById('list').childElementCount;
    expect(liCnt).toEqual(3);
  });
});

さあ、npm testでテストを実行してみましょう。

  • 実行結果
    スクリーンショット 2015-05-30 22.18.59.png

おや、なんかFirefoxの方だけエラーが起きてますね。。

あっ、検査文字列を取得するメソッドがinnerTextになっている!
ということでこれをinnerHTMLに変えてみましょう。これでこそマルチブラウザテスト!

  • ファイル監視ですでにテストが終わってました
    スクリーンショット 2015-05-30 22.22.27.png

今度は成功です!
DOMやスタイルに対するHTMLテストが出来ました!

karmaレポーター

さて、最後にもう少し便利にすることにトライしてみます。

テストしたHTMLファイルを表示する/テスト結果をもう少しリッチにする

ではテスト結果に彩りを添えるために下記のパッケージをインストールします。

コンソール
npm i -D karma-spec-reporter karma-jasmine-html-reporter

さらにkarma.conf.jsに設定を変更します。結果表示用のreportersをデフォルトのprogressから変更します。

karma.conf.js
module.exports = function (config) {
  config.set({
    
    reporters: ['spec', 'html'],
    
  });
};

さて、やる事はこれだけです。npm testを実行してみましょう。


  • karma-spec-reporterによって実行結果がリッチに
    スクリーンショット 2015-05-30 22.37.56.png

  • karma-jasmine-html-reporterによってHTMLにテスト後の状態が表示される
    スクリーンショット 2015-05-30 22.38.43.png

このHTMLはキャプチャではなくて実際にボタンをクリックする事ができます。
注意点:karma-jasmine-html-reporterをカマすとファイル監視とブラウザ実行の関連付けが切れます。何度もモジュールを書き換えながらテスト結果をチェックしたい場合はオフっておいた方が良さそうです

まとめ

今回インストールしたパッケージ群まとめ

パッケージ名 概要
karma 実ブラウザでJavaScriptコードを実行できる実行環境
karma-cli -gでインストールすればどこからでもkarmaコマンドを実行できる
karma-jasmine KarmaをJasmineに適合させる事ができる
karma-chrome-launcher Chromeブラウザをを起動できる
karma-firefox-launcher Firefoxブラウザをを起動できる
karma-html2js-preprocessor HTMLファイルをJavaScriptで扱える文字列にコンバートできる
karma-spec-reporter コンソール上のテスト結果表示フォーマットを変更する
karma-jasmine-html-reporter launcherが起動したブラウザにWEB画面を表示する

launcher, preprocessor, reporterはここで探す

Karmaを使って他にもいろんな事が出来るので探してみると新しい可能性が見つかるかもしれません。

今回は以上です。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away