【入門編】Jenkinsにおける『パイプライン・アズ・コード』のユニットテスト:JenkinsPipelineUnit活用術 – バージョン管理・CI/CD活用バイブル

こんにちは!日々のCI/CDパイプラインの構築やメンテナンス、本当にお疲れ様です。

「Jenkinsfileを修正してリポジトリにプッシュし、Jenkins上でビルドを走らせたら、初歩的なGroovyの文法エラーで即座に失敗した……。それを直してまたプッシュ……これを何度も繰り返した」
そんな絶望的な経験、ありませんか?

これ、本当に時間が溶けますよね。デバッグのために無駄なコミットログが汚れていくのも、エンジニアとしてはかなりストレスフルなはずです。

今回は、そんな不毛な「プッシュ&トライ」の泥沼からあなたを救い出す、JenkinsPipelineUnitを用いた「パイプライン・アズ・コードのユニットテスト」について、基礎の基礎から優しく丁寧に解説していきます。

これをマスターすれば、ローカルPC上で一瞬にしてJenkinsfileのテストが完了し、あなたの開発スピードとコードの信頼性は劇的に向上しますよ。さあ、一緒に新しい扉を開きましょう!

—

1. なぜ、Jenkinsfileにも「ユニットテスト」が必要なのか?

私たちが普段書くアプリケーションコード(Java, Go, Python, TypeScriptなど)には、当たり前のようにユニットテストを書きますよね。では、なぜインフラやCI/CDのコード(Jenkinsfile)にはテストを書かないのでしょうか?

「動かしてみないと分からないから」というのは、実は大きな誤解です。

Jenkinsfileも立派なGroovyスクリプトというプログラムです。プログラムである以上、ロジックがあり、条件分岐があり、外部コマンド呼び出しがあります。本番のJenkinsサーバーで動かす前に、ローカル環境で以下の検証ができるとしたら、どうでしょう?

  • 文法エラーやタイポの即座の検知:プッシュする前にエディタやテストランナーが教えてくれる。
  • 条件分岐(if/else)の網羅的テスト:ブランチ名や環境変数(`env.BRANCH_NAME`など)が異なる場合の挙動を、ローカルで一瞬でシミュレートできる。
  • 重いビルドを伴わない高速なフィードバック:数秒でテストが完了する。

これを実現するのが、今回紹介するJenkinsPipelineUnitです。

—

2. JenkinsPipelineUnitとは何か?

JenkinsPipelineUnitは、Jenkins Pipeline(Declarative / Scripted)を、実際のJenkinsサーバーなしで(モックを用いて)単体テストするためのGroovy製フレームワークです。

内部的には、JUnitなどのテストフレームワークと組み合わせて使用し、Jenkins特有のステップ(`sh`, `echo`, `git`, `stash`など)を「モック(偽物)」に置き換えて、スクリプトが意図通りに動作するかを検証します。

—

3. 基礎セットアップ:環境を整えよう

それでは、実際に手を動かしながら環境を構築していきましょう。
今回は、最も一般的で分かりやすい Gradle + Groovy + Spock(または JUnit) の構成をベースに進めます。

プロジェクト構成

プロジェクトのディレクトリ構造は、以下のように整理するのが美しくておすすめです。

my-pipeline-repo/
├── Jenkinsfile
├── vars/ # 共有ライブラリ用(必要に応じて)
├── src/ # 共有ライブラリのソース
└── test/
├── groovy/
│ └── JenkinsfileTest.groovy # テストコード
└── resources/ # テスト用リソース

`build.gradle` の設定

テストを実行するために、Gradleの依存関係を設定します。Groovyでテストを書く場合、BDDスタイルで非常に読みやすいテストが書けるSpockフレームワークの利用を強くおすすめします。

plugins {
id ‘groovy’
id ‘java’
}

repositories {
mavenCentral()
// JenkinsのライブラリがホストされているJenkins公式リポジトリ
maven { url ‘https://repo.jenkins-ci.org/public/’ }
}

dependencies {
// Groovyの基本ライブラリ
implementation ‘org.codehaus.groovy:groovy-all:3.0.9’

// テストフレームワーク (Spock)
testImplementation ‘org.spockframework:spock-core:2.0-groovy-3.0’

// 🌟 主役:JenkinsPipelineUnit
testImplementation ‘com.lesfurets:jenkins-pipeline-unit:1.11’

// JUnit 5 (Spockの基盤として必要)
testImplementation ‘org.junit.jupiter:junit-jupiter:5.8.1’
}

test {
useJUnitPlatform()
}

たったこれだけの依存関係を追加するだけで、あなたのローカル環境はJenkinsのテストラボに生まれ変わります。

—

4. 精度高い「Hello World」的テストを書く

それでは、実際に簡単なJenkinsfileと、それをテストするコードを書いてみましょう。

ターゲットの Jenkinsfile

今回は、環境変数によってメッセージを変えるシンプルなDeclarative Pipelineを対象にします。

// Jenkinsfile
pipeline {
agent any
stages {
stage(‘Greeting’) {
steps {
script {
if (env.BRANCH_NAME == ‘main’) {
echo “Deploying to PRODUCTION!”
} else {
echo “Deploying to Staging…”
}
}
}
}
}
}

テストコードの作成 (`JenkinsfileTest.groovy`)

次に、このJenkinsfileが正しく分岐するかを検証するテストコードを `src/test/groovy/JenkinsfileTest.groovy` として作成します。

import com.lesfurets.jenkins.unit.BasePipelineTest
import org.junit.Before
import org.junit.Test

class JenkinsfileTest extends BasePipelineTest {

@Override
@Before
void setUp() throws Exception {
super.setUp()
// JenkinsPipelineUnitの初期化
// 実際のJenkinsプラグインや共有ライブラリのパスなどをここで解決します
}

@Test
void “mainブランチの場合の本番デプロイメッセージのテスト”() {
// Given: 環境変数 BRANCH_NAME に ‘main’ を設定
helper.registerSharedLibrary(
// 必要に応じて共有ライブラリのモックも設定可能
)

// パイプライン内で参照される環境変数をモック
binding.setVariable(‘env’, [BRANCH_NAME: ‘main’])

try {
// When: Jenkinsfileをロードして実行
loadScript(‘Jenkinsfile’)
} catch (Exception e) {
// JenkinsPipelineUnitは処理の途中でジョブ終了系メソッド(currentBuildなど)で
// 例外を投げることがあるため、期待されるフローの終了をキャッチします
println “Pipeline execution finished with: ${e.message}”
}

// Then: ‘Deploying to PRODUCTION!’ というログが出力されたことを検証
// helper.callStack から呼び出されたステップを検証できます
boolean logged = helper.callStack.any { call ->
call.methodName == ‘echo’ && call.args[0] == ‘Deploying to PRODUCTION!’
}

assert logged, “Expected production deployment log was not found.”
}
}

テストの実行

ターミナルを開き、以下のコマンドを実行してみましょう。

./gradlew test

ビルドとテストが走り、緑色の成功(SUCCESS)が返ってくれば大成功です!
もしここでJenkinsfile側にタイポがあったり、存在しないメソッドを呼び出していたりすると、Gradleのテストが即座に失敗し、エラー箇所を教えてくれます。

—

5. モックを駆使して条件分岐を網羅する

実務のJenkinsfileは、もっと複雑です。`sh`コマンドで外部スクリプトを叩いたり、共有ライブラリのカスタムステップを呼び出したりしますよね。

JenkinsPipelineUnitの真骨頂は、これら外部との副作用を完全にモック化できる点にあります。

例えば、`sh ‘mvn clean package’` のようなシェル実行を伴うステップがある場合、実際のMavenビルドを走らせるとテストが遅くなりますし、ローカル環境にMavenがないと失敗してしまいます。

そんなときは、以下のようにモックを仕込みます。

// テストコード内で sh メソッドの挙動をインターセプトする
helper.registerAllowedMethod(‘sh’, [String.class], { cmd ->
println “Mocked sh execution: ${cmd}”
// 必要に応じて特定のコマンドに対する戻り値を返すことも可能
if (cmd.contains(‘mvn’)) {
return “BUILD SUCCESS”
}
return null
})

このように、ネットワークやファイルシステム、実際のDockerやKubernetesに依存しない「純粋なロジックのテスト」をローカルで高速に回すことができるのです。

—

6. おわりに:毎日の作業を劇的に楽にするために

今回は、JenkinsPipelineUnitを使ったJenkinsfileのユニットテストの基礎について解説しました。

  • Jenkinsfileの文法エラーやタイポは、プッシュする前にローカルで防ぐ。
  • `./gradlew test` を叩くだけで、数秒でパイプラインのロジックを検証できる。
  • `helper.callStack` や `registerAllowedMethod` を使って、外部コマンドや条件分岐を完全にコントロールする。

これをCI(GitHub Actionsや GitLab CI、あるいは自身のローカルフック)に組み込んでおけば、「Jenkinsにプッシュしてエラー、修正してプッシュ……」という無間地獄とは永久にお別れです。

パイプラインのコードも、立派なソフトウェアです。テストという安全網を敷くことで、あなたのデプロイメントパイプラインはより強靭で、変更を恐れないものに生まれ変わります。

明日からのCI/CDライフが、もっと快適で楽しいものになりますように。先輩エンジニアからのエールを送ります!

タイトルとURLをコピーしました