12  Step 6: パッケージ化とテスト

最後の温度スキャンに進む前に、コードを適切な Julia パッケージに変換し、テストを追加する。このステップは短いソフトウェアエンジニアリングの寄り道である。物理を覆い隠すことが目的ではなく、再利用、検証、拡張がしやすいコードを残すことが目的である。Step 0 で作った ising-project ディレクトリはそのまま使い、その中身を package 本体と解析用コードに整理していく。

12.1 目的

プロジェクトを Julia パッケージ構造に変換し、基本的なテストを追加し、実装前にブレインストーミングすることがなぜ重要かを学ぶ。

12.2 パッケージとは?

  • パッケージは機能を再利用可能なモジュールにまとめたもの
  • Project.toml を持ち、依存関係と互換性の制約を宣言できる
  • 他のプロジェクトから usingimport で使える
  • Step 0 で作ったプロジェクト環境との違いは、moduleexport によって公開 API を定義する点

12.3 なぜブレインストーミングが大切か

AI に「これをパッケージに変換して」と頼むと、すぐにファイルの作成に取りかかるかもしれない。しかし、より良いアプローチは、まず以下を考え抜くことである:

  • そもそも Julia のパッケージとは何か? Step 0 で作ったプロジェクト環境と何が違うか?
  • パッケージ名は何にすべきか?
  • どの関数を公開(export)すべきか?
  • どの関数を非公開(内部ヘルパー)のままにすべきか?
  • まず最初に追加すべきテストは何か?
  • 温度スキャンやプロットのコードを package 本体に入れるべきか、analysis/ のような別の場所に置くべきか?

これがブレインストーミングである。コードを書く前に問いを立てることだ。これにより、より良い設計と少ない手戻りが実現する。

12.3.1 ツールごとのブレインストーミングの進め方

使っている AI ツールに合わせて、以下のように進めよ:

  • OpenAI Codex: 以下の「実行スペル」をそのまま貼り付けよ。Codex は対話的にブレインストーミングに応じる。
  • Cursor: チャットモードを PLAN モード(Agent モードの隣のドロップダウンで選択)に切り替えてから、実行スペルを貼り付けよ。PLAN モードでは、Cursor はコードを書く前に設計プランを提示し、あなたの承認を得てから実装に移る。
  • GitHub Copilot / Gemini: 以下の実行スペルをそのまま貼り付けよ。プロンプト自体が「コードを書く前に考えよう」という指示を含んでいるため、手動でブレインストーミングを行える。
ヒント発展: Superpowers フレームワーク

AI エージェントに体系的なワークフロー(ブレインストーミング、TDD など)を自動的に適用させる Superpowers というフレームワークもある。Claude Code や Cursor で利用でき、ブレインストーミングを自動でトリガーしてくれる。興味がある場合はリンク先を参照せよ。この講義では手動のブレインストーミングで十分である。

12.4 到達目標

このステップの終わりまでに、以下ができていること:

  • ブレインストーミングでテスト計画を決定した
  • Julia パッケージ構造(src/test/)がある
  • module 宣言がある
  • エクスポートされた関数(公開 API)がある
  • ブレインストーミングの議論に基づくテストがある
  • ブレインストーミングがなぜ役立つかの理解がある

12.5 実行スペル 1: ブレインストーミング

まず設計を考えるために、以下を AI に貼り付けよ。

最初に作った `ising-project` を、再利用しやすい Julia パッケージと解析用コードに整理したい。
コードを書く前に、一緒に設計を考えよ。
まず Julia のパッケージとは何かと、Step 0 で作ったプロジェクト環境と何が違うかを短く説明せよ。

以下の方針を前提に、必要なら改善案を出せ:
- 計算の核はプロジェクト直下の `src/` と `test/` を持つ package にまとめる
- 温度スキャンやプロットのような解析コードは `analysis/` に置く
- `mccompute/` と `analysis/` を別の Julia 環境に分ける案とも短く比較する

その上で以下を整理せよ:
- パッケージ名は何が良いか?
- どの関数をエクスポート(公開 API)すべきか?
- どの関数を非公開にすべきか?
- `analysis/` 側に置くべきコードは何か?
- まず最初に追加すべき最も重要なテストは何か?

一度に1つずつ質問して、設計を洗練せよ。まだファイルは作るな。

12.6 説明スペル 1

今合意した設計を整理せよ。
なぜ今回は 1 つの `ising-project` の中で package 本体と `analysis/` を分けるのか、
`src/`、`test/`、`analysis/` の役割、
package 側に置く関数と `analysis/` 側に置くコードの違い、
後で何を確認すれば設計どおりになったと判断できるかを説明せよ。

12.7 実行スペル 2: パッケージ変換

ブレインストーミングの後、以下を AI に貼り付けよ。

設計の議論に基づいて、この Julia プロジェクトを最小限のパッケージに変換せよ。
プロジェクト直下に `src/` と `test/` を持つ標準的なパッケージ構造を作成し、
コアコードをモジュールに移動し、公開関数をエクスポートし、
議論したテストを実装せよ。
温度スキャンやプロットのコードは package 本体に入れず、
後で `analysis/` から使える形にせよ。
シンプルで読みやすくせよ。

12.8 説明スペル 2

今作成したものを説明せよ。パッケージ構造、
src/ に移動したもの、module 宣言の役割、
export の意味、テストの構成、
なぜ温度スキャンやプロットをまだ `src/` に入れないのかを説明せよ。

12.9 習得する概念

  • 実装前のブレインストーミング
  • Julia パッケージ構造(src/test/
  • moduleexport
  • 公開 API vs 非公開ヘルパー
  • Project.toml のパッケージメタデータ
  • 基本的なテスト構造

12.10 最小限のパッケージ構造

ブレインストーミングの後、パッケージは以下のようになるかもしれない:

ising-project/
├── Project.toml
├── Manifest.toml
├── src/
│   └── Ising2D.jl
└── test/
    └── runtests.jl

Step 6 では package 本体をここまで整えれば十分である。次の Step 7 で analysis/temperature_scan.jl のような解析用スクリプトを追加し、using Ising2D で package を使う。

12.10.1 src/Ising2D.jl: モジュール

module Ising2D

export initialize_spins, magnetization, energy, metropolis_step!

# ... 既存の関数 ...

end # module
  • module Ising2D ... end で名前空間を作成
  • export で公開 API を宣言
  • パッケージが大きくなったら、src/ を複数ファイルに分割し include で読み込む

12.10.2 test/runtests.jl: テスト

using Ising2D
using Test

@testset "Ising2D" begin
    @test size(initialize_spins(8)) == (8, 8)

    spins = initialize_spins(8)
    m = magnetization(spins)
    @test -1 <= m <= 1

    e = energy(spins)
    @test isfinite(e)
end
  • @testset で関連するテストをグループ化
  • テストでは構文だけでなく、物理的に意味のある動作をチェックする

12.10.3 Project.toml: パッケージメタデータ

name = "Ising2D"
uuid = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
version = "0.1.0"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test"]

[compat]
julia = "1.10"
  • name: パッケージ名(通常はモジュール名と一致)
  • uuid: 一意なパッケージ識別子(pkg> generate で自動生成)
  • version: セマンティックバージョン番号
  • [extras][targets]: テスト専用の依存関係とテストターゲット
  • [compat]: サポートする Julia のバージョン範囲

12.11 テストの実行

パッケージのルートディレクトリで以下を実行する:

julia --project=. -e 'using Pkg; Pkg.instantiate(); Pkg.test()'

Test Summary: | Pass Total のような出力が表示されれば、テストは成功である。

12.12 なぜこれが重要か

  • ブレインストーミングにより、設計を先に考えることで手戻りを防げる
  • パッケージ構造により、コードが再利用可能でテスト可能になる
  • テストにより、バグを捕捉し、期待される動作を文書化できる

12.13 コードリーディング確認

以下を読め:

module Ising2D
export initialize_spins
# ...
end

以下の質問に答えよ:

  1. export は何をするか?
  2. 一部の関数がエクスポートされない理由は?
  3. 後でコードをリファクタリングするとき、テストがどう役立つか?
  4. プロジェクトとパッケージの共通点は何か、違いは何か?

12.14 追加テスト生成プロンプト

Julia のパッケージ構造とテストについて、
短いコードリーディング問題を3つ作成せよ。

以下に焦点を当てよ:
- module と export の意味
- パッケージ構造がなぜ役立つか
- テストが動作をどう文書化するか

まだ答えは出すな。
私が答えた後、回答を採点し、フォローアップ質問を1つ出せ。

12.15 最低限のチェックリスト

  • アクティブ化した環境で using Ising2D が動作する
  • Pkg.test() が通る
  • 変更をコミット&プッシュできる

12.16 発展: CI による自動テスト

12.16.1 CI と GitHub Actions とは?

CI(継続的インテグレーション)とは、コードがプッシュされるたびに自動的にテストやビルドを実行することである。GitHub では一般的に GitHub Actions で行う。

  • .github/workflows/ 以下に YAML ファイルを置くと、プッシュやプルリクエストでジョブが実行される
  • テストが通るとグリーンチェック、失敗するとレッドクロスが表示される
  • 「自分のマシンでは動くのに」問題を防げる

12.16.2 LLM に CI ワークフローを作ってもらう

GitHub CLI(gh)を使えば、リポジトリへのワークフローファイルの追加も AI に任せられる。

プロンプト例:

このリポジトリに GitHub Actions の CI ワークフローを追加せよ。
プッシュのたびに Julia 1.10 で Pkg.test() が実行されるように
.github/workflows/ci.yml を書け。

生成される YAML は通常以下のようになる:

name: CI
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: julia-actions/setup-julia@v2
        with:
          version: "1.10"
      - uses: julia-actions/cache@v2
      - run: |
          julia --project=. -e 'using Pkg; Pkg.instantiate(); Pkg.test()'

YAML のすべての詳細を理解する必要はない。LLM が使える出発点を生成でき、コミット&プッシュすれば GitHub が自動でテストを実行してくれることを知っていれば十分である。

12.17 スキップ条件

以下がすでにできるなら、このステップはスキップできる:

  • 実装前のブレインストーミングがなぜ役立つか説明できる
  • 最小限の Julia パッケージ構造を作成できる
  • Julia の Test モジュールを使って基本的なテストを書ける
  • エクスポートされた関数と非公開関数の違いを説明できる