关于 testing
这份 Turso 测试指南面向项目贡献者,介绍测试类型的选择、编写方法和运行方式。它说明了优先采用的 `.sqltest`,以及逐步淘汰中的 TCL 测试、Rust 集成测试和模糊测试。
主要功能
- 01测试类型选择指南
- 02`.sqltest` 编写与转换
- 03TCL 与 Rust 集成测试
- 04用于发现边界情况的模糊测试
- 05断言方法与测试运行命令
适用场景
- 01补充 SQL 兼容性测试
- 02为复杂行为编写回归测试
- 03通过模糊测试发现边界情况
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ tursodatabase/ turso --skill testing安装后在 Claude Code 中输入/testing确认已加载。
详细介绍
测试指南
测试类型及适用场景
| 类型 | 位置 | 适用场景 |
|---|---|---|
.sqltest | sqlite/conformance/sqlite-sqltests/ | SQL 兼容性。新测试优先使用 |
TCL .test | testing/ | 旧版 SQL 兼容性测试(正在逐步淘汰) |
| Rust 集成测试 | tests/integration/ | 回归测试、复杂场景 |
| 模糊测试 | tests/fuzz/ | 复杂功能、发现边界情况 |
注意: TCL 测试正在逐步淘汰,改用 sqlite/conformance/ 中的 .sqltest 测试套件。.sqltest 格式可以让同一组测试用例在多个后端(CLI、Rust 绑定等)上运行。
运行测试
# Main test suite (TCL compat, sqlite3 compat, Python wrappers)
make test
# Single TCL test
make test-single TEST=select.test
# SQL test runner
make -C sqlite/conformance run-cli
# OR
cargo run -p sqltest -- run <test-file or directory>
# Rust unit/integration tests (full workspace)
cargo test
编写测试
.sqltest(优先使用)
@database :default:
test example-addition {
SELECT 1 + 1;
}
expect {
2
}
test example-multiple-rows {
SELECT id, name FROM users WHERE id < 3;
}
expect {
1|alice
2|bob
}
位置:sqlite/conformance/sqlite-sqltests/*.sqltest
必须先使用测试运行器的 convert 命令开始转换 TCL 测试(例如 cargo run -- convert <TCL_test_path> -o <out_dir>)。转换结果并不总是准确,但能处理大多数测试。如果转换时出现警告,你需要手动补全缺失内容(例如手动展开 for each 循环)。然后,运行 make -C sqlite/conformance run-rust 验证测试是否有效;如果转换结果有误,则调整测试输出。此外,TCL 测试使用硬编码数据库,而 .sqltest 会使用不同的种子生成数据库,因此你可能需要修改预期测试结果,使之与新的数据库查询输出一致。避免修改测试中的 SQL 语句,只修改预期结果。
TCL
do_execsql_test_on_specific_db {:memory:} test-name {
SELECT 1 + 1;
} {2}
位置:testing/*.test
Rust 集成测试
// tests/integration/test_foo.rs
#[test]
fn test_something() {
let conn = Connection::open_in_memory().unwrap();
// ...
}
断言
除了最简单的断言外,优先使用 asserting crate。它提供了丰富的断言,能让测试更易读。以下是一些简单示例:
use asserting::prelude::*;
assert_that!(conn.execute("SELECT * FROM t1;")).is_err();
assert_that_code!(|| parse(bad_input)).panics_with_message("unexpected token");
assert_that!(&rows)
.has_length(3)
.any_satisfies(|r| r
.name == "bob")
.first_element_ref()
.is_equal_to(&Row { id: 1, name: "alice".into() });
assert_that!(&header)
.named("page 2 header")
.satisfies_with_message("be a leaf table page", |h| h[0] == 0x0d);
// soft assertions: mark the test as failed but don't stop
verify_that!(&plan)
.starts_with("SEARCH")
.contains("USING INDEX")
.soft_panic();
crate::assertions 为结果行提供了 row!,还提供了 column 和查询计划断言:
use crate::assertions::{AssertColumn, AssertQueryPlan, Cell, NULL};
assert_that!(limbo_exec_rows(&conn, "SELECT id, name FROM t ORDER BY id"))
.is_equal_to(vec![row![1, "alice"], row![2, NULL]]);
assert_that!(limbo_exec_rows(&conn, "SELECT id FROM t WHERE id = 1"))
.single_element()
.is_equal_to(row![1]);
assert_that!(limbo_exec_rows(&conn, "SELECT id, name FROM t"))
.column(1)
.contains(Cell::from("alice"));
assert_that!(limbo_exec_rows(&conn, "EXPLAIN QUERY PLAN SELECT id FROM t WHERE name = 'a'"))
.uses_index("idx_name")
.searches_table("t")
.has_table_access_order(["t"]);
重要规则
- 每项功能变更都需要测试
- 测试必须在没有变更时失败,在有变更时通过
- 优先使用内存数据库:
:memory:(sqltest)或{:memory:}(TCL) - 不要自创测试格式。遵循现有模式
- 尽可能先编写测试
测试数据库架构
testing/system/testing.db 包含 users 和 products 表。架构详见 docs/testing.md。
测试期间的日志记录
RUST_LOG=none,turso_core=trace make test
输出位置:testing/system/test.log。警告:日志非常详细。