moonbit-extract-spec-test について
moonbit-extract-spec-testは、既存実装からAPI仕様と仕様駆動テストを作成するMoonBit開発者向けスキルです。有効・無効入力や境界ケースを扱い、仕様とテストの検証手順も示します。
主な機能
- 01公開APIとエラー型の整理
- 02宣言形式の仕様ファイル生成
- 03有効・無効ケース別のテスト整理
- 04境界・回帰・統合ケースの網羅
- 05チェックによる仕様とテストの検証
活用シーン
- 01既存のMoonBitパッケージからAPI契約を抽出
- 02実装に仕様駆動テストを追加
- 03レガシーコードを仕様駆動のワークフローへ移行
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ moonbitlang/ skills --skill moonbit-extract-spec-testインストール後、Claude Code で/moonbit-extract-spec-testを入力して読み込まれたか確認します。
詳細
MoonBit 仕様とテストの抽出
概要
既存の MoonBit 実装から、形式的な API コントラクト(<pkg>_spec.mbt)と包括的なテストスイートをリバースエンジニアリングします。これにより、すでに記述されたコードに対する仕様駆動テストが可能になります。
使用するタイミング
- ユーザーが「既存の実装から仕様を抽出して」と依頼したとき
- ユーザーが「パッケージの仕様駆動テストを生成して」と求めたとき
- ユーザーが「コードから形式的な API 仕様を作成して」と依頼したとき
- レガシーコードを仕様駆動開発のワークフローに移行するとき
- 既存の公開 API の範囲を文書化してテストする必要があるとき
ワークフロー
1. 既存の実装を分析する
公開 API の範囲を特定します:
- すべての
pubおよびpub(all)の型、関数、トレイトを一覧にします - エラー型(サブエラー)とそのバリアントを記録します
- メソッドシグネチャとトレイト実装を文書化します
- 主要なエントリーポイントと中核機能を特定します
テストのパターンを調べます(テストが存在する場合):
- 既存のテストファイル(
*_test.mbt、*_wbtest.mbt)を探します - テストカバレッジの不足箇所を特定します
- 一般的なテストシナリオを記録します
2. 仕様ファイル(<pkg>_spec.mbt)を生成する
形式的な API コントラクトを作成します:
- すべての公開関数/型に
declareキーワードを使用します - 実装の型シグネチャを正確に保ちます
- テスト可能にするため、エラー型に
derive(Show, Eq, ToJson)を含めます - 元のコードにあるドキュメントコメントを追加します
declareを使った宣言には本体を含めません- テスト内で構築する必要がある型には
pub(all)を使用します - 不透明型には
pubを使用します
仕様の構成例:
///|
/// Error type for parsing operations
pub(all) suberror ParseError {
InvalidFormat(String)
UnexpectedEof
} derive(Show, Eq, ToJson)
///|
/// Main data type
declare pub(all) type Toml
///|
/// Convert to test JSON format
declare pub fn Toml::to_test_json(self : Toml) -> Json
///|
/// Parse TOML from string
declare pub fn parse(input : StringView) -> Result[Toml, ParseError]
3. テストケースを抽出して整理する
妥当性(正常系とエラー系)に基づいてテストを分類します:
有効なテスト(<pkg>_valid_test.mbt):
- 正常系:中核機能を実行する有効な入力
- エッジケース:境界サイズ、空の入力、最大のネスト
- 互換性:実世界のデータや主要な実装と一致する動作
- リグレッション:以前の作業で修正されたバグや既知の落とし穴
無効なテスト(<pkg>_invalid_test.mbt):
- エラー系:無効な入力と、必要とされるエラー動作
- 曖昧さ:仕様が複数の解釈を許す入力(テストによって選択した解釈を確定します)
- 不正な入力:構文エラー、型の不一致、制約違反
4. 仕様駆動テストを記述する
有効な入力のテスト:
///|
test "valid/category/test-name" {
let toml_text =
#|key = "value"
#|
let toml = match @pkg.parse(toml_text) {
Ok(toml) => toml
Err(error) => fail("Failed to parse: " + error.to_string())
}
json_inspect(toml.to_test_json(), content={
"key": { "type": "string", "value": "value" }
})
}
無効な入力のテスト:
///|
test "invalid/category/test-name" {
let toml_text =
#|invalid syntax here
#|
match @pkg.parse(toml_text) {
Ok(toml) => {
let json = toml.to_test_json()
fail("Expected parse to fail, got \{json.stringify(indent=2)}")
}
Err(_) => ()
}
}
5. 検証する
仕様の型チェック:
moon check
テストを実行する:
moon test # Run all tests
moon test -u # Update snapshot tests
カバレッジを確認する:
- すべての公開 API 関数にテストがあることを確認します
- 未テストのエラー経路がないか確認します
- エッジケースが網羅されていることを確認します
重要な原則
仕様ファイルのルール
- 宣言のみのスタブ:すべての公開 API に
declareキーワードを使用します(本体は不要です) - 正確なシグネチャ:実装の型シグネチャと正確に一致させます
- エラー処理:適切な derive を指定して、すべてのエラー型を含めます
- ドキュメント:明確さのためにドキュメントコメントを保持または追加します
- テストヘルパー:検査用の
to_test_json()などの変換関数を含めます
テストの整理
- ブラックボックステスト:テストでは公開 API のみを使用します(
@pkg.function経由で呼び出します) - スナップショットテスト:複雑な値には
json_inspect()を優先します - 明確な命名:「valid/arrays/nested」や「invalid/keys/empty」のような説明的なテスト名を使用します
- 分類:妥当性に基づいて整理します(正常系は
<pkg>_valid_test.mbt、エラー系は<pkg>_invalid_test.mbt) - ブロックの区切り:
///|を使ってテストブロックを区切ります
テストカバレッジの目標
- 肯定的なケース:期待どおりの出力を生成する有効な入力
- 否定的なケース:適切なエラーを生成する無効な入力
- エッジケース:境界、空の入力、特殊な値
- 統合:複数の機能が連携して動作するケース
- API カバレッジ 80~90% を目指します
抽出例
実装から
pub type Config
pub fn Config::parse(text : String) -> Result[Config, String] {
// implementation
}
pub fn Config::get_value(self : Config, key : String) -> String? {
// implementation
}
仕様へ
///|
declare pub type Config
///|
declare pub fn Config::parse(text : String) -> Result[Config, String]
///|
declare pub fn Config::get_value(self : Config, key : String) -> String?
テストへ
///|
test "parse valid config" {
let result = try? Config::parse("key=value")
match result {
Ok(cfg) => {
inspect(cfg.get_value("key"), content="Some(\"value\")")
}
Err(e) => fail("Parse failed: \{e}")
}
}
///|
test "parse invalid config returns error" {
let result = try? Config::parse("invalid")
match result {
Ok(_) => fail("Should have failed to parse")
Err(_) => ()
}
}
ヒント
- シンプルに始める:コア機能から始め、段階的に複雑さを加えていきます
- 既存のテストを活用する:テストが存在する場合は、移行して拡張します
- 繰り返し確認する:型エラーを早期に検出できるよう、
moon checkを頻繁に実行します - スナップショットを更新する:出力形式が変わった場合は、
moon test -uを使用します - 理由を記録する:自明でないテストケースには、その理由を説明するコメントを追加します
よくあるパターン
Result 型を使ったテスト
let result : Result[T, Error] = try? function_call(...)
match result {
Ok(value) => inspect(value, content="...")
Err(e) => fail("Unexpected error: \{e}")
}
失敗が想定されるケースのテスト
match try? function_call(...) {
Ok(v) => fail("Expected error, got \{v}")
Err(_) => () // Success - error was expected
}
テスト用 JSON 形式の使用
json_inspect(value.to_test_json(), content={
"field": { "type": "integer", "value": "42" }
})
コメント
Markdown 対応