[Jenkins] Pipelineスクリプトの単体テスト環境構築

# はじめに
業務で、JenkinsのPipelineスクリプト（いわゆる`Jenkinsfile`というファイル名の、Groovyスクリプト）を書くことが度々あります。その単体テストができるということで
[Jenkins Pipeline Unit testing framework](https://github.com/jenkinsci/JenkinsPipelineUnit)というものを使ってみようとしたのですが。。。
筆者の知識が十分でなかったために、休日を1日溶かしました。

- Javaでの開発経験ならあるが、機能追加・不具合対応ばかりなので、既存のコードについて完璧には知らない
- 今回は、JUnitのアノテーション（C#でいう属性のような、関数定義の冒頭につけるアレ）を思い出せなかった

そこで、同じような目に会う方が減ってくれるといいな、という思いで投稿します。
今更Jenkins、という方もいるでしょう。しかし、組み込みソフト開発の業界は他のソフト開発業界での流行が、数年遅れて来ると思っているので、少しはニーズがあると嬉しいです。

# 構築手順
ハマったことも添えながら、手順を示していきます。
事前準備のところは、ネットに転がっているので、省略します。
（DOS窓で、`java --version`とか`gradle -v`とか打って、返事もらえていればOK）

- 事前準備
    - JDKのインストール
    - Gradleのインストール

- 手順概要
    1. ルートフォルダを作成して、`gradle init`する
    1. ソース格納フォルダを作成して、`build.gradle`を編集する
    1. テスト対象コードを作成する
    1. テストコードを作成する
    1. テストを実行する

## 1. ルートフォルダを作成して、`gradle init`する
好きなところに、好きな名前のフォルダを作成してください。
（以降の例は、`C:/Work/pipelineUnitTest`で進めます）

作成したフォルダを表示するWindowsエクスプローラのアドレス欄に`cmd`と打つなどして、作成したフォルダがカレントのDOS窓を開いて、`gradle init`と打ちます。
（以下は、Gradle 7.2での実行例。途中のキー操作は、何も入力せずにEnterでもOKです。）

``` bat
C:\Work\pipelineUnitTest>gradle init

Select type of project to generate:
  1: basic
  2: application
  3: library
  4: Gradle plugin
Enter selection (default: basic) [1..4] 1

Select build script DSL:
  1: Groovy
  2: Kotlin
Enter selection (default: Groovy) [1..2] 1

Project name (default: pipelineUnitTest):


> Task :init
Get more help with your project: Learn more about Gradle by exploring our samples
 at https://docs.gradle.org/7.2/samples

BUILD SUCCESSFUL in 1m 39s
2 actionable tasks: 2 executed
C:\Work\pipelineUnitTest>
```
いきなり選択肢が4個ある時点で、面食らいます。
普通に最初は`2: application`を選択したため、生成されたファイルが多すぎる上、どこに何が書いてあるかよく分からない、という状態に陥りました。
（`2: application`を選択して生成される`build.gradle`は次の手順2.で利用できます）

## 2. ソース格納フォルダを作成して、`build.gradle`を編集する
ルートフォルダ以下に、任意のソース格納フォルダを作成します。
GradleとかJavaとかの作法、お決まりみたいなのを知らないので、
筆者は下記`src`フォルダ以下を適当に作成しました。

```
pipelineUnitTest
│
├─.gradle       // 自動生成されたフォルダ
│
├─gradle        // 自動生成されたフォルダ
│
└─src           // 作成するフォルダ
    ├─job       // 作成するフォルダ：テスト対象コード格納用
    └─tests     // 作成するフォルダ
        └─job   // 作成するフォルダ：テストコード格納用
```

ソース格納フォルダを作成したら、設定ファイルである`build.gradle`を編集します。

`// コメント`付き設定は、`gradle init`で`2: application`を選択したときに生成される`build.gradle`から拝借すればよいです。
なので、筆者とはインストールしたJavaやGradleのバージョンが違うよ、という方も安心だと思います。

``` groovy:build.gradle
plugins {
    // Apply the groovy Plugin to add support for Groovy.
    id 'groovy'
}

repositories {
    maven { url 'https://repo.jenkins-ci.org/releases/' }
    
    // Use Maven Central for resolving dependencies.
    mavenCentral()
}

dependencies {
    testImplementation 'com.lesfurets:jenkins-pipeline-unit:1.9'

    // Use the latest Groovy version for building this library
    implementation 'org.codehaus.groovy:groovy-all:3.0.8'

    // Use the awesome Spock testing and specification framework even with Java
    testImplementation 'org.spockframework:spock-core:2.0-groovy-3.0'
    testImplementation 'junit:junit:4.13.2'
}

test {
    systemProperty "printstack.disabled", false
}

sourceSets {
    test {
        groovy {
            srcDirs= ['src']
        }
    }
}
```

何がハマったかというと、公式（[Jenkins Pipeline Unit testing framework](https://github.com/jenkinsci/JenkinsPipelineUnit)）の説明では`build.gradle`の編集について、
以下2行を足してくれ、ということしか書かれていないのです。。。

- `maven { url 'https://repo.jenkins-ci.org/releases/' }`
- `testImplementation 'com.lesfurets:jenkins-pipeline-unit:1.9'`

Gradleを分かっている方なら、きっと余裕なんだと思いますが、Gradle初心者の筆者には厳しかったです。

test { } の`systemProperty "printstack.disabled", false`は公式の説明がありません。
しかし、これがないと、テストコードに`printCallStack()`を書いても、コールスタックは出力されません。
（frameworkのコードを解析するのに、小一時間かけてしまいました・・・）

## 3. テスト対象コードを作成する
テストされる`Jenkinsfile`を作成します。
筆者は、`src/job/Jenkinsfile`として、公式の説明通りの内容で作成しました。

``` groovy:Jenkinsfile
node {
    stage('Process output') {
        if (fileExists('output') && readFile('output') == 'FAILED!!!') {
            currentBuild.result = 'FAILURE'
            error 'Build failed'
        }
    }
}
```

テストコードの中で、テスト対象コードのファイルパスを指定するので、ルートフォルダ以下なら、格納先フォルダはどこでも良さそうです。

## 4. テストコードを作成する
作成した`Jenkinsfile`をテストするためのコードを作成します。
以下の例は、`src/tests/job/JenkinsfileTest.groovy`として作成しました。

``` groovy:JenkinsfileTest.groovy
package tests.job

import org.junit.Before
import org.junit.Test

import com.lesfurets.jenkins.unit.BasePipelineTest

class JenkinsfileTest extends BasePipelineTest {
    @Override
    @Before
    void setUp() throws Exception {
        super.setUp()
    }
    
    @Test
    void executeTest() {
        runScript("src/job/Jenkinsfile")
        assertJobStatusSuccess()
        printCallStack()
    }

    @Test
    void exampleReadFileTest() {
        helper.addFileExistsMock('output', true)
        helper.addReadFileMock('output', 'FAILED!!!')
        runScript("src/job/Jenkinsfile")
        assertJobStatusFailure()
    }
}
```

公式（[Jenkins Pipeline Unit testing framework](https://github.com/jenkinsci/JenkinsPipelineUnit)）の説明に書かれている
`import com.lesfurets.jenkins.unit.BasePipelineTest`
だけでなく、アノテーションするためのimportも必要です。
（実際は色々まとめて、`import org.junit.*`とすれば良いと思います）

また、frameworkが用意してくれている各種モックを使うとき、（少なくとも自分の環境では）オーバーライドした`setUp()`で`super.setUp()`しておかないと、使えませんでした。
公式の説明だと`Mocking readFile and fileExists`の章以降は、そのことに触れていないので、ハマりました。

## 5. テストを実行する
ルートフォルダがカレントのDOS窓で、`gradle clean build test`と打ちます。
（testするにはbuildが必要なので、`gradle clean test`でも良いはず）

``` bat
C:\Work\pipelineUnitTest>gradle clean build test

BUILD SUCCESSFUL in 9s
4 actionable tasks: 4 executed
C:\Work\pipelineUnitTest>
```

ビルドが成功するとルートフォルダ直下に`build`フォルダが生成されているので、
`build\reports\tests\test\index.html`をブラウザで表示すれば、テスト結果を確認できます。
コールスタックも、ブラウザに表示される`Standard output`ボタンを押すと確認できます。

# おわりに
ソース格納フォルダの構成とか、`build.gradle`の書き方とか、その他諸々で、よろしくない点があれば、ご指摘ください。

公式（[Jenkins Pipeline Unit testing framework](https://github.com/jenkinsci/JenkinsPipelineUnit)）の説明について、自分がハマったことを色々と書いていますが、公式を貶めるつもりはなく、自分の知識不足なところを補完できて良かったと思っています。また、この記事で取り上げた説明は、記事を作成し始めた2021/9/11時点の内容を参照しています。そのため、この記事を投稿した後で、説明は修正されているかもしれません。