JavaScript
テスト
karma

step by stepで始めるKarma

More than 1 year has passed since last update.

Karmaを使うことでフロントエンドの単体テストを楽しくかつ機能的に行うことができます。
ステップを通して少しづつ理解できるように執筆しました。
また、最後に重要だろうと思われる補足内容を記載しました。
Karmaの導入に一役買えれば幸いです。

テスト全体の流れについては以前書いた記事フロントエンドにテストを導入を参照してください。

Karmaとは

ブラウザ上で 単体テストを実行するためのテストランナーです。
テストを実行するだけでなくファイルの変更監視や結果のレポートを出力してくれたりと単体テストに必要な機能が一通りそろっています。
特定のフレームワームに依存しておらず汎用的に使えるツールで、プラグインを使った機能の拡張が強力です。

前提

Nodejs,npm,chromeが導入済みであること

流れ

Karmaは4つの大きな機能(プラグイン)が存在します。
Step 3 〜 Step 6でそれぞれ解説していきます。

  • Framework
  • Launcher
  • Preprocessor
  • Reporter

※Stepのリンク先はソースです。

Step 表題 目的
Step 1 準備 プロジェクトの作成/karmaのインストール
Step 2 ログレベルを変更 Karmaの設定方法の学習
Step 3 Framework, ファイルの登録 テストライブラリやテスト対象ファイルやテストファイルの登録
Step 4 Launcherを登録 テストを実行するブラウザを登録
Step 5 Preprocessorを登録 ファイル読み込みの前に処理を割りこませる
Step 6 Reporterを登録 テスト結果に対して処理を行う

ゴール

Karmaの全体の流れと設定ファイルの書き方を理解する

ステップバイステップで始めるKarma ゴール.png

Step 1 準備

package.jsonの作成

以下のコマンドから作成します。返答はエンターでOKです。

package.jsonの作成
npm init

karmaとkarma-cliのインストール

karma-cliをインストールすることでkarmaとうつだけでプロジェクト内のkarmaを実行してくれます。

karmaのインストール
npm install --save-dev karma
npm install -g karma-cli

Karmaが動作しているか確認

この時点でkarma自体は実行できます。起動してみましょう。

karmaの起動
karma start

09 05 2016 12:23:14.712:WARN [karma]: No captured browser, open http://localhost:9876/
09 05 2016 12:23:14.721:INFO [karma]: Karma v0.13.22 server started at http://localhost:9876/

補足されたブラウザがないよ。と怒られてますね。
ですがブラウザで記載されたURLにアクセスすることは可能です。
以下の様な画面が表示されます。

Screen Shot 2016-05-12 at 9.43.36 PM.png

karma startはWebサーバを立てることと言ってもいいかもしれません。
そのWebサーバーにプラグインで機能を追加していくイメージです。

またStep6で出てきますがKarmaがコンソールに結果を出力する際にreportersにデフォルトのprogressが使われています。

ステップバイステップで始めるKarma Step1.png

Step 2 ログレベルを変更

KarmaのデフォルトのログレベルはINFOです。
学習時はDEBUGのほうが何かと都合がいいのでDEBUGに変更しましょう。
方法は2通りあります。

その1 実行時のオプションで指定

--log-levelで指定可能です。

debugレベルを指定
karma start --log-level debug

09 05 2016 12:31:49.435:DEBUG [plugin]: Loading karma-* from /Users/howdy/VscodeProjects/study-karma/node_modules
09 05 2016 12:31:49.464:DEBUG [karma]: List of files has changed, trying to execute
09 05 2016 12:31:49.465:WARN [karma]: No captured browser, open http://localhost:9876/
09 05 2016 12:31:49.470:INFO [karma]: Karma v0.13.22 server started at http://localhost:9876/

DEBUG行が追加されていますね。

その2 設定ファイル(karma.conf.js)で指定

Karmaの設定はkarma.conf.jsがデフォルトで読みこまれます。

karma initを実行して上記ファイルを簡単に作れますが、学習のため手動で作っていきましょう。

karmaの設定ファイルを作成
touch karma.conf.js
karma.conf.js
module.exports = function(config) {
    config.set({
        logLevel: config.LOG_DEBUG
    })
}
karma start

09 05 2016 12:38:32.036:DEBUG [plugin]: Loading karma-* from /Users/howdy/VscodeProjects/study-karma/node_modules
09 05 2016 12:38:32.068:DEBUG [karma]: List of files has changed, trying to execute
09 05 2016 12:38:32.069:WARN [karma]: No captured browser, open http://localhost:9876/
09 05 2016 12:38:32.074:INFO [karma]: Karma v0.13.22 server started at http://localhost:9876/

どっちを使うべき?

特に理由がなければ設定ファイルを使いましょう。
設定ファイルで指定が可能でコマンドで指定できない項目がありますし、そもそも打つのが大変です。

コマンドオプションで使うケースとしては一時的に設定を変更したい時とかでしょうか。(コマンドオプションの方が設定ファイルより優先度が高いです)
以降は全て設定ファイルで記載していきます。

Tips 設定ファイルについて

設定ファイルはKarmaによってrequireされます。
※require('./karma.conf.js')が内部的に呼ばれている形です。
そのためmodule.exportsという形になっているわけですね。

また引数のconfigにはデフォルトの設定が入っています。
config.set(...)でデフォルト設定を上書きしていくイメージです。

ステップバイステップで始めるKarma Step2.png

Step 3 Framework, ファイルの登録

Karmaにファイルを登録する方法について記載します。
登録することでKarma起動時に読み込まれます。
htmlでいう<script>で読み込むのと同じイメージです。

テスト対象ファイルの作成と登録

テスト対象ファイルを作成
mkdir app && touch app/app.js

中身はコンソールへのログ出力と、add,subtract関数を定義しておきます。

app.js
function add(a, b) {
    return a + b;
}

function subtract(a, b) {
    return a - b;
}

設定ファイルのfilesに上記ファイルを追加します

karma.conf.js
module.exports = function(config) {
    config.set({
+       files: [
+           'app/app.js'
+       ],
        logLevel: config.LOG_DEBUG
    })
}
karmaを実行
karma start

...
09 05 2016 21:46:52.236:DEBUG [watcher]: Watching "/Users/howdy/VscodeProjects/study-karma/app/app.js"
...

[watcher]行が増えていることが確認できます。

Jasmineのインストール

テストフレームワークであるJasmineをkarmaで使うためにkarma-jasmineをインストールします。

karma-jasmineのインストール
npm install --save-dev karma-jasmine

設定ファイルのframeworksにjasmineを追加します

karma.conf.js
module.exports = function(config) {
    config.set({
+       frameworks: ['jasmine'],
        files: [
            'app/app.js'
        ],
        logLevel: config.LOG_DEBUG
    })
}

テストファイルの作成と登録

テストファイルを作成
mkdir test && touch test/appSpec.js
appSpec.js
describe('add関数のテスト', function() {
    it('1 + 2 は 3', function() {
        expect(add(1, 2)).toBe(3);
    });
});

設定ファイルのfilesに追加します。
*をつかってパターンマッチで指定することが可能です。
※正確にはglobスタイルです。

karma.conf.js
module.exports = function(config) {
    config.set({
        frameworks: ['jasmine'],
        files: [
            'app/app.js',
+           'test/*Spec.js'
        ],
        logLevel: config.LOG_DEBUG
    })
}

Tips パターンマッチの読み込まれる順番

パターンマッチで複数登録が可能ですが、その場合に読み込まれるのはアルファベット順になります。

Karmaを実行してブラウザでアクセス

karmaを実行
karma start

ブラウザでアクセスしてみましょう。

ブラウザでアクセスした後のコンソール
Chrome 50.0.2661 (Mac OS X 10.11.2): Executed 1 of 1 SUCCESS (0.006 secs / 0.001 secs)

アクセスした瞬間に処理が流れます。
以下の様な流れです。
ブラウザでアクセス→登録したjsファイルが読み込まれる→テストが実行される

Tips 外部ライブラリを使っている場合

アプリケーションのjs内でMoment.jsなどの外部ライブラリを使っているケースもあるかと思います。
この場合もfilesに登録してあげればOKです。
※filesに記載した順番に読み込まれるので必要になるjsより先に書く形になります。
(つまりアプリケーションのhtmlで記載しているscriptタグと同じ順です)

ステップバイステップで始めるKarma Step3.png

Step 4 Launcherを登録

ブラウザから手動でアクセスするのは面倒です。
これを自動化するための仕組みがランチャーです。
ランチャーを使うことでkarma start時に自動でテストを実行するURLを開いたブラウザが立ち上がります。

chromeランチャーをインストール
npm install --save-dev karma-chrome-launcher

設定ファイルのbrowsersにChromeを追加します

karma.conf.js
module.exports = function(config) {
    config.set({
        frameworks: ['jasmine'],
        files: [
            'app/app.js',
            'test/*Spec.js'
        ],
+       browsers: ['Chrome'],
        logLevel: config.LOG_DEBUG
    })
}
karmaを実行
karma start

ブラウザが自動で起動しましたね。
browsersとなっているように複数指定することが可能です。
つまり複数ブラウザを同時にテストすることもできます。

Tips ファイル監視による再実行

テスト対象ファイルでもテストファイルでもいいので書き換えてみましょう。
設定ファイルには記載していませんがautoWatchの設定がデフォルトでtrueになっており、ファイルの変更を監視しています。
そのため自動でテストが再実行されます。

ステップバイステップで始めるKarma Step4.png

Step 5 Preprocessorを登録

今まではブラウザでそのまま動作する素のJavascriptファイルを登録していました。
CoffeeScriptやTypeScriptやBabelを使用している場合は当然そのままでは動きません。
そのため Karmaに読み込ませる前に変換する必要があります。
このファイルの読み込みの前に処理を行うのが プリプロセッサです。
ここではテストファイルをCoffeeScriptにしてみます。

karma-coffee-preprocesserのインストール

CoffeeScriptをKarmaで使うためにkarma-coffee-preprocessorをインストールします

npm install --save-dev karma-coffee-preprocessor

テストファイルをCoffeeScriptで作成する

コピー
cp test/appSpec.js test/appSpec.coffee
appSpec.coffee
describe('add関数のテスト', () ->
    it('1 + 2 は 3', () ->
        expect(add(1, 2)).toBe(3);
    );
);

設定ファイルのpreprocessorsにcoffeeを追加します。
また.jsから.coffeeの方を読み込むようにします。

karma.conf.js
module.exports = function(config) {
    config.set({
        frameworks: ['jasmine'],
        files: [
            'app/app.js',
-           'test/*Spec.js'
+           'test/*Spec.coffee'
        ],
+       preprocessors: {
+           'test/*Spec.coffee': ['coffee']
+       },
        browsers: ['Chrome'],
        logLevel: config.LOG_DEBUG
    })
}
karmaを実行
karma start

テストファイルをCoffeeScriptで書くことができました。

karma-coffee-preprocessorのオプションを設定

この状態だとjsファイルに変換した後の状態になっているためエラーが起きた時にCoffeeScriptのどこで起きたのかわかりません。

coffee-scriptモジュールにはソースマップを出力する機能があるためそれを利用するように教えてあげる必要があります。

karma.conf.js
module.exports = function(config) {
    config.set({
        frameworks: ['jasmine'],
        files: [
            'app/app.js',
            'test/*Spec.coffee'
        ],
        preprocessors: {
            'test/*Spec.coffee': ['coffee']
        },
+       coffeePreprocessor: {
+           options: {
+               sourceMap: true
+           }
+       },
        browsers: ['Chrome'],
        logLevel: config.LOG_DEBUG
    })
}

試しにこの状態でテストをわざと間違えてエラーにしてあげましょう。
すると以下の様にcoffeeのどこでエラーになったかが正確にわかります。

エラー
Chrome 50.0.2661 (Mac OS X 10.11.2) add関数のテスト 1 + 2 は 3 FAILED
    Expected 3 to be 111111.
        at Object.<anonymous> (/Users/howdy/VscodeProjects/study-karma/test/appSpec.js:3:30 <- appSpec.coffee:3:26)

Tips CoffeeScript以外はどうする?

TypeScriptやBabelも同様にkarma-xxx-preprocessorが提供されているのでそれらを使えばOKです。
WebpackやBrowserifyは目的は違いますが 事前に変換するという意味では同じため同様にpreprocessorを使用します。
それぞれ使うプリプロセッサによってオプションが違いますので各々のサイトで確認しましょう。

ステップバイステップで始めるKarma Step5.png

Step 6 Reporterを登録

レポーターを利用することでテスト結果をカスタマイズすることができます。
ここでは代表的な3つを紹介します。

karma-mocha-reporter

karma-mocha-reporterはテスト結果を見やすく表示してくれます。

karma-mocha-reporterをインストール
npm install --save-dev karma-mocha-reporter

設定ファイルのreportersにmochaを追加します

karma.conf.js
module.exports = function(config) {
    config.set({
        frameworks: ['jasmine'],
        files: [
            'app/app.js',
            'test/*Spec.coffee'
        ],
        preprocessors: {
            'test/*Spec.coffee': ['coffee']
        },
        coffeePreprocessor: {
            options: {
                sourceMap: true
            }
        },
        browsers: ['Chrome'],
+       reporters: ['mocha'],
        logLevel: config.LOG_DEBUG
    })
}
karmaを実行
karma start

こっちのほうが私は好きですね。
Screen Shot 2016-05-12 at 10.26.53 PM.png

比較用に標準(変更前)も貼っときます。
Screen Shot 2016-05-12 at 10.28.05 PM.png

karma-coverage

karma-coverageはテストのカバレッジをhtmlで出力することができます。

karma-coverageをインストール
npm install --save-dev karma-coverage

設定ファイルのreportersにcoverageを追加します。
coverage対象を定めるためにpreprocessorsに対象のjsファイルを追加します。

karma.conf.js
module.exports = function(config) {
    config.set({
        frameworks: ['jasmine'],
        files: [
            'app/app.js',
            'test/*Spec.coffee'
        ],
        preprocessors: {
+           'app/app.js': ['coverage'],
            'test/*Spec.coffee': ['coffee']
        },
        coffeePreprocessor: {
            options: {
                sourceMap: true
            }
        },
        browsers: ['Chrome'],
-       reporters: ['mocha'],
+       reporters: ['mocha', 'coverage'],
        logLevel: config.LOG_DEBUG
    })
}
karmaを実行
karma start

プロジェクトフォルダ直下にcoverageディレクトリが作成されます。

coverageディレクトリの中身
coverage
└── Chrome\ 50.0.2661\ (Mac\ OS\ X\ 10.11.2)
    ├── app
    │   ├── app.js.html
    │   └── index.html
    ├── base.css
    ├── index.html
    ├── prettify.css
    ├── prettify.js
    ├── sort-arrow-sprite.png
    └── sorter.js

2 directories, 8 files

app.js.htmlをブラウザで開いてみましょう。
subtract関数のテストを行っていないため赤くなりパーセンテージが下がってます。
Screen Shot 2016-05-12 at 10.35.32 PM.png

karma-junit-reporter

karma-junit-reporterはJUnitXML形式でテスト結果を出力できます。

karma-junit-reporterをインストール
npm install --save-dev karma-junit-reporter

設定ファイルのreportersにjunitを追加します。
junitReporter.outputDirに出力フォルダを指定します。(指定しない場合、プロジェクト直下にできて邪魔になります)

karma.conf.js
module.exports = function(config) {
    config.set({
        frameworks: ['jasmine'],
        files: [
            'app/app.js',
            'test/*Spec.coffee'
        ],
        preprocessors: {
            'app/app.js': ['coverage'],
            'test/*Spec.coffee': ['coffee']
        },
        coffeePreprocessor: {
            options: {
                sourceMap: true
            }
        },
        browsers: ['Chrome'],
-       reporters: ['mocha', 'coverage'],
+       reporters: ['mocha', 'coverage', 'junit'],
+       junitReporter: {
+           outputDir: 'report'
+       },
        logLevel: config.LOG_DEBUG
    })
}
karmaを実行
karma start
reportディレクトリの中身
report
└── TESTS-Chrome_50.0.2661_(Mac_OS_X_10.11.2).xml

0 directories, 1 file

Screen Shot 2016-05-12 at 10.43.10 PM.png

ステップバイステップで始めるKarma Step6.png

その他 Karmaの実行時オプション

前項までに扱っていた内容ですとkarma startしか出てきませんでしたが
他にもkarma initkarma runという2つのコマンドがあります。
それぞれ解説します。

karma init

karma.conf.jsを対話形式で作るためのオプションです。
本記事では学習のために敢えて使いませんでした。
一度自分なりの設定ファイルを作ってしまったらそれをコピったほうが早いので気がします。
Screen Shot 2016-05-15 at 8.28.37 PM.png

karma run

テストだけを実行するコマンドです。
karma startでKarmaのサーバーを立てておきます。(ブラウザもそのまま起動したままにしておく)
その状態で 別のコンソールからkarma runを実行することでテストを実行することが可能です。
ソースファイルを変更するとテストが再実行されますが、このときに裏でkarma runが呼ばれているのだと思われます。

karma start

主にやっていることは以下の3点です。

  • KarmaのWebサーバーを立てる
  • テスト実行用ブラウザーを起動する
  • karma runを一度だけ呼ぶ

Tips 設定ファイルの指定

init, start, runの全てでファイル名を指定することが可能です。
karma start karma-dev.conf.jsのように書けます。

また本記事ではkarma.conf.jsしか扱いませんでしたが.coffeeでも記述可能です。
指定しない場合は以下の4ファイルを読みこむようになっています。
OSSのプロジェクトでkarma.conf.jsが見つからないときは.configにおいてあるかもしれません。

  • ./karma.conf.js
  • ./karma.conf.coffee
  • ./.config/karma.conf.js
  • ./.config/karma.conf.coffee

Tips --single-run オプション

--single-runオプションを指定することでテストを一度だけ実行することが可能です。
karmaを動かした後にビルドする際に使用します。

karma start --single-run

その他 設定ファイルの項目

Stepごとの手順では省略しましたが重要そうなものを記載しておきます。

詳細はリファレンスを参照してください。

Tips 項目の種類は2種類ある

Karmaが用意している項目とプラグインが独自に定義した項目の2種類があるのに注意してください。
ここではKarmaが用意している項目しか扱いませんがStepの中で出てきたcoffeePreprocessorkarma-coffee-preprocessorが独自に定義した項目です。
そのためKarmaのリファレンスには記載されていません。

basepath

filesで読み込むファイルのベースを指定できます。
デフォルトは''です。
karma.conf.jsをプロジェクト直下から変更した場合はいじったほうが記載が楽になります。

exclude

filesで登録したファイルから除外したいファイルを指定できます。
※files同様*を使った指定が可能です。

plugins

自分で書くことはないかもしれませんが、仕組み上重要なので記載しておきます。
npmで各種プラグインをインストールした後にkarma.conf.jsjasmineChromeと書くだけでなぜ使えるのかですが、このpluginsの設定のおかげです。
デフォルトでkarma-*が指定されているためkarma-*でマッチするnode_moduleを自動で読み込んでいるわけですね。

試しにplugins:[]と記載して実行すると以下のように見つからない旨のエラーになります。

pluginが見つからない場合のエラー
...
Error: No provider for "framework:jasmine"! (Resolving: framework:jasmine)
...

autoWatch

デフォルトはtrueです。
filesで指定したファイルを監視して変更があったら再実行してくれます。

autoWatchBatchDelay

デフォルトは250です。
監視対象ファイルが変更されてから何msテストを実行するまで待機するかどうかの設定です。
例えば5000を指定した場合、5秒間の間にファイル保存を何度やってもテストは5秒後に1回しか走りません。

port

デフォルトは9876です。

singleRun

デフォルトはfalseです。
CI用のモードです。
trueを指定した場合、テスト実行後にサーバーやブラウザを落とします。

client.args

デフォルトはundefinedです。
karma-jasmineやkarma-mochaなどにオプションを渡すためのプロパティです。

例えばgrepを使えばテスト項目を絞ることができます。

karma.conf.js
config.set({
    ...
    client: {
        args: ['--grep', 'add関数のテスト']
    }
})

karma runで指定することも可能です。以下のように--の後に指定します。

部分的にテストを実行
karma run -- --grep='add関数のテスト'

Tips オプションについて

例えばjasmineはspecFilterという機能を使ってテスト対象を絞り込むことが可能です。
grepを指定した場合、karma-jasmineがgrepの内容をjasmine上のspecFilterに登録してくれています。

その他 プラグインの探し方

npmjsで検索になるのでしょうか。
良いやり方がありましたら教えて下さい。

あとがき

Karma自体はそんなに内容は深くないので問題はないのですがプラグインに渡すプロパティの設定はちょっと厄介かもしれません。
プラグインのドキュメントが整備されていないとソースを見ないとちょっとわからないかもです。。