mcpField
M
moonbit-extract-spec-test

moonbit-extract-spec-test

github.com/moonbitlangテスト・品質34256 回インストール6 週間前に更新
リポジトリ

moonbit-extract-spec-test について

moonbit-extract-spec-testは、既存実装からAPI仕様と仕様駆動テストを作成するMoonBit開発者向けスキルです。有効・無効入力や境界ケースを扱い、仕様とテストの検証手順も示します。

主な機能

  1. 01公開APIとエラー型の整理
  2. 02宣言形式の仕様ファイル生成
  3. 03有効・無効ケース別のテスト整理
  4. 04境界・回帰・統合ケースの網羅
  5. 05チェックによる仕様とテストの検証

活用シーン

  1. 01既存のMoonBitパッケージからAPI契約を抽出
  2. 02実装に仕様駆動テストを追加
  3. 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 関数にテストがあることを確認します
  • 未テストのエラー経路がないか確認します
  • エッジケースが網羅されていることを確認します

重要な原則

仕様ファイルのルール
  1. 宣言のみのスタブ:すべての公開 API に declare キーワードを使用します(本体は不要です)
  2. 正確なシグネチャ:実装の型シグネチャと正確に一致させます
  3. エラー処理:適切な derive を指定して、すべてのエラー型を含めます
  4. ドキュメント:明確さのためにドキュメントコメントを保持または追加します
  5. テストヘルパー:検査用の to_test_json() などの変換関数を含めます
テストの整理
  1. ブラックボックステスト:テストでは公開 API のみを使用します(@pkg.function 経由で呼び出します)
  2. スナップショットテスト:複雑な値には json_inspect() を優先します
  3. 明確な命名:「valid/arrays/nested」や「invalid/keys/empty」のような説明的なテスト名を使用します
  4. 分類:妥当性に基づいて整理します(正常系は <pkg>_valid_test.mbt、エラー系は <pkg>_invalid_test.mbt)
  5. ブロックの区切り:///| を使ってテストブロックを区切ります
テストカバレッジの目標
  • 肯定的なケース:期待どおりの出力を生成する有効な入力
  • 否定的なケース:適切なエラーを生成する無効な入力
  • エッジケース:境界、空の入力、特殊な値
  • 統合:複数の機能が連携して動作するケース
  • 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" }
})

コメント