TST - Document View

# TST Specification

Generated at 2026-03-09 22:49:07

## 1.0 序文 TST025

本ドキュメントは、『beautyspot』のテスト計画および検証内容を定義する。

## 2.0 詳細仕様 TST026

各機能が仕様通りに動作することを確認するための検証観点およびテストシナリオを定義する。

2.1 mark デコレータテスト TST001
CACHE

## 目的

`@spot.mark()` はユーザーが最も頻繁に使う公開APIであり、キャッシュの正確性・透過性・拡張性の基盤となる。デコレーションによって元の関数の挙動やメタデータが損なわれると、ユーザーコードのデバッグや型チェックに支障をきたすため、ラッパーの透過性を厳密に検証する。

## 検証観点

- 正常系: 同一引数での2回目呼び出しでキャッシュヒットし、元の関数が再実行されないこと
- Blobモード: `save_blob=True` 指定時にDB直接保存ではなくBlobストレージ経由で保存されること
- ラッパー透過性: デコレーション後も `__name__`, `__doc__`, `inspect.signature` が元の関数と一致すること
- シリアライザ差し替え: `serializer=` パラメータでデフォルトの `MsgpackSerializer` をカスタム実装に置換できること
- キャッシュ無効化: `version=` パラメータを変更するとキャッシュキーが変わり、既存キャッシュがヒットしなくなること

2.2 cached_run テスト TST002
CACHE

## 目的

`cached_run` はデコレータを直接付与できない外部ライブラリ関数にキャッシュを適用する唯一の手段であり、このAPIの不具合はサードパーティ統合シナリオ全体に影響する。戻り値の型が関数の数で変わるスマートリターン仕様は、誤実装すると型安全性を損なうため重点的に検証する。

## 検証観点

- 単一関数: 1つの関数を渡した場合、結果がそのまま（タプルでなく）返ること
- 複数関数: 2つ以上の関数を渡した場合、結果がタプルで返ること
- バリデーション: 関数を0個渡した場合に `ValidationError` が送出されること
- 型推論: mypy / pyright で戻り値の型が正しく推論されること
- キャッシュ動作: コンテキスト内で同一引数の再呼び出しがキャッシュヒットすること

2.3 同期・非同期サポートテスト TST003
CACHE

## 目的

beautyspot は同期・非同期の両方の関数を透過的にキャッシュするが、asyncio 固有のイベントループ制約（スレッドをまたぐコルーチン実行、例外伝播のタイミング差異）により、同期版とは異なる不具合が発生しやすい。非同期パスの堅牢性を独立して検証する。

## 検証観点

- 自動判定: `inspect.iscoroutinefunction()` により同期/非同期が正しく識別され、適切なラッパーが適用されること
- 例外伝播: async 関数内で送出された例外が、await 側に正しく伝播し、キャッシュに保存されないこと
- 非同期 thundering herd: 同一キーへの複数の concurrent な `await` が1回の実行結果を共有すること
- 非同期保存: `save_sync=False` 時に `_BackgroundLoop` 経由でキャッシュ保存が非同期実行されること

2.4 SHA-256 キャッシュキーテスト TST004
KEY

## 目的

キャッシュキーはキャッシュの同一性判定の基盤であり、ハッシュが不安定だとキャッシュミスの頻発（性能劣化）や誤ヒット（データ不整合）を引き起こす。Python バージョンやプラットフォームに依存しないキー生成の安定性を保証する。

## 検証観点

- 決定性: 同一の `func_identifier:input_id[:version]` 入力に対して、複数回の呼び出しで常に同じ SHA-256 ハッシュが生成されること
- 一意性: 異なる引数から異なるキーが生成されること（衝突しないこと）
- 構造: キーが `module.qualname` 形式の func_identifier を含む正しい構造であること
- version 反映: version 文字列がキーに含まれ、version 変更でキーが変わること

2.5 再帰的正規化テスト TST005
KEY

## 目的

`canonicalize()` は引数の型ごとに正規化ルールを適用し、論理的に同値な入力が同じキャッシュキーを生成することを保証する。正規化の不備は「同じ入力なのにキャッシュミス」または「異なる入力なのに誤ヒット」という致命的なバグに直結する。

## 検証観点

- dict の順序非依存: `{"a": 1, "b": 2}` と `{"b": 2, "a": 1}` が同一キーを生成すること
- 型タグによる区別: `[1, 2]` (list) と `(1, 2)` (tuple) が異なるキーを生成すること（型タグ付き正規化）
- set/frozenset: 要素の挿入順序に依存しないこと
- deque/defaultdict/OrderedDict: コレクション型ごとに正しく正規化されること
- Enum: 値ではなく `型名.メンバー名` で正規化されること
- numpy 配列: `shape + dtype + tobytes` による正規化で、同値配列が同一キーを生成すること
- Pydantic モデル: モデルの dict 表現で正規化されること
- bool vs int の区別: `True` と `1` が異なるキーを生成すること（Python では `True == 1` だが区別が必要）
- 再帰的処理: ネストされたデータ構造（dict の中の list の中の set 等）が正しく正規化されること

2.6 KeyGenPolicy テスト TST006
KEY

## 目的

`KeyGenPolicy` は引数ごとのキー生成戦略をカスタマイズする機構であり、ログレベルやデバッグフラグのような非決定的引数をキーから除外したり、ファイルパスの代わりにファイル内容でキーを生成するユースケースを支える。戦略の誤適用はキャッシュの正確性を根本から損なう。

## 検証観点

- KeyGen.ignore(): 指定された引数がキャッシュキーの計算から除外され、その引数の値を変えてもキャッシュヒットすること
- KeyGen.map() + FILE_CONTENT: ファイルパス引数に対し、パス文字列ではなくファイル内容のハッシュがキーに使われること。同一内容の別パスファイルが同一キーを生成すること
- KeyGen.path_stat(): ファイルの mtime/size に基づくキー生成が機能すること
- bind() 連携: `KeyGenPolicy.bind(func)` が関数シグネチャ（位置引数・キーワード引数・デフォルト値）を正しく解釈すること
- @mark 統合: `@spot.mark(keygen=KeyGen.ignore("verbose"))` のようにデコレータ経由で戦略が正しく適用されること

2.7 SQLiteTaskDB テスト TST007
DB

## 目的

`SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす。WALモード・専用ライタースレッドという独自アーキテクチャの信頼性を保証する。

## 検証観点

- スキーマ初期化: 新規DBファイルに対してテーブルとインデックスが正しく作成されること。既存DBへの自動マイグレーション（content_type, version, result_data, func_identifier, expires_at カラム追加）が冪等に動作すること
- CRUD操作: タスクの作成・取得・更新・削除が正しく動作し、存在しないキーへのアクセスが適切にハンドルされること
- 読み取り接続リカバリ: スレッドローカルな読み取り接続がクローズされた場合に自動的に再作成され、接続リークが発生しないこと
- ライタースレッドのタイムアウト: 書き込みキューのタイムアウト時に適切なエラーが返り、ライタースレッドがデッドロックしないこと
- GC耐性: ガベージコレクション実行後もDBファイルが存続し、有効なタスクが失われないこと

2.8 DB プロトコル階層テスト TST008
DB

## 目的

`TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装（Redis、PostgreSQL 等）の差し替えを可能にする拡張ポイントである。インターフェース契約が不明確だと、カスタム実装が実行時に予期しないエラーを起こす。

## 検証観点

- インターフェース準拠: `TaskDBCore` の必須メソッド（save, load, delete 等）を実装したクラスが `isinstance` チェックをパスすること
- TaskDBBase 拡張: `TaskDBBase` がメンテナンス系メソッド（prune, stats 等）のデフォルト実装を提供し、サブクラスが必須メソッドのみの実装で動作すること
- デフォルト動作: メンテナンスメソッドのデフォルト実装が安全なno-op（空リスト返却等）であること
- カスタム実装の注入: `bs.Spot(db=CustomDB())` でプロトコル準拠のカスタムDBが正常に動作すること

2.9 LocalStorage テスト TST009
STORE

## 目的

`LocalStorage` はBlobデータのファイルシステム永続化を担い、並行書き込み時のデータ破損やパストラバーサルによるセキュリティ脆弱性が発生しうる。アトミック書き込みとセキュリティバリデーションの正確性を保証する。

## 検証観点

- ワークスペース初期化: `base_dir` が存在しない場合に自動作成され、`.gitignore` が配置されること
- アトミック書き込み: `tempfile.mkstemp` → `fsync` → `os.replace` のフローで、書き込み中のクラッシュや並行アクセスでファイルが破損しないこと
- 並行書き込み一貫性: 複数スレッドから同一キーへの同時書き込みで、最終状態が正しい完全なデータであること
- パストラバーサル防止: キーに `..` や `/` を含む場合に `ValidationError` が送出され、`base_dir` 外へのアクセスが不可能であること。`load()` でも `is_relative_to` 検証が機能すること
- 一時ファイルクリーンアップ: `clean_temp_files()` が猶予期間超過の `.spot_tmp` ファイルを削除し、猶予期間内のファイルは保持すること
- 冪等削除: `delete()` が存在しないキーに対してもエラーを送出しないこと

2.10 S3Storage テスト TST010
STORE

## 目的

`S3Storage` はクラウド環境での大規模Blob保存を担い、ネットワーク障害・認証エラー・バケット不在などオンプレミスにはない障害モードが存在する。boto3 オプショナル依存のガード処理も含め、クラウドストレージ統合の信頼性を検証する。

## 検証観点

- 基本CRUD: S3互換ストレージへの `save` / `load` / `delete` / `list_keys` が正しく動作すること
- オプショナル依存ガード: boto3 がインストールされていない環境で `import` 時にわかりやすいエラーメッセージが表示されること
- エラーハンドリング: バケット不在、権限不足、ネットワークエラー時に適切な例外が送出されること
- 注記: boto3 のモック戦略（moto 等）を使用したテストの実装を推奨

2.11 ストレージポリシーテスト TST011
STORE

## 目的

ストレージポリシーは「データをDB直接保存するか、Blobストレージに分離するか」を決定する戦略レイヤーである。閾値判定の誤りはDBの肥大化（性能劣化）や不要なBlob分離（オーバーヘッド増加）を招く。3種のポリシー実装がそれぞれの契約を正しく満たすことを検証する。

## 検証観点

- ThresholdStoragePolicy: 閾値未満のデータで `False`、閾値以上のデータで `True` を返すこと。境界値（ちょうど閾値サイズ）での挙動が明確であること
- WarningOnlyPolicy: 常に `False` を返しDB直接保存を選択すること。ただし閾値超過時に WARNING レベルのログが出力されること
- AlwaysBlobPolicy: データサイズに関係なく常に `True` を返すこと
- Spot 統合: `@spot.mark(save_blob=True/False)` の明示指定がポリシー判定より優先されること。`save_blob=None`（デフォルト）時にポリシーの `should_save_as_blob()` が呼ばれること

2.12 MsgpackSerializer テスト TST012
SERIAL

## 目的

`MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失（デシリアライズ不能）やサイレントなデータ劣化（精度低下等）に直結する。スレッドセーフ性の欠如はマルチスレッド環境での競合状態を引き起こす。

## 検証観点

- ラウンドトリップ: プリミティブ型（int, float, str, bytes, None, bool）と基本コレクション（list, dict）が `dumps` → `loads` で元の値と一致すること
- Pydantic モデル: Pydantic モデルが dict に変換されてシリアライズされ、デシリアライズ後に元のモデルが復元されること
- numpy 配列: ndarray の shape, dtype, 値が保持されること。float64 等の精度が劣化しないこと
- スレッドセーフ LRU キャッシュ: サブクラス解決のキャッシュがスレッドローカルであり、複数スレッドからの同時アクセスで競合しないこと
- ExtType 拡張: カスタム型が ExtType (code 0-127) で正しく登録・解決されること

2.13 カスタム型登録テスト TST013
SERIAL

## 目的

カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッシュ保存失敗を引き起こし、ユーザーの既存コードとの統合を阻害する。デコレータ方式と命令方式の両方の登録パスを検証する。

## 検証観点

- デコレータ登録: `@spot.register(code=N, encoder=..., decoder=...)` でクラスが登録され、そのインスタンスのシリアライズ/デシリアライズが正常に動作すること
- 命令的登録: `spot.register_type(MyClass, code=N, ...)` でも同等の登録が行われること
- Pydantic factory デコーダ: Pydantic モデルの `model_validate` をデコーダとして使用する場合に、バリデーション付きの復元が正しく動作すること
- ネスト構造: カスタム型を含む dict、カスタム型のリスト、カスタム型をフィールドに持つカスタム型など、複雑な入れ子構造が正しくエンコード/デコードされること
- code 衝突: 同一 code を重複登録した場合の挙動が明確であること

2.14 BackgroundLoop テスト TST014
BGIO

## 目的

`_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコルーチン受け渡しは競合状態やデッドロックが発生しやすく、シャットダウン時のタスク消失はデータロスに直結する。

## 検証観点

- 保存の正確性: `submit(coro)` で投入したコルーチンが正しく実行され、結果がDBとストレージに反映されること
- 直列化保証: 同一キーへの保存が投入順に処理され、最終状態が最後に投入されたデータであること
- ゾンビスレッド防止: シャットダウン後にアクティブタスクが `drain_timeout` 秒以内に完了すること。タイムアウト後は強制終了されること
- atexit 安全網: プロセス終了時に atexit ハンドラが発火し、未完了タスクの drain が試行されること
- 新規受付停止: シャットダウン後の `submit()` 呼び出しが受け付けられないこと

2.15 save_sync / flush / drain テスト TST015
BGIO

## 目的

`save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユーザーが制御する手段である。`save_sync=False` 使用時にデータが失われないことと、コンテキストマネージャによる確実なフラッシュを保証する。

## 検証観点

- save_sync=True（デフォルト）: キャッシュ保存が呼び出しスレッドで同期的に完了し、関数リターン時点でデータが永続化されていること
- save_sync=False: キャッシュ保存がバックグラウンドに委譲され、関数がレイテンシなしでリターンすること。保存が最終的に完了すること
- flush(): `spot.flush(timeout)` 呼び出しで全ペンディング保存が完了するまで待機すること。タイムアウト時の挙動が明確であること
- コンテキスト exit での drain: `with spot:` ブロック終了時に未完了の保存が自動的に drain されること
- コンテキストマネージャの再利用: 同一 spot インスタンスが複数の `with` ブロックで再利用でき、各ブロック終了時に適切に drain されること
- バックグラウンド保存失敗: 非同期保存が例外を送出した場合に `on_background_error` コールバックが呼ばれ、メインスレッドには影響しないこと

2.16 LifecyclePolicy テスト TST016
LIFE

## 目的

`LifecyclePolicy` はキャッシュデータの保持期間を関数名パターンで制御する。パターンマッチングの不備は、重要なキャッシュの意図しない削除（データロス）や不要なキャッシュの蓄積（ディスク圧迫）を引き起こす。

## 検証観点

- パターンマッチング: glob パターン（`"train_*"`, `"*.preprocess"` 等）がルールの `func_pattern` として正しく機能し、マッチした関数に対して指定の `Retention` が適用されること
- ルール優先順位: 複数ルールがマッチした場合に、リスト順（先勝ち or 後勝ち）でルールが決定されること
- resolve_with_fallback: 完全修飾名（`module.qualname`）でルールが見つからない場合に、短縮名（`qualname` のみ）でフォールバック検索が行われること
- デフォルト保持期間: どのルールにもマッチしない関数に対して `default_retention` で指定された保持期間（デフォルト: 30日）が適用されること
- 無期限保持の互換性: `default_retention=None` を指定した場合に無期限保持（`None`）が返されること

2.17 Retention テスト TST017
LIFE

## 目的

`Retention` はキャッシュの有効期間をユーザーフレンドリーな形式で指定する値オブジェクトである。パースの不備は意図しない保持期間（例: "7d" を 7秒と誤解釈）を招き、重要なキャッシュの早期削除やディスクの際限ない肥大化に繋がる。

## 検証観点

- 文字列パース: `"7d"` → 7日、`"12h"` → 12時間、`"30m"` → 30分、`"10s"` → 10秒として正しく解釈されること
- timedelta 入力: `timedelta(days=7)` がそのまま保持期間として受け入れられること
- 秒数入力: 整数・浮動小数点数が秒として解釈されること
- FOREVER シングルトン: `Retention.FOREVER` が無期限保持を表し、シングルトンであること（`is` 比較が成立）
- バリデーション: 負値（`"-1d"`）やゼロ（`"0s"`）が `ValidationError` を送出すること。不正な形式（`"abc"`, `""` 等）も拒否されること

2.18 TokenBucket テスト TST018
LIMIT

## 目的

`TokenBucket` はAPI呼び出しやリソースアクセスのレート制限を実現する。レートリミッターの不具合はAPIの過負荷（制限が甘い場合）やスループットの不必要な低下（制限が厳しすぎる場合）を招く。GCRA アルゴリズムのスムーズなレート制御を検証する。

## 検証観点

- 初期化バリデーション: rate ≤ 0 や capacity ≤ 0 などの不正パラメータが拒否されること
- max_cost 超過拒否: 1回のリクエストコストがバケット容量を超える場合に即座に拒否されること（無限待機にならないこと）
- GCRA 待機動作: バケットが空の状態でリクエストした場合に、トークン補充まで適切な時間だけ待機すること。待機時間がレートに基づいて正確であること
- バースト許容: 容量分のリクエストがバーストで処理でき、超過分からスロットリングが開始されること

2.19 Hook テスト TST019
HOOK

## 目的

Hook システムはキャッシュライフサイクルへのユーザー拡張ポイント（ロギング、メトリクス収集、監査等）であり、フック内の不具合がメインの関数実行に影響を与えないことが最重要の契約である。また `ThreadSafeHookBase` の自動ロック機構の正確性を保証する。

## 検証観点

- ライフサイクル呼び出し順: キャッシュミス時に `pre_execute` → 関数実行 → `on_cache_miss` の順でフックが呼ばれること。キャッシュヒット時に `pre_execute` → `on_cache_hit` の順で呼ばれること
- コンテキスト情報: 各フックに渡されるコンテキスト（`PreExecuteContext`, `CacheHitContext`, `CacheMissContext`）が正しい情報（func_name, args, result 等）を含むこと
- 例外隔離: フック内で例外が発生しても、関数の実行結果には影響せず、ERROR ログにのみ記録されること
- 状態保持: フックインスタンスが呼び出し間で状態（カウンタ等）を保持できること
- ThreadSafeHookBase: `__init_subclass__` によりユーザー定義メソッドが自動的に RLock でラップされ、複数スレッドからの同時呼び出しで競合状態が発生しないこと

2.20 MaintenanceService テスト TST020
MAINT

## 目的

`MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用者の誤判断を招き、メンテナンス操作の不備はデータの意図しない削除に繋がる。

## 検証観点

- タスク詳細取得: 存在するタスクIDに対して正しいメタデータ（func_name, input_key, result, created_at 等）が返却されること
- 存在しないタスク: 不在のタスクIDに対して `None` が返却され、例外が送出されないこと
- 統計情報: キャッシュの件数、サイズ、関数ごとの分布など統計情報が正確に算出されること
- クリア操作: 指定条件に合致するタスクのみが削除され、他のタスクが影響を受けないこと
- GC 操作: 有効期限切れタスクの削除と孤立Blobファイルのクリーンアップが正しく連動すること

2.21 CLI テスト TST021
CLI

## 目的

CLI はユーザーがキャッシュの状況確認・管理を行う主要インターフェースであり、コマンドの不具合はユーザー体験と運用効率に直接影響する。`CliRunner` によるコマンド実行と出力の正確性を E2E で検証する。

## 検証観点

- list コマンド: キャッシュ済みタスクの一覧が正しく表示されること。DBが空の場合に適切なメッセージが表示されること
- show コマンド: 指定タスクの詳細情報（func_name, input_key, created_at, result サイズ等）が正しく表示されること
- stats コマンド: キャッシュ統計（総件数、関数別件数、ストレージ使用量等）が正確に表示されること
- clear コマンド: 確認プロンプト後にキャッシュが削除されること。`--force` フラグでプロンプトがスキップされること
- gc / prune コマンド: 有効期限切れタスクの削除と孤立Blobの清掃が正しく実行されること
- E2E パイプライン: `@mark` でキャッシュ保存 → `beautyspot list` で確認 → `beautyspot clear` で削除、という一連のワークフローが正常に動作すること
- エラー時の終了コード: 不正な引数やDB不在時に非ゼロの終了コードが返ること

2.22 ファクトリ関数・DI テスト TST022
DI

## 目的

`bs.Spot()` ファクトリ関数は全コンポーネントの DI 配線を担う唯一のパブリックエントリポイントであり、デフォルト構成の正確性とカスタム実装の差し替え可能性がシステム全体の柔軟性と正確性を決定する。

## 検証観点

- デフォルト構成: 引数未指定時に `SQLiteTaskDB`, `LocalStorage`, `MsgpackSerializer`, `TokenBucket`, `WarningOnlyPolicy` が自動注入されること。ワークスペースディレクトリ（`.beautyspot/`）が自動作成されること
- カスタム DB 注入: `TaskDBBase` / `TaskDBCore` プロトコル準拠のカスタムDBを `db=` で注入し、正常にキャッシュ操作が行えること
- カスタム Storage 注入: `BlobStorageBase` 準拠のモック Storage を `storage_backend=` で注入し、Blob保存が委譲されること
- カスタム Serializer 注入: `SerializerProtocol` 準拠のカスタム Serializer を注入し、シリアライズ処理が差し替わること
- 型推論: `bs.Spot()` の戻り値型が pyright / mypy で正しく推論されること
- Protocol 不準拠の拒否: 必須メソッドを欠くオブジェクトを注入した場合に型チェックエラーまたは実行時エラーが発生すること

2.23 Thundering Herd テスト TST023
HERD

## 目的

Thundering Herd Protection は、同一キーへの並行リクエストが一斉に関数を実行する「Thundering Herd」問題を防ぐ。この保護が不完全だと、重い計算やAPI呼び出しが不要に多重実行され、リソース浪費やレートリミット超過を招く。並行性バグはテスト困難であるため、複数の観点から網羅的に検証する。

## 検証観点

- 同期並行: 複数スレッドが同一キーで同時にキャッシュミスした場合、関数が1回のみ実行され、全スレッドが同じ結果を受け取ること
- 非同期並行: 複数の `asyncio.Task` が同一キーで同時にキャッシュミスした場合も、関数が1回のみ実行されること
- 例外伝播: 実行者（executor）の関数が例外を送出した場合、全待機スレッド/タスクに同じ例外が伝播すること。例外後に `_inflight` エントリがクリーンアップされること
- タイムアウト: 待機が `HERD_TIMEOUT`（300秒）を超過した場合にタイムアウト処理が発動し、無限待機にならないこと
- リトライ: `HERD_MAX_RETRIES`（3回）の範囲でリトライが行われ、上限超過で最終エラーが返ること
- CacheManager モック: `_inflight` 辞書の状態遷移（エントリ追加→結果セット→エントリ削除）が正しい順序で行われること

2.24 BlobStorageBase インターフェース契約テスト TST024
STORE

## 目的

`BlobStorageBase` は全ストレージバックエンド（LocalStorage, S3Storage, サードパーティ）が 準拠すべき抽象契約を定義する。この契約が正しく機能しないと、キャッシュデータの 保存・復元・削除・メンテナンス（GC）の全てが破綻する。 `LocalStorage` を具象実装として使い、インターフェース契約を網羅的に検証する。

## 検証観点

- save 基本動作: データを永続化し、一意な識別子（文字列）を返すこと
- ReadableBuffer 互換: `bytes`, `bytearray`, `memoryview` の全てを受け入れること（ゼロコピー書き込み）
- save 上書き: 同一キーに再保存するとデータが更新されること
- 空データ: 空バイト (`b""`) を正常に保存・復元できること
- load ラウンドトリップ: save した内容を load で完全に復元できること
- load 欠損時エラー: 存在しないロケーションへの load は `CacheCorruptedError` を送出すること
- delete 基本動作: 保存済みデータを削除し、以後の load がエラーになること
- delete 冪等性: 存在しないロケーションや二重削除でエラーにならないこと（冪等）
- list_keys 空: データ未保存時は空イテレータを返すこと
- list_keys 列挙: save したアイテムが list_keys に含まれること
- list_keys 削除後: delete 後は list_keys に含まれないこと
- get_mtime 正確性: POSIX タイムスタンプ（float）を返し、保存直後は現在時刻に近いこと
- get_mtime 欠損時エラー: 存在しないロケーションでエラーを送出すること

Ref: tests/integration/storage/test_blob_storage_base.py