☕️

mocha + power-assertを導入してみた(ブラウザもnode.jsも)

2021/07/04に公開

テスト書いてないとかお前それ @t_wada の前でも同じこと言えんの?

こんにちは。皆さんテストは書いていますか。

私はAngularJSを書き始めた当初Jasmineを使い、以後1年以上そのままだったのですが、周りの流れがどうもmochaらしいのと、t_wada氏のpower-assertを試してみたかったことから、今回一念発起して乗り換えました。今回はその内容のメモです。

あと、gruntです。(gulpは全然勉強してない)

変更前の環境

クライアント・サイド (Browser)

  • grunt
  • jasmine
  • karma
  • karma-jasmine

サーバ・サイド (node.js)

  • grunt
  • jasmine-node

両サイドともフレームワークにjasmineを用いて、テストランナーにはjasmine-nodekarmaを使用、gruntで監視して動かすといった構成でした。

クライアントではAngularJSを書いているため、jasmine標準のspyやmockを多用しています。

サーバ・サイドのmocha + power-assert導入

クライアントとサーバでは導入法が異なります。まずはnode.js向けの話です。

package.json

{
  "devDependencies": {
    "grunt": "~0.4.5",
    "grunt-espower": "~0.9.1",
    "grunt-este-watch": "~0.1.18",
    "grunt-mocha-istanbul": "~2.2.0",
    "load-grunt-tasks": "~1.0.0",
    "power-assert": "~0.9.1"
  }
}

今回の内容に無縁なものは除外して載せています。npmgruntの使い方などはここでは割愛します。

  • #grunt-espower
    • power-assertを利用するためには一度コードを変換する必要がある。
  • #grunt-este-watch
    • 監視のためのもの。grunt-contrib-watchより使い勝手がよかった。今回の話とは関係ない。
  • #grunt-mocha-istanbul
    • mochaが含まれている。istanbulはカバレッジの可視化ツールなのでこの際こっちにしてみた。
  • #load-grunt-tasks
    • grunt.loadNpmTasks()を省略できる。今回の話とは関係ない。
  • #power-assert
    • 一番重要なアサーション・ツール。

Gruntfile.js

grunt.initConfig({
  dir: {
    espowered: 'espowered/'
  },
  src: {
    spec: '**/*-spec.js'
  },
  espower: {
    test: {
      files: [
        {
          expand: true,
          cwd: 'test/',
          src: ['<%= src.spec %>'],
          dest: '<%= dir.espowered %>',
          ext: '.js'
        }
      ]
    }
  },
  mocha_istanbul: {
    test: {
      src: '<%= dir.espowered %>',
      options: {
        mask: '<%= src.spec %>',
        reportFormats: ['lcov']
      }
    }
  },
  // ...

この辺は各々のREADMEを参照してください。mocha_istanbulはこの設定だけで動いたので優秀です。

[
  'ts',
  'espower',
  'mocha_istanbul',
  // ...
]

タスクはこんな感じ。tsはTypeScriptのコンパイルです。

specを修正

mochajasmineと同じようにdescribe, itの記法なので大幅に書きなおす必要がありません。
冒頭にvar assert = require('power-assert');を追記して、jasmineのアサーションをpower-assertに書き換えていくだけです。

あとはgruntを実行すれば、espowerで変換→mochaで実行→istanbulのカバレッジ保存、という流れになっています。

クライアント・サイドのmocha + power-assert導入

続いてクライアント・サイドです。こちらの手順は色々みているとBrowserifyを使ったりなんやかんや…、と拒絶反応を起こしそうだったので、学習コストを低くする今に近い構成としました。

概要としては、

  • karma
  • karma-mocha
  • PhantomJS

といった感じです。karmaはAngularJSチームが開発したテストランナーです。

package.json

{
  "devDependencies": {
    "grunt": "~0.4.5",
    "grunt-espower": "~0.9.1",
    "grunt-este-watch": "~0.1.18",
    "grunt-karma": "~0.9.0",
    "karma": "~0.12.24",
    "karma-coverage": "~0.2.6",
    "karma-mocha": "~0.1.9",
    "karma-mocha-reporter": "~0.3.1",
    "karma-phantomjs-launcher": "~0.1.4",
    "load-grunt-tasks": "~1.0.0"
  }
}
  • #grunt-karma
    • gruntからkarmaを動かすためのもの。
  • #karma
    • テストランナー本体。
  • #karma-coverage
    • istanbulを用いたカバレッジ可視化をkarmaがやってくれる。karma-runnerのプロジェクト下で開発されているのも強み。
  • #karma-mocha
    • karmamochaを使うためのもの。他にもjasmine用のプラグインなど複数ある。
  • #karma-mocha-reporter
    • 後述。
  • #karma-phantomjs-launcher
    • karmaの扱うブラウザをPhantomJSにするためのもの。これでTerminal内でクライアント・サイドのテストが完結する。(※)

power-assertはここには記述しません。

※注意: karma-phantomjs-launcherが利用するPhantomJSの現時点でのバージョンではFunction.prototype.bindが実装されていないようで、この処理を利用したい場合は# es5-shim を利用すると良いです。

bower.json

{
  "dependencies": {
    "power-assert": "*"
  }
}

power-assertはbower側に記述します。

Gruntfile.js

espowerの設定はサーバ・サイドのときと同様です。ここにkarmaの設定が加わります。

// ...
karma: {
  unit: {
    configFile: 'karma.conf.js',
    singleRun: true
  }
},
// ...

といってもGruntfile.jsに書くのはこれだけで、詳細はkarma.conf.jsに記述します。

[
  'ts',
  'espower',
  'karma',
  // ...
];

タスクは、espowerで変換してからkarmaの順です。

karma.conf.js

// Karma configuration
// http://karma-runner.github.io/0.10/config/configuration-file.html

module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['mocha'], // 重要!

    files: [
      'app/bower_components/angular/angular.js',    // こんな感じで
      'app/bower_components/jquery/dist/jquery.js', // ブラウザで使うライブラリを指定
      'app/bower_components/power-assert/build/power-assert.js', // 重要!
      //
      'lib/**/*.js',      // 元となるソース
      'espowered/**/*.js' // spec, ただし espowered/ の方を指定する
    ],

    exclude: [],
    port: 8080,
    logLevel: config.LOG_INFO,
    autoWatch: false,

    // カバレッジの取得元を記述
    preprocessors: {
      'lib/**/*.js': 'coverage'
    },

    // mocha とは karma-mocha-reporter のこと
    // coverage を集計するなら 'coverage' も追加
    reporters: ['mocha', 'coverage'],
    
    colors: true,
    browsers: ['PhantomJS'],
    singleRun: false
  });
};

最初karma-mocha-reporter無しのまま動作させてみたんですが、karmaが生成する結果ログを表示するだけで、せっかくのmochaらしい表示がされず不満だったので、karma-mocha-reporterというプラグインを導入しました。これでmochaらしいリスト表示やチェックマークなどがkarmaログに続いて表示されます。

今後の課題

mochaにはspyやmockといった仕組みが無く、別のライブラリを組み合わせるらしいので、AngularJSのテストはまだまだjasmineと併用になるかという感じです。

私の下では巨大な本体と分割したプロジェクトを動かしているので、小規模なものから順次mochaにして、大規模な本体側は当面jasmineのまま移行を続けるという予定で考えています。protractor#との併用も悩ましいですが、今回は省略しました。

まとめ

jasmineを使っていても簡単に乗り換えられることが分かりましたね! mochaにしたけどspecを全部書き換えるのは大変…というときはexpectations#というjasmine APIと共通のインタフェースを持つライブラリがあるようなので安心です!(@vvakame氏ありがとう)

それでは、mochaのナイスなログ表示と強力なpower-assertで快適なテストライフを!

Discussion