用途
MoonBit Extract Spec & Test 用于从已有的 MoonBit 实现中反向工程出正式的 API 契约(<pkg>_spec.mbt)以及完整的测试套件,从而为已经写好的代码引入规范驱动的测试流程。
适用场景包括:
- 用户要求“从已有实现中提取规范”
- 用户希望“为某个包生成规范驱动的测试”
- 用户要求“从代码创建正式 API 规范”
- 将遗留代码转换为规范驱动开发工作流
- 需要为现有公开 API 表面编写文档并进行测试
主要能力
1. 分析已有实现
识别公开 API 表面:
- 列出所有 pub 和 pub(all) 类型、函数、trait
- 记录错误类型(suberrors)及其变体
- 记录方法签名与 trait 实现
- 识别关键入口点与核心功能
检查测试模式(如果已有测试):
- 查找现有测试文件(_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):
- 正常路径:能触发核心功能的有效输入
- 边界情况:边界大小、空输入、最大嵌套
- 兼容性:与真实数据或主流实现一致的行为
- 回归:此前修复的 bug 或已知陷阱
无效测试(<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 为错误路径)
- 块分隔:使用 ///| 分隔测试块
测试覆盖目标
- 正向用例:有效输入产生预期输出
- 负向用例:无效输入产生适当错误
- 边界情况:边界、空输入、特殊值
- 集成:功能协同工作
- 目标为 80-90% API 覆盖率
提取示例
从实现:
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" }
})
限制
资料中未提供关于收费、价格、平台支持范围或具体效果保证的信息。该技能面向 MoonBit 语言项目,依赖 moon 工具链(moon check、moon test)。