testing について
Turso のコントリビューター向けに、テストの選び方、作成方法、実行方法を説明するガイドです。推奨される`.sqltest`に加え、移行が進む TCL テスト、Rust 統合テスト、ファズテストを扱います。
主な機能
- 01テスト種別の選択ガイド
- 02`.sqltest`の作成と変換
- 03TCL と Rust の統合テスト
- 04境界ケースを探すファズテスト
- 05アサーションと実行コマンド
活用シーン
- 01SQL 互換性テストの追加
- 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 クレートを優先してください。豊富なアサーションが用意されており、
テストの可読性が向上します。簡単な例をいくつか示します。
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"]);
主なルール
- 機能に変更を加える場合は、必ずテストを追加してください
- 変更を加えない状態ではテストが失敗し、変更後は成功する必要があります
- インメモリ DB を優先してください:
: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。警告:非常に詳細な出力です。