Azure PipelinesでGithub上のコードのユニットテストを自動実行

## はじめに
MicrosoftによるGithubの買収が[完了して](https://japan.zdnet.com/article/35127696/)、MS製品とGithubの連携が強化されてきました。また、VSTSがAzure DevOps Serviceに衣替えしました。アドベントカレンダーというせっかくの機会なので、前から試してみたかった[Azure Pipelines](https://azure.microsoft.com/ja-jp/services/devops/pipelines/)でGithubにホストしているソースコードのCI/CDを回してみたいと思います。

Azure PipelinesでCI/CDを回す大きなメリットは、並列処理数です。OSSとしてホストしているソースコードでであれば、10個の並列処理を時間無制限で使用できます。私が使ったことのあるCircleCI（無償枠だと、1並列）と比較すると、大幅な並列数アップが図れます。

![コメント 2018-11-27 231250.jpg](https://qiita-image-store.s3.amazonaws.com/0/67429/e4b00b99-8f51-3bb7-4873-9652e5616c38.jpeg)

なお、今回は以下の環境とコードでユニットテストを実行します。 

#### 環境

| カテゴリ          | 製品               |
|:-----------------|:------------------|
| CI/CD            | Azure Pipelines |
| Repository       | Github |
| traget OS        | Windows Server, Ubuntu |
| Runtime          | Node.js 10, 11 |
| Test Framework   | Mocha, mocha-junit-reporter, chai |

#### テスト対象コード

```js:index.js
const fs = require("fs");

const readTxt = () => {
    // このコード、突っ込までないでください。。。。
	const txt = fs.readFileSync(__dirname + "\\hello.txt", "utf8");
	return txt;
};

module.exports = readTxt;
```

#### テストコード

```js:test.js
const assert = require("chai").assert;
const readTxt = require("../index");

describe("index.js", function () {
    it("should recieve Hello, World!", function () {
        const txt = readTxt();;
        assert.equal(txt, "Hello, World!");
    });
});
```

## Azure pipelinesの設定

Step by StepでAzureのpiplinesを設定していきます。私は認証など一部の操作が完了していて画面ショットが取れていない箇所があるので、全て確認したいという方は[公式サイト](https://docs.microsoft.com/ja-jp/azure/devops/pipelines/get-started/pipelines-sign-up?toc=/azure/devops/pipelines/toc.json&bc=/azure/devops/boards/pipelines/breadcrumb/toc.json&view=vsts)も確認してください。

### プロジェクトの作成
[Azure Pipelines](https://azure.microsoft.com/ja-jp/services/devops/pipelines/)のページからサインインしたら、最初にプロジェクトを作成します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/67429/22ba46a9-2df6-32ba-8a46-0250fdb6522c.png)

OSSの無償枠で使用する場合、Githubのリポジトリはpublicになっているはずです。そのため、プロジェクトのVisibilityもpublicにします（有償のプライベートリポジトリと連携させるなら、privateにします）。その後、唯一の必須項目であるのプロジェクト名を入力して、Create projectボタンをクリックするだけです。今回はプロジェクト名をtest-projectとしました。

### パイプラインの設定
新しいパイプラインを作ります。PipelinesペインからNew pipelineを選びます。
新しいパイプラインは、①Location、②Repository、③Template、④Save and runの4ステップで作成します。

![create-project.jpg](https://qiita-image-store.s3.amazonaws.com/0/67429/bc8c52af-1ad5-b423-2a46-2e6f55e3e70b.jpeg)

##### ①Location
もちろん、Githubを選びます。

![create-project.jpg](https://qiita-image-store.s3.amazonaws.com/0/67429/fe001fc0-7927-2312-0138-b83da6a3d47e.jpeg)

##### ②Repository
Githubのリポジトリとつなげます。そのためには、GithubとAzureの認証を行う必要があります。そこで、Athorizeボタンをクリックして、自分が管理しているGithubと連携させます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/67429/3ec97a38-9c01-cc54-da51-a17f62f9639c.png)

つづいて、Github内のリポジトリを選択します。私はpipelines-testというリポジトリを事前に準備しておきました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/67429/58e0c5ff-1ea9-6ec5-34b3-1c2c40efdcdf.png)

#### ④Template
Templateを選びます。テンプレートは、Azure Piplesで実行するステップを記述するための`azure-pipelines.yml`のひな形になります。今回はNode.jsを選びます。レポジトリに既に`azure-pipelines.yml`がある場合、この手順は飛ばされます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/67429/8b0201d7-dc9e-c1df-1500-3afdb8b079d0.png)

#### ④Save and run
選んだテンプレート内の処理を変更できます（変更内容は次節で紹介します）。Save and runボタンをクリックすると、連携しているレポジトリにPRを送って、`azure-pilelines.yml`を保存できます。これ以降、Githubのリポジトリが更新されるたびに、パイプラインが実行されることになります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/67429/f9964949-fd03-ddf7-bb4c-d68c7b3c3bb9.png)

## `azure-pilelines.yml`の編集
パイプラインで実行する処理は、[ビジュアルデザイナー](https://docs.microsoft.com/ja-jp/azure/devops/pipelines/get-started-designer?view=vsts&tabs=new-nav)と[YAML](https://docs.microsoft.com/ja-jp/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema)の2つの方法で記述できます。公式サイトではYAMLがお勧めされているので、私はYAMLを使用しています。

先述した通り、Win ServerおよびUbuntu上のNode.js v10とv11でMochaのユニットテストを実行します。

YAMLファイルは以下のようになります。この中から、ポイントとなる部分をピックアップして解説します。

```yaml:azure-pipelines.yml
# Node.js
# Build a general Node.js project with npm.
# Add steps that analyze code, save build artifacts, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript

jobs:
- job: Linux

  pool:
    vmImage: 'Ubuntu 16.04'

  strategy:
    matrix:
      node_10_x:
        node_version: 10.x
      node_11_x:
        node_version: 11.x

  steps:
  - script: |
      npm install
    displayName: 'npm install'

  - script: |
      npm test
    displayName: 'exec test'

  - task: PublishTestResults@2
    inputs:
      testRunner: JUnit
      testResultsFiles: ./test-results.xml
      testRunTitle: 'test result'

- job: Windows

  pool:
    vmImage: 'vs2017-win2016'

  strategy:
    matrix:
      node_10_x:
        node_version: 10.x
      node_11_x:
        node_version: 11.x

  steps:
  - script: |
      npm install
    displayName: 'npm install'

  - script: |
      npm test
    displayName: 'exec test'

  - task: PublishTestResults@2
    inputs:
      testRunner: JUnit
      testResultsFiles: ./test-results.xml
      testRunTitle: 'test result'
```

#### vmimage
vmimageでテスト環境を選択します。VMイメージはセルフホストエージェントと[マイクロソフトホストエージェント](https://docs.microsoft.com/ja-jp/azure/devops/pipelines/agents/hosted?view=vsts&tabs=yaml)の2種類があります。

セルフホストエージェントは自分でホストした環境を使用できます。セルフホストは、OSごとにドキュメントが用意されているので、そちらをご覧ください（たとえば、Linuxなら、[このページ](https://docs.microsoft.com/ja-jp/azure/devops/pipelines/agents/v2-linux?view=vsts)）。

一方、マイクロソフトホストエージェントは、文字通りマイクロソフトが準備してくれている環境のため、手軽に始められます。現在のところ、環境は以下の5つです。

- Ubuntu 16.04 (ubuntu-16.04)
- Visual Studio 2017 on Windows Server 2016 (vs2017-win2016)
- macOS 10.13 (macOS-10.13)
- Windows Server 1803 (win1803) - for running Windows containers
- Visual Studio 2015 on Windows Server 2012R2 (vs2015-win2012r2)

#### strategy
strategyのmatrixを指定すると、複数バージョンのNode.jsを指定できます。テスト対象のNode.jsが1バージョンのみの場合、stirategyの箇所を以下のように置き換えられます。

```yaml
- task: NodeTool@0 
  inputs:
    versionSpec: '11.x' # 実際に使用するバージョンを指定する
```

#### script
スクリプトを実行できます。私は、`package.json`のパッケージのインストールとscriptsに記述したtestコマンドを呼び出しています。ちなみに、testコマンドは以下のようになっています。

```json:package.json
{
  "scripts": {
    "test": "mocha test --reporter mocha-junit-reporter --reporter-options toConsole=true"
  }
}
```

#### task
タスクはパイプラインのビルディングブロックです。細かくパイプラインの処理を書かなくて済むため、適用できるものがあれば使った方が楽をできます。タスクの一覧は[カタログ](https://docs.microsoft.com/ja-jp/azure/devops/pipelines/tasks/index?view=vsts)にあるので、参照してください。

タスクの[**PublishTestResults@2**](https://docs.microsoft.com/ja-jp/azure/devops/pipelines/languages/javascript?view=vsts&tabs=yaml#run-unit-tests)は、jUnit XMLフォーマットの結果を出力するタスクです。

MochaのテストでjUnit XMLフォーマットの出力をするには、[mocha-junit-reporter](https://www.npmjs.com/package/mocha-junit-reporter)を使用します。mocha-junit-reporterは`--reporter-options toConsole=true`オプションを付けるとAzure Pipelineでも実行結果が見れるので便利です。

## 実行結果
YAMLの解説も終わったので、先ほどのパイプラインの実行結果を見てみましょう。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/67429/c2ac29dc-1ab6-a7fa-af0d-598e41acdef1.png)

Linuxだと失敗してる!!!!ということで、`index.js`を以下のように修正してGithubにアップします。再テストは自動で回ります。

```js:index.js
const fs = require("fs");
const path = require("path"); // 修正

const readTxt = () => {
	const txt =  fs.readFileSync(path.join(__dirname, "hello.txt"), "utf8"); // 修正
	return txt;
};

module.exports = readTxt;
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/67429/f30a4817-9b9a-af0a-f98c-2ae84eedba9e.png)

今度は、無事成功!!!! Windows OSで作っていたので、パス問題に気付いてなかったので助かりました（白々しい）。

このように、Azure Pipelinesを使えば簡単にユニットテストを自動化できます。ぜひ使ってみてください。