こんにちは!インフラストラクチャの世界へようこそ。
クラウドの構成管理(IaC)を触り始めると、誰もが一度はこんな恐怖心を抱くはずです。
- 「これ、本当に意図した通りの設定になっているだろうか…?」
- 「本番環境に適用して、万が一クラッシュしたらどうしよう……」
- 「適用(`apply`)するまで結果がわからないの、やっぱりスリリングすぎない?」
そう、従来のIaCは「書いて、適用して、祈る」の繰り返しでした。でも、そんなエンジニアの肝を冷やす時代はもう終わりです。
今回は、次世代のIaCツールとして絶大な支持を集める Pulumi を使って、「テスト駆動開発(TDD)」 を行う方法を徹底解説します。
これをマスターすれば、コードを書いたその場でテストが走り、クラウドに触れることなく安全性を担保できるようになりますよ。毎日のインフラ作業が、劇的に楽しく、そして安心に変わる体験を一緒に見ていきましょう!
—
1. IaCにおけるテストの重要性とPulumiのテスト機能
そもそも、なぜインフラに「テスト」が必要なのでしょうか?
アプリケーション開発の世界では、ユニットテストや結合テストを書くのはもはや常識です。しかし、インフラコードになると、なぜか「直接ステージング環境にデプロイして目視確認する」という荒業がまかり通っていました。
結果として、文法エラーやセキュリティグループの穴あき、命名規則の違反などが本番一歩手前で発覚し、夜中に冷や汗をかくことになります。
Pulumiがテストしやすい「決定的な理由」
多くのIaCツール(例えばHCLを使うTerraformなど)は独自のドメイン固有言語(DSL)を採用しているため、一般的なテストフレームワークを組み込むのが少し面倒でした。
しかし、PulumiはTypeScript, Python, Go, C#といった「使い慣れた本物のプログラミング言語」でインフラを書くことができます。
つまり、普段アプリ開発で使っているJest(TypeScript)やPyTest(Python)といった強力なテストエコシステムを、そのままインフラテストに流用できるのです。
Pulumiのテストには、大きく分けて以下の2つのアプローチがあります。
1. ユニットテスト(Mock Test): クラウドに接続せず、コードの構造やプロパティを秒速で検証する。
2. インテグレーションテスト(Integration Test): 実際にクラウドへリソースをデプロイし、正しく動作するか検証する。
今回は、この両輪の回し方を優しく丁寧に紐解いていきます。
—
2. モックを使った高速なユニットテストの実装
まずは、クラウドに一切接続せず、手元のマシンで一瞬で終わる「ユニットテスト」を作ってみましょう。今回は多くの現場で馴染み深い TypeScript + Jest の組み合わせを採用します。
基礎セットアップ
まずはプロジェクトの雛形を作り、Jestを導入します(ここではすでにPulumiプロジェクトが初期化されている前提で進めます)。
必要なテストライブラリをインストール
npm install –save-dev jest ts-jest @types/jest
npx ts-jest config:init
テスト対象のコード
例として、「安全なS3バケット(パブリックアクセスが完全にブロックされ、暗号化されている)」を構築するシンプルなコード(`index.ts`)を用意しました。
// index.ts
import as aws from “@pulumi/aws”;
// 暗号化され、パブリックアクセスが禁止された安全なバケット
export const bucket = new aws.s3.Bucket(“my-secure-bucket”, {
acl: “private”,
serverSideEncryptionConfiguration: {
rule: {
applyServerSideEncryptionByDefault: {
sseAlgorithm: “AES256”,
},
},
},
});
export const bucketName = bucket.id;
ユニットテストを書く
「このバケットは本当にパブリックになっていないか?」「暗号化設定は入っているか?」を検証するテストコードを書いてみましょう。Pulumiには、クラウド環境を完全に模倣(モック)する仕組みが標準で用意されています。
// index.test.ts
import as pulumi from “@pulumi/pulumi”;
// Pulumiのランタイムをモック化(実際のAWSには絶対にアクセスしません)
pulumi.runtime.setMocks({
newResource:- (args: pulumi.runtime.MockResourceArgs): { id: string, state: any } => {
return {
id: `${args.name}-id`,
state: args.inputs,
};
},
call: (args: pulumi.runtime.MockCallArgs) => {
return args.inputs;
},
});
describe(“S3 Bucket Security Tests”, () => {
// テスト実行前にインポートしてリソースを評価させる
let awsModule: typeof import(“./index”);
beforeAll(async () => {
awsModule = await import(“./index”);
});
it(“S3バケットのACLはプライベートであるべき”, (done) => {
pulumi.all([awsModule.bucket.acl]).apply(([acl]) => {
try {
expect(acl).toBe(“private”);
done();
} catch (error) {
done(error);
}
});
});
it(“S3バケットはサーバーサイド暗号化を設定していなければならない”, (done) => {
pulumi.all([awsModule.bucket.serverSideEncryptionConfiguration]).apply(([sseConfig]) => {
try {
expect(sseConfig).toBeDefined();
expect(sseConfig.rule.applyServerSideEncryptionByDefault.sseAlgorithm).toBe(“AES256”);
done();
} catch (error) {
done(error);
}
});
});
});
動作確認
ターミナルでテストを実行してみましょう。
npx jest
結果:
数ミリ秒でテストが走り、「PASS」の文字が表示されます。クラウドのアカウントすら持っていなくても、コードの品質チェックがローカルで完璧に完結するのです。これがユニットテストの強みです。
—
3. 実際にクラウドへデプロイして行うインテグレーションテストの手法
ユニットテストでコードのロジックが正しいことが確認できたら、次は「本当にAWS上で正しくリソースが作動するか」を検証するインテグレーションテストに進みます。
Pulumiには、テスト用のライブラリとして `@pulumi/pulumi/automation`(Automation API)が用意されています。これを使うと、テストコードの中からプログラム制御で「デプロイ $\rightarrow$ 動作検証 $\rightarrow$ 破棄(クリーンアップ)」を完全自動化できます。
インテグレーションテストの実装例
以下は、実際にスタックを立ち上げて、AWS上で正しく動いているかを確認するテストのイメージです。
// integration.test.ts
import as pulumi from “@pulumi/pulumi”;
import as auto from “@pulumi/pulumi/automation”;
import as aws from “@pulumi/aws”;
describe(“Infrastructure Integration Test”, () => {
let stack: auto.Stack;
// テスト開始前にインフラをデプロイ (Up)
beforeAll(async () => {
const projectName = “my-infra-test”;
const stackName = “integration”;
// インラインでPulumiプログラムを定義、または既存のプロジェクトを指定
const program = async () => {
const bucket = new aws.s3.Bucket(“test-bucket”, {
acl: “private”,
});
return { bucketName: bucket.id };
};
// 一時的なインメモリ・スタックを作成
stack = await auto.LocalProgram.createOrSelectStack({
projectName,
stackName,
program,
});
// プレビューなしでデプロイを実行
await stack.up({ onOutput: console.log });
}, 300000); // タイムアウトは長め(5分)に設定
// テスト終了後には必ずインフラを破棄する (Destroy)
afterAll(async () => {
if (stack) {
await stack.destroy({ onOutput: console.log });
await stack.workspace.removeStack(stack.name);
}
}, 300000);
it(“実際にAWS上にS3バケットが作成されていること”, async () => {
const outputs = await stack.outputs();
expect(outputs.bucketName.value).toBeDefined();
// 必要に応じてAWS SDK等を使って、バケットに実際にアクセスできるかテストも書けます
});
});
このテストを走らせると、Jestが裏側で自動的にAWSへリソースをデプロイし、アサーション(検証)をクリアした瞬間に、きれいにリソースを全削除(`destroy`)してくれます。課金の心配や「消し忘れ」の恐怖からも解放されますね。
—
4. 品質を担保するCIパイプラインの組み方
ローカルでユニットテストとインテグレーションテストが書けるようになったら、最後はこれをGitHub ActionsなどのCI/CDパイプラインに組み込み、「人間がレビューする前に、テストが合格しないとマージできない仕組み」を作ります。
以下は、GitHub Actionsのワークフロー設定ファイルのサンプルです。
.github/workflows/infra-test.yml
name: Infrastructure TDD Pipeline
on:
pull_request:
branches: [ main ]
permissions:
id-token: write
contents: read
jobs:
unit-test:
name: Fast Unit Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ’20’
cache: ‘npm’
- name: Install dependencies
run: npm ci
- name: Run Unit Tests (Mock)
run: npx jest index.test.ts
integration-test:
name: Cloud Integration Tests
needs: unit-test # ユニットテストが成功した時だけ実行
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ’20’
cache: ‘npm’
- name: Install dependencies
run: npm ci
# OIDCなどを使って安全にAWS認証情報を取得
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsPulumiRole
aws-region: ap-northeast-1
- name: Run Integration Tests
run: npx jest integration.test.ts
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
このパイプラインがもたらす圧倒的なメリット
1. プルリクエストの段階でバグを阻止: 文法ミスやセキュリティポリシー違反のあるコードは、ユニットテストの段階で弾かれます。
2. 安全なデプロイの自動化: クラウドを使った結合テストも自動で行われるため、「動くことが証明されたインフラコード」だけが `main` ブランチにマージされます。
—
まとめ
今回は、Pulumiを使ったインフラのテスト駆動開発(TDD)について、ユニットテストから結合テスト、そしてCIパイプラインの構築までを一気に解説しました。
- ユニットテスト(Mock)で、クラウドに触れずに秒速でロジックを検証する。
- インテグレーションテストで、Automation APIを使い、デプロイからクリーンアップまでを自動化する。
- CI/CDでテストを義務化し、チーム全体の安心感を底上げする。
「インフラの変更は怖いもの」という古い常識は、もう過去のものです。Pulumiとテストの力を借りれば、アプリ開発と同じように、自信を持ってコントリビュートできるようになります。
さあ、今日の作業から、あなたのインフラコードにもテストを取り入れてみませんか?毎日の作業が劇的に楽しく、そして驚くほど安全になりますよ!