LoginSignup
34
25

More than 5 years have passed since last update.

KarmaとJasmineとWebpackでJavaScript(TypeScript)でユニットテストを実行してレポートやカバレッジを出力する

Posted at

TypeScript Advent Calendar 2016 の 最終日 の記事を書かせて頂きました。

はじめに

Javascript のユニットテストを初めてやります。
その上で調査したことを書きます。

ゴール

TypeScript + Webpack + Karma + Jasmine
出力: テストレポート&カバレッジレポート

作業ステップ

  1. Jasmine 導入
  2. Karma 導入(Karma + Jasmine)
  3. Webpack導入(Karma + Webpack)
  4. TypeScriptの導入(TypeScript + Webpack + Karma)

前提

  • npm が使えること
  • node_modules/.bin にパスが通っていること
    • npm -g は使っていません
  • TypeScript を HelloWorld レベルで使えること
  • Webpack を HelloWorld レベルで使えること

テスト対象コードの準備

src/calc-utils.js
function add(x, y) {
  return x + y;
}

こちらをテスト対象にします。

Jasmine導入

Jasmine とは、Javascript のテスティングフレームワークです。

ターミナル
$ npm install -D jasmine

テストコード実装

test/calc-utils-test.js
describe('関数のテスト', function() {
  // 成功するテスト
  it('1 + 1 = 2である', function(){
    expect(add(1, 1)).toBe(2);
  });

  // 失敗するテスト
  it('1 + 2 = 3である', function(){
    expect(add(1, 2)).toBe(2);
  });
});

Hello World

以下のファイルを HTML で読み込むだけでテスト&テスト結果が表示されるようです。

  • node_modules/jasmine-core/lib/jasmine-core/jasmine.css
  • node_modules/jasmine-core/lib/jasmine-core/jasmine.js
  • node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js
  • node_modules/jasmine-core/lib/jasmine-core/boot.js

HTML ファイルの作成

index.html
<html>
  <head>
    <title>Jasmine Test</title>

    <link rel="stylesheet" type="text/css" href="./node_modules/jasmine-core/lib/jasmine-core/jasmine.css">
    <script src="./node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script>
    <script src="./node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js"></script>
    <script src="./node_modules/jasmine-core/lib/jasmine-core/boot.js"></script>

    <script src="./src/calc-utils.js"></script>
    <script src="./test/calc-utils-test.js"></script>
  </head>
  <body>
  </body>
</html>

ブラウザで開いてみる

Screenshot from 2016-12-16 12-26-48.png

※以降 index.html は使用しません。

Karma導入(Karma + Jasmine)

Node.js 上で動作するテストランナー
AngularJS の開発で使うために作られたらしいです。
テストを実行し、テストレポートを出力したり、カバレッジを取得したりという機能が備わっているようです。

ターミナル
$ npm install -D karma

Hello World

Karma 用の設定ファイル作成

ターミナル
$ karma init

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
> 

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
> test/*.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

Config file generated at "/home/chibi/unit-test/karma.conf.js".

Karma 実行

ターミナル
$ karma start

Screenshot from 2016-12-16 13-32-03.png

なんかブラウザが立ち上がった

ターミナル
$ karma start
16 12 2016 13:36:27.571:WARN [karma]: No captured browser, open http://localhost:9876/
16 12 2016 13:36:27.609:WARN [karma]: Port 9876 in use
16 12 2016 13:36:27.614:INFO [karma]: Karma v1.3.0 server started at http://localhost:9877/
16 12 2016 13:36:27.616:INFO [launcher]: Launching browser Chrome with unlimited concurrency
16 12 2016 13:36:27.676:INFO [launcher]: Starting browser Chrome
16 12 2016 13:36:29.778:INFO [Chrome 55.0.2883 (Linux 0.0.0)]: Connected on socket /#_5lnmqR9SDXJe0M6AAAA with id 46430444
Chrome 55.0.2883 (Linux 0.0.0): Executed 0 of 2 SUCCESS (0 secs / 0 secs)
[1AChrome 55.0.2883 (Linux 0.0.0): Executed 1 of 2 SUCCESS (0 secs / 0.002 secs)
[1AChrome 55.0.2883 (Linux 0.0.0) 関数のテスト 1 + 2 = 3である FAILED
    Expected 3 to be 2.
        at Object.<anonymous> (test/calc-utils-test.js:7:23)
Chrome 55.0.2883 (Linux 0.0.0): Executed 2 of 2 (1 FAILED) (0 secs / 0.004 secs)
[1AChrome 55.0.2883 (Linux 0.0.0): Executed 2 of 2 (1 FAILED) (0.086 secs / 0.004 secs)

なんか上手くいったっぽい

ブラウザが閉じれない(閉じるとまた開く)

karma がウォッチしてて、
コードが変わるたびにテストしてくれるようにしているためですかね。
karma の設定ファイルの以下の部分を true に変えてあげることで実行を続けることがなくなりました。

karma.conf.js
    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
-   singleRun: true,
+   singleRun: true,

PhantomJS

PhantomJS という Headless ブラウザです。
karma start を実行するたびに Chrome が起動していたので、PhantomJS を使うようにしました。

ターミナル
$ npm install -D karma-phantomjs-launcher

karma.conf.js を編集

karma.conf.js
    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
-   browsers: ['Chrome'],
+   browsers: ['PhantomJS'],

実行すると、コンソールログのみに!

JUnit形式レポート出力

ターミナル
$ npm install -D karma-junit-reporter

karma.conf.js を編集

karma.conf.js
    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
-   reporters: ['progress'],
+   reporters: ['progress', 'junit'],
+
+   junitReporter: {
+     outputDir: 'report'
+   },

出力結果

ターミナル
$ ls report/
TESTS-PhantomJS_2.1.1_(Linux_0.0.0).xml
ターミナル
$ cat report/TESTS-PhantomJS_2.1.1_\(Linux_0.0.0\).xml 
<?xml version="1.0"?>
<testsuite name="PhantomJS 2.1.1 (Linux 0.0.0)" package="" timestamp="2016-12-16T05:34:43" id="0" hostname="Chibintu" tests="2" errors="0" failures="1" time="0.004">
  <properties>
    <property name="browser.fullName" value="Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1 Safari/538.1"/>
  </properties>
  <testcase name="関数のテスト 1 + 1 = 2である" time="0" classname="PhantomJS_2_1_1_(Linux_0_0_0).関数のテスト"/>
  <testcase name="関数のテスト 1 + 2 = 3である" time="0.004" classname="PhantomJS_2_1_1_(Linux_0_0_0).関数のテスト">
    <failure type="">Expected 3 to be 2.
test/calc-utils-test.js:7:27
loaded@http://localhost:9876/context.js:151:17
</failure>
  </testcase>
  <system-out>
    <![CDATA[
]]>
  </system-out>
  <system-err/>

カバレッジ出力

ターミナル
$ npm install -D karma-coverage

karma.conf.js を編集

karma.conf.js
    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
+     'src/*.js': ['coverage']
    },

    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
-   reporters: ['progress', 'junit'],
+   reporters: ['progress', 'junit', 'coverage'],

    junitReporter: {
      outputDir: 'report'
    },
+
+   coverageReporter: {
+     dir: 'report',
+     reporters: [
+       { type: 'html' },
+       { type: 'cobertura' }
+     ]
+   },

出力結果

ターミナル
$ ls report/
PhantomJS 2.1.1 (Linux 0.0.0)  TESTS-PhantomJS_2.1.1_(Linux_0.0.0).xml

$ ls report/PhantomJS\ 2.1.1\ \(Linux\ 0.0.0\)/
cobertura-coverage.xml

$ cat report/PhantomJS\ 2.1.1\ \(Linux\ 0.0.0\)/cobertura-coverage.xml 
<?xml version="1.0" ?>
<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">
<coverage lines-valid="2"  lines-covered="2"  line-rate="1"  branches-valid="0"  branches-covered="0"  branch-rate="1"  timestamp="1481869473013" complexity="0" version="0.1">
<sources>
    <source>/home/chibi/unit-test</source>
</sources>
<packages>
    <package name="src"  line-rate="1"  branch-rate="1" >
    <classes>
        <class name="calc-utils.js"  filename="src/calc-utils.js"  line-rate="1"  branch-rate="1" >
        <methods>
            <method name="add"  hits="2"  signature="()V" >
                <lines><line number="1"  hits="2" /></lines>
            </method>
        </methods>
        <lines>
            <line number="1"  hits="1"  branch="false" />
            <line number="2"  hits="2"  branch="false" />
        </lines>
        </class>
    </classes>
    </package>
</packages>
</coverage>

Screenshot from 2016-12-16 19-47-16.png

Webpack導入(Karma + Webpack)

ここでは src/ にファイルが1個しかないので Webpack 効果は薄いけど・・・

ターミナル
$ npm install -D webpack
$ npm install -D karma-webpack

テスト対象コードを変更

Webpack を導入するにあたり、テスト対象コードを module 化します。

src/calc-utils.js(変更後)
module.exports = {
  add: function() {
    return x + y;
  }
}

テストコードを変更

test/calc-utils-test.js(変更後)
var calcutils = require('../src/calc-utils');

describe('関数のテスト', function() {
  // 成功するテスト
  it('1 + 1 = 2である', function(){
    expect(calcutils.add(1, 1)).toBe(2);
  });

  // 失敗するテスト
  it('1 + 2 = 3である', function(){
    expect(calcutils.add(1, 2)).toBe(2);
  });
});

webpack.config.js を作成

webpack.config.js
module.exports = {
  entry: "./src/calc-utils.js",
  output: {
    filename: "bundle.js"
  }
};

karma.conf.js を変更

module 化したことによって src/ をロードする必要がなくなりました。

karma.conf.js
    // list of files / patterns to load in the browser
    files: [
-     'src/*.js',
      'test/*.js'
    ],

また、Webpack を導入したことによって require を解決するために karma-webpack を使用します。

karma.conf.js
    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
-     'src/*.js': ['coverage']
+     'src/*.js': ['coverage', 'webpack']
+     'test/*.js': ['webpack']
    },

カバレッジが出力できなくなってしまった・・・

ターミナル
$ cat report/PhantomJS\ 2.1.1\ \(Linux\ 0.0.0\)/cobertura-coverage.xml 
<?xml version="1.0" ?>
<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">
<coverage lines-valid="0"  lines-covered="0"  line-rate="1"  branches-valid="0"  branches-covered="0"  branch-rate="1"  timestamp="1481876370794" complexity="0" version="0.1">
<sources>
    <source>/home/chibi/unit-test</source>
</sources>
<packages>
</packages>
</coverage>

こんな感じ・・・
色々調べた結果、2つのパッケージを導入

Karma + Webpack で Coverage を出力する

ターミナル
$ npm install -D karma-sourcemap-loader
$ npm install -D istanbul-instrumenter-loader

karma.conf.js を編集

karma.conf.js
    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
-     'src/*.js': ['coverage', 'webpack'],
-     'test/*.js': ['webpack']
+     'src/*.js': ['coverage', 'webpack', 'sourcemap'],
+     'test/*.js': ['webpack', 'sourcemap']
    },
+
+
+   // Webpack settings.
+   webpack: {
+     devtool: 'inline-source-map',
+     module: {
+       preLoaders: [
+         {
+           test: /\.js$/,
+           exclude: /(test|node_modules)/,
+           loader: 'istanbul-instrumenter'
+         }
+       ]
+     }
+   },

webpack: {} の部分は webpack.config.js の中でも良さそうな気がする
試したらダメだった。 karma.conf.js に書かないとダメみたい。

karma.conf.js から webpack.config.js を参照するわけではなくて、
karma.conf.js に記述されている webpack の設定を読むだけのようです。

karma.conf.js を微修正

istanbul-instrumenter-loader 自体がカバレッジに関するパッケージなので、
以下の行を削除してもカバレッジが出力されるようになるっぽい

karma.conf.js
    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
-     'src/*.js': ['coverage', 'webpack', 'sourcemap'],
      'test/*.js': ['webpack', 'sourcemap']
    },

ここまでで JavaScript + Webpack + Karma + Jasmine

TypeScriptの導入(TypeScript + Webpack + Karma)

コンソール
$ npm install -D typescript

Webpackを使っているので以下もインストール

コンソール
$ npm install -D ts-loader

更にKarmaも使っているので以下もインストールしておく

コンソール
$ npm install -D karma-typescript-preprocessor

更に更に TypeScript で Jasmine のコードを扱うので型定義ファイルをインストールしておく

コンソール
$ npm install -D @types/jasmine

JavaScriptのコードをTypeScriptに置き換える

src

src/calc-utils.js(置き換え前)
module.exports = {
  add: function() {
    return x + y;
  }
}
calc-utils.ts(置き換え後)
export class CalcUtils {
  public static add(x: number, y: number): number {
    return x + y;
  }
}

test

test/calc-utils-test.js(置き換え前)
var calcutils = require('../src/calc-utils');

describe('関数のテスト', function() {
  // 成功するテスト
  it('1 + 1 = 2である', function(){
    expect(calcutils.add(1, 1)).toBe(2);
  });

  // 失敗するテスト
  it('1 + 2 = 3である', function(){
    expect(calcutils.add(1, 2)).toBe(2);
  });
});
test/calc-utils-test.ts(置き換え後)
/// <reference path="../node_modules/@types/jasmine/index.d.ts" />
import {CalcUtils} from '../src/calc-utils';

describe('関数のテスト', function() {
  it('1 + 1 = 2である', function(){
    expect(CalcUtils.add(1, 1)).toBe(2);
  });

  it('1 + 2 = 3である', function(){
    expect(CalcUtils.add(1, 2)).toBe(2);
  });
});

tsconfig.json を作成

tsconfig.json
{
  "compileOnSave": true,
  "compilerOptions": {
    "target": "ES5",
    "noImplicitAny": false,
    "removeComments": false,
    "sourceMap": false
  },
  "exclude": [
    "node_modules",
    "test"
  ],
  "files": [
    "./src/calc-utils.ts"
  ]
}

karma.conf.js を変更

karma.conf.js
    // Webpack settings.
    webpack: {
      devtool: 'inline-source-map',
+     resolve: {
+       extensions: ['', '.ts', '.js', ".tsx"]
+     },
      module: {
+       loaders: [
+         {
+           test: /\.ts$/,
+           loader: 'ts-loader'
+         }
+       ],
        postLoaders: [
          {
-           test: /\.js$/,
+           test: /\.ts$/,
            exclude: /(test|node_modules)/,
            loader: 'istanbul-instrumenter'
          }
        ]
      }
    },

Karma + Webpack で TypeScript をビルドできるようにするために、
webpack.config.json に記述するべき TypeScript用の設定を karma.conf.js に記述する。
Webpack のみでビルドするときは webpack.config.json にも記述が必要

出力結果

4f4fc66d-5c4a-50fd-964b-f8b691332aec.png

ちゃんと TypeScript のコードでカバレッジが取れている。

最後に

package.json
"devDependencies": {
  "@types/jasmine": "^2.5.38",
  "istanbul-instrumenter-loader": "^1.1.0",
  "jasmine": "^2.5.2",
  "karma": "^1.3.0",
  "karma-chrome-launcher": "^2.0.0",
  "karma-coverage": "^1.1.1",
  "karma-jasmine": "^1.1.0",
  "karma-junit-reporter": "^1.2.0",
  "karma-phantomjs-launcher": "^1.0.2",
  "karma-sourcemap-loader": "^0.3.7",
  "karma-typescript-preprocessor": "^0.3.0",
  "karma-webpack": "^1.8.0",
  "ts-loader": "^1.3.3",
  "typescript": "^2.1.4",
  "webpack": "^1.14.0"
}

package.json のパッケージたちです。

参考にしたURL

https://tech.recruit-mp.co.jp/front-end/post-5299/
http://blog.odoruinu.net/2014/11/27/test-webpack-based-application-with-karma/
http://qiita.com/howdy39/items/cdd5b252096f5a2fa438

ありがとうございました!

34
25
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
25