Karma を使ったユニットテストの結果を Jenkins 上で綺麗に表示するための設定

Angular CLI 製のプロジェクトなどで、Karma を使ったユニットテストを実施した時に、テスト結果を Jenkins 上で良い感じに表示させたい。そのために必要な設定をまとめる。

目次

Karma の設定

Karma の設定ファイル karma.conf.js を修正して、テスト実施後に JUnit 形式のテスト結果レポートと Cobertura 形式のカバレッジレポートを出力するようにする。いずれも、対応する Jenkins プラグインがあるためである。

まずはレポートを作成するために以下のパッケージをインストールする。

# Cobertura 形式のレポートを出力するためのプラグイン・Angular CLI で生成した場合は導入済
$ npm i -D karma-coverage-istanbul-reporter

# JUnit 形式のレポートを出力するためのプラグイン
$ npm i -D karma-junit-reporter

karma.conf.jsplugins に追加し、以下のようにプラグインが揃うようにしておく。

module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', 'jasmine-matchers', '@angular/cli'],
    plugins: [
      require('karma-jasmine'),
      require('karma-jasmine-matchers'),
      require('karma-chrome-launcher'),   // Chrome ブラウザでテストする場合
      require('karma-firefox-launcher'),  // Firefox ブラウザでテストする場合
      require('karma-ie-launcher'),       // IE ブラウザでテストする場合
      require('karma-spec-reporter'),
      require('karma-jasmine-html-reporter'),
      require('karma-coverage-istanbul-reporter'),  // Cobertura 形式レポート用・Angular CLI の場合導入済
      require('karma-junit-reporter'),              // JUnit 形式レポート用
      require('@angular/cli/plugins/karma')
    ],
  // 以下略…

続いて、Cobertura 形式のレポートが生成できるよう、coverageIstanbulReporter プロパティを書き加える。

module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', 'jasmine-matchers', '@angular/cli'],
    plugins: [ /* …中略… */ ],
    // Cobertura 形式のカバレッジレポートを出力するための設定
    coverageIstanbulReporter: {
      reports: ['html', 'lcovonly', 'cobertura'],
      fixWebpackSourcePaths: true
    },
    reporters: config.angularCli && config.angularCli.codeCoverage
      ? ['spec', 'coverage-istanbul', 'junit']  // この行については後述
      : ['spec', 'kjhtml'],
    // 以下略

さらに、JUnit 形式のレポートを出力するための設定を追加する。これで ./coverage/junit-report.xml が生成されるようになる。

    // Cobertura 形式のカバレッジレポートを出力するための設定
    coverageIstanbulReporter: {
      reports: ['html', 'lcovonly', 'cobertura'],
      fixWebpackSourcePaths: true
    },
    // JUnit 形式のレポートを出力するための設定
    junitReporter: {
      outputDir: 'coverage',
      outputFile: 'junit-report.xml',
      suite: '',
      useBrowserName: false,
      nameFormatter: undefined,
      classNameFormatter: undefined,
      properties: {},
      xmlVersion: null
    },

最後に、reporters プロパティで Cobertura 形式のレポートと JUnit 形式のレポートが出力されるよう設定する。これも Angular CLI で生成した雛形をベースにしているので、適宜調整すること。

    junitReporter: { /* …中略… */ },
    // Cobertura・JUnit 形式のレポートを出力する
    reporters: config.angularCli && config.angularCli.codeCoverage
      ? ['spec', 'coverage-istanbul', 'junit']  // ← ココ
      : ['spec', 'kjhtml'],

(必要に応じて) 複数ブラウザでテストを実行するための設定

複数のブラウザで同時にテストする際は、タイムアウト設定を入れておく必要がある。

Jenkins サーバ上で動作を見ていると、最大2ブラウザまでしか同時接続 (テスト実行) しないようだったので、残りの1ブラウザがタイムアウトにならないよう、タイムうと設定を長め (3分程度) にしておくと良いだろう。

    // 複数ブラウザでの動作を安定させるためタイムアウト設定を追加する (3分)
    browserNoActivityTimeout: 180000,
    // テストするブラウザの指定
    browsers: ['Chrome', 'Firefox', 'IE_extoff'],
    // IE はアドオンを無効にしないと正常に動作しないため独自に設定する
    customLaunchers: {
      IE_extoff: {
        base: 'IE',
        flags: ['-extoff']
      }
    },

レポートの生成確認

ここまでの作業でテストを実行してみて、以下の2ファイルが生成されるか確認する。

$ npm test – --single-run

正常に出力できていれば OK。あとはコレを Jenkins から呼ぶだけ。

一応、Angular CLI で生成した雛形をベースにした karma.conf.js の全量を置いておく。

module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', 'jasmine-matchers', '@angular/cli'],
    plugins: [
      require('karma-jasmine'),
      require('karma-jasmine-matchers'),
      require('karma-chrome-launcher'),   // Chrome ブラウザでテストする場合
      require('karma-firefox-launcher'),  // Firefox ブラウザでテストする場合
      require('karma-ie-launcher'),       // IE ブラウザでテストする場合
      require('karma-spec-reporter'),
      require('karma-jasmine-html-reporter'),
      require('karma-coverage-istanbul-reporter'),  // Cobertura 形式レポート用・Angular CLI の場合導入済
      require('karma-junit-reporter'),              // JUnit 形式レポート用
      require('@angular/cli/plugins/karma')
    ],
    client: {
      clearContext: false
    },
    files: [
      { pattern: './src/test.ts', watched: false }
    ],
    preprocessors: {
      './src/test.ts': ['@angular/cli']
    },
    mime: {
      'text/x-typescript': ['ts', 'tsx']
    },
    angularCli: {
      environment: ''
    },
    specReporter: {
      maxLogLines: 5,
      suppressErrorSummary: true,
      suppressFailed: false,
      suppressPassed: false,
      suppressSkipped: true,
      showSpecTiming: false
    },
    // Cobertura 形式のカバレッジレポートを出力するための設定
    coverageIstanbulReporter: {
      reports: ['html', 'lcovonly', 'cobertura'],
      fixWebpackSourcePaths: true
    },
    // JUnit 形式のレポートを出力するための設定
    junitReporter: {
      outputDir: 'coverage',
      outputFile: 'junit-report.xml',
      suite: '',
      useBrowserName: false,
      nameFormatter: undefined,
      classNameFormatter: undefined,
      properties: {},
      xmlVersion: null
    },
    // Cobertura・JUnit 形式のレポートを出力する
    reporters: config.angularCli && config.angularCli.codeCoverage
      ? ['spec', 'coverage-istanbul', 'junit']
      : ['spec', 'kjhtml'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    singleRun: false,
    // 複数ブラウザでの動作を安定させるためタイムアウト設定を追加する (3分)
    browserNoActivityTimeout: 180000,
    // テストするブラウザの指定
    browsers: ['Chrome', 'Firefox', 'IE_extoff'],
    // IE はアドオンを無効にしないと正常に動作しないため独自に設定する
    customLaunchers: {
      IE_extoff: {
        base: 'IE',
        flags: ['-extoff']
      }
    }
  });
};

Jenkins プラグインをインストールする

JUnit 形式のレポートと、Cobertura 形式のカバレッジレポートを出力し、Jenkins 管理画面上で表示できるようにするため、以下の2つのプラグインをインストールしておく。

Jenkinsfile を作成する

今回は Declarative Pipeline で作ってみる。

それぞれのプラグインをインストールしておくと、「Pipeline Syntax」画面で以下の2項目が選べるようになっているはずなので、コレでスニペットを生成すると良いだろう。

pipeline {
  // エージェント : 「any」で良い
  agent any
  // ビルドトリガ
  triggers {
    bitbucketPush()  // 「Build when a change is pushed to BitBucket」相当 (Bitbucket との連携時)
    pollSCM('')  // 「SCM をポーリング」相当
  }
  options {
    // ビルドの保存最大数を設定する
    buildDiscarder(logRotator(numToKeepStr: '5'))
  }
  // 変数定義
  environment {
    // チェックアウトするブランチ名 : ココでは develop ブランチでテストするテイ
    BRANCH = 'develop'
    // プロジェクト・リポジトリ URL
    GIT_URL = 'ssh://git@【プロジェクト・リポジトリ URL】.git'
    // リポジトリ・ブラウザ URL
    BROWSER_URL = 'http://【Bitbucket リポジトリ・ブラウザ URL】/browse'
  }
  stages {
    stage('Git チェックアウト') {
      steps {
        // Push を監視しチェックアウトする
        checkout poll: true,
                 scm: [
                   $class: 'GitSCM',
                   branches: [[name: "origin/${BRANCH}"]],
                   browser: [
                     $class: 'BitbucketWeb',
                     repoUrl: "${BROWSER_URL}"
                   ],
                   doGenerateSubmoduleConfigurations: false,
                   extensions: [],
                   submoduleCfg: [],
                   userRemoteConfigs: [[
                     credentialsId: '【認証情報】',
                     url: "${GIT_URL}"
                   ]]
                 ]
      }
    }
    stage('インストール') {
      steps {
        nodejs(configId: '【.npmrc ファイル】', nodeJSInstallationName: '【Node.js バージョン】') {
          bat 'npm install'
        }
      }
    }
    stage('テスト実行') {
      steps {
        nodejs(configId: '【.npmrc ファイル】', nodeJSInstallationName: '【Node.js バージョン】') {
          bat 'npm test -- --single-run'
        }
      }
      post {
        // UT が失敗しても必ず実行する
        always {
          // JUnit 結果集計を行う
          junit 'coverage/junit-report.xml'
          
          // Cobertura カバレッジレポートを出力する
          cobertura autoUpdateHealth: false,
                    autoUpdateStability: false,
                    coberturaReportFile: 'coverage/cobertura-coverage.xml',
                    conditionalCoverageTargets: '70, 0, 0',
                    failUnhealthy: false,
                    failUnstable: false,
                    lineCoverageTargets: '80, 0, 0',
                    maxNumberOfBuilds: 0,
                    methodCoverageTargets: '80, 0, 0',
                    onlyStable: false,
                    zoomCoverageChart: false
        }
      }
    }
  }
  post {
    always {
      // ワークスペースを削除する
      deleteDir()
    }
  }
}

あとは良きようにジョブを作って実行すれば OK。

テスト実施結果の見方

テストの実施結果は、Jenkins 管理画面で当該ジョブの「ビルド履歴」を開き、

を参照できる。

「Cobertura カバレッジ・レポート」が文字化けしている場合は、生成した ./coverage/cobertura-coverage.xml のエンコーディングに合わせて、Jenkinsfile で sourceEncoding 設定を変えてみよう (Pipeline Syntax を参照)。

以上。