Skip to content

Latest commit

 

History

History
662 lines (478 loc) · 23.6 KB

File metadata and controls

662 lines (478 loc) · 23.6 KB

EpisodicRAG Testing Guide

テストスイートのガイドドキュメント。

Table of Contents

Overview

Test Infrastructure

Writing Tests

Running Tests

CI/CD


Quick Start

# 全テスト実行
pytest scripts/test/ -v

# 単体テストのみ
pytest scripts/test/ -m unit

# 統合テストのみ
pytest scripts/test/ -m integration

# CI メイン job と同じ選択(壁時計テストを除外した決定論ゲート)
pytest scripts/test/ -m "not slow and not performance"

# CI performance job と同じ選択(壁時計テストのみ)
pytest scripts/test/ -m "slow or performance" --no-cov

# Property-based tests のみ
pytest scripts/test/ -m property

# CLI統合テストのみ [v4.0.0+]
pytest scripts/test/cli_integration_tests/ -m cli

Test Organization

Architecture Layers

テストはアプリケーションのアーキテクチャ層に対応して構成されています:

test/
├── conftest.py              # 共通フィクスチャ
├── test_helpers.py          # テストヘルパー
├── test_constants.py        # テスト用定数
├── domain_tests/            # 純粋なビジネスロジック (34 files)
│   └── test_*_properties.py # Property-based (5 files)
├── config_tests/            # Config層3層化対応 (14 files) [v4.0.0+]
│   ├── test_config_properties.py
│   └── …                    # 他は省略(代表例のみ)
├── application_tests/       # ユースケース (26 files)
│   ├── grand/               # GrandDigest関連
│   ├── shadow/              # Shadow関連(cascade_orchestrator含む)
│   │   ├── test_shadow_io_properties.py
│   │   ├── test_provisional_appender.py
│   │   └── …                # 他は省略(代表例のみ)
│   ├── finalize/            # Finalize処理
│   │   ├── validators/      # バリデータ
│   │   └── …                # 他は省略(代表例のみ)
│   ├── test_shadow_components.py  # CascadeComponents [v5.2.0+]
│   ├── test_cascade_properties.py
│   └── test_template_properties.py
├── infrastructure_tests/    # I/O操作 (14 files)
│   ├── config/              # PathValidatorChain [v4.1.0+]
│   ├── test_file_scanner_properties.py
│   └── test_json_repository_properties.py
├── interfaces_tests/        # エントリポイント (30 files)
│   ├── provisional/         # Provisional処理
│   └── test_auto_*.py       # digest_auto パッケージ内モジュールのテスト [v5.3.0+](本ディレクトリ直下にフラット配置)
├── integration_tests/       # E2Eシナリオ (14 files)
├── cli_integration_tests/   # CLI E2E (4 files) [v4.0.0+]
├── performance_tests/       # ベンチマーク (1 file)
└── tools_tests/             # 開発ツール (4 files) [v4.1.0+]

Test Summary

層別テストファイル一覧

主なテストファイル ファイル数
Domain test_validators.py, test_file_naming.py, test_level_registry.py, test_formatter_registry.py, test_types_imports.py, test_level_literals.py, test_constants.py, test_singleton_docs.py 34
Config test_config.py, test_path_resolver.py, test_threshold_provider.py, test_config_builder.py 14
Infrastructure test_json_repository.py, test_file_scanner.py, test_logging_config.py, test_path_validators.py, test_persistent_path.py, test_json_repository_types.py 14
Application test_shadow_*.py, test_grand_digest.py, test_cascade_orchestrator.py, test_persistence.py, test_shadow_components.py 26
Interfaces test_finalize_from_shadow.py, test_*_cli_*.py, test_setup_*.py, test_auto_*.py (digest_autoパッケージ対応 v5.2.0+), test_digest_auto_detection.py, test_cli_helpers.py, test_digest_readiness.py, test_digest_entry.py, test_encoding.py, test_auto_models.py, test_auto_report.py, test_path_resolver.py 30
Integration test_e2e_workflow.py, test_full_cascade.py, test_config_integration.py 14
CLI Integration test_digest_*_cli.py, test_workflow_cli.py 4
Performance test_benchmarks.py 1
Tools test_check_footer.py, test_link_checker.py, test_validate_json.py, test_bandit_integration.py 4
Property test_*_properties.py (全11ファイル、各層に分散) 11

📊 最新のテスト数: pytest --collect-only | tail -1 📁 ファイル数確認: find scripts/test -name "test_*.py" | wc -l

カバレッジ目標

カテゴリ 目標 現状
Domain層 90%+ Codecov参照
Application層 80%+ 同上
全体 80%+ ~92%(CI メイン job = not slow and not performance の選択分)

CI が計測するのはメイン job の選択分のみ(slow / performance は別 job で --no-cov)。 ローカルで全テストを走らせた場合の値はこれより数 pt 高くなる。閾値は --cov-fail-under=80

Test Markers

@pytest.mark.unit          # 純粋ロジック、<100ms、I/Oなし
@pytest.mark.integration   # ファイルI/O、複数コンポーネント
@pytest.mark.slow          # 1秒超(CI メイン job から除外)
@pytest.mark.property      # Hypothesis property-based tests
@pytest.mark.performance   # ベンチマーク(CI メイン job から除外)
@pytest.mark.cli           # CLI統合テスト(subprocess経由)[v4.0.0+]

Note: slow / performance壁時計に依存する検査の隔離マーカーです。CI のメイン job は -m "not slow and not performance" で両者を deselect し、専用の performance job (-m "slow or performance")だけが実行します(→ Continuous Integration)。 マーカーは pyproject.tomlmarkers に登録された 6 種のみ有効です(--strict-markers)。 未登録のマーカーを付けるとエラーになるため、追加時は pyproject.toml への登録が先です。


Fixture Dependency Map

graph TD
    A["conftest.py<br/>(Shared Fixtures)"]
    R["reset_all_singletons<br/>(autouse=True)"]

    A --> B["temp_plugin_env<br/>(function scope)"]
    A --> C["shared_plugin_env<br/>(module scope)"]
    A --> L["level_hierarchy"]
    A --> P["placeholder_manager"]

    B --> D["digest_config"]
    B --> E["config (alias)"]
    B --> M["mock_digest_config"]

    D --> F["times_tracker"]
    D --> G["shadow_manager"]
    D --> H["grand_digest_manager"]
    D --> I["file_detector"]

    J["template"] --> K["shadow_io"]
    B --> K

    style A fill:#e1f5ff,color:#000000
    style B fill:#fff9c4,color:#000000
    style D fill:#f3e5f5,color:#000000
    style R fill:#ffcdd2,color:#000000
Loading

Core Fixtures

temp_plugin_env (function scope)

隔離された一時ファイルシステムを提供。

def test_something(temp_plugin_env):
    config = DigestConfig()  # 環境変数経由で自動設定
    # テスト後に自動クリーンアップ

v5.3.0変更: EPISODICRAG_CONFIG_DIR 環境変数で永続化ディレクトリをテスト用にリダイレクトします。

Properties:

  • .plugin_root - Pluginルートディレクトリ
  • .loops_path - data/Loops ディレクトリ
  • .digests_path - data/Digests ディレクトリ
  • .essences_path - data/Essences ディレクトリ
  • .config_dir - .claude-plugin ディレクトリ
  • .persistent_config_dir - 永続化設定ディレクトリ(v5.2.0+)

shared_plugin_env (module scope)

モジュール内で共有される読み取り専用環境。

注意: このフィクスチャを使用するテストは環境を変更してはいけません。

sample_loop_files

5つのサンプルLoopファイルを含む環境を提供。

def test_with_loops(sample_loop_files):
    env, loop_files = sample_loop_files
    assert len(loop_files) == 5

Additional Fixtures [v4.0.0+]

reset_all_singletons (autouse=True)

テスト間の状態分離を保証する自動実行フィクスチャ。

リセット対象 [v5.2.0+ 更新]:

モジュール リセット関数 説明
level_registry reset_level_registry() レベル設定のシングルトン
file_naming reset_registry() ファイル命名用レジストリ参照
error_formatter reset_error_formatter() エラーフォーマッタのデフォルトインスタンス

手動リセット例:

from domain.level_registry import reset_level_registry
from domain.file_naming import reset_registry
from domain.error_formatter import reset_error_formatter

reset_level_registry()
reset_registry()
reset_error_formatter()

Note: 各シングルトンモジュールのdocstringにリセット方法が記載されています(v5.2.0+)。

mock_digest_config

パス情報のみを持つ軽量モックDigestConfig。

def test_with_mock(mock_digest_config):
    assert mock_digest_config.config_file.exists()

level_hierarchy

SSoT関数からレベル階層情報を取得。

placeholder_manager

PlaceholderManagerインスタンスを提供。


Adding New Tests

Unit Tests

@pytest.mark.unit
class TestFileNaming:
    def test_extract_loop_number_valid_format(self):
        result = extract_file_number("L00123_test.txt")
        assert result == ("L", 123)

    @pytest.mark.parametrize("input,expected", [
        ("L00001_test.txt", 1),
        ("L99999_test.txt", 99999),
    ])
    def test_extract_with_various_formats(self, input, expected):
        _, number = extract_file_number(input)
        assert number == expected

Integration Tests

@pytest.mark.integration
@pytest.mark.slow
class TestShadowUpdate:
    def test_update_adds_files_to_shadow(self, temp_plugin_env):
        # Arrange
        config = DigestConfig()  # 環境変数で設定済み
        manager = ShadowGrandDigestManager(config)

        # Act
        manager.update_shadow_for_new_loops()

        # Assert
        shadow_data = manager.get_shadow_digest_for_level("weekly")
        assert shadow_data is not None

Property-Based Tests

@pytest.mark.property
class TestFileNamingInvariants:
    @given(st.integers(min_value=1, max_value=99999))
    @settings(max_examples=500)
    def test_format_extract_roundtrip(self, number):
        """フォーマット→抽出のラウンドトリップ不変条件"""
        formatted = format_digest_number("weekly", number)
        result = extract_file_number(formatted)
        assert result[1] == number

Test Naming Convention

  • test_<module>.py - 単体テスト
  • test_e2e_<scenario>.py - E2Eワークフローテスト
  • test_<component>_properties.py - Property-based tests
  • test_concurrent_<aspect>.py - 並行処理テスト

Property-Based Tests

Hypothesis を使用したプロパティベーステスト。 不変条件(invariants)と境界条件を網羅的にテスト。

ファイル一覧 (11ファイル)

ファイル テスト数 対象
Domain test_constants_properties.py 14 プレースホルダ生成
Domain test_file_naming_properties.py 10 ファイル命名規則
Domain test_text_utils_properties.py 14 テキスト抽出
Domain test_validation_helpers_properties.py 14 バリデーションヘルパー
Domain test_validators_properties.py 16 型バリデータ
Config test_config_properties.py 11 設定読込
Application test_cascade_properties.py 12 カスケード処理
Application test_template_properties.py 13 テンプレート生成
Application test_shadow_io_properties.py 9 Shadow I/O
Infrastructure test_file_scanner_properties.py 14 ファイルスキャン
Infrastructure test_json_repository_properties.py 8 JSON永続化

合計: 約125テストケース (Hypothesisにより各テストで100+の入力を生成)

実行方法

# Property-based tests のみ実行
pytest scripts/test/ -m property -v

# CI用プロファイル(500 examples)
HYPOTHESIS_PROFILE=ci pytest scripts/test/ -m property

# 高速チェック(20 examples)
HYPOTHESIS_PROFILE=quick pytest scripts/test/ -m property

CLI Integration Tests [v4.0.0+]

v4.0.0で追加されたCLI E2Eテストフレームワーク。subprocess経由で実際のCLIコマンドを実行してテストします。

ディレクトリ構成

cli_integration_tests/
├── __init__.py
├── conftest.py              # CLI専用フィクスチャ
├── cli_runner.py            # CLIRunner ヘルパークラス
├── test_digest_setup_cli.py
├── test_digest_config_cli.py
├── test_digest_auto_cli.py
└── test_workflow_cli.py     # ワークフロー統合テスト [v4.1.0+]

CLIRunner

subprocess経由でCLIコマンドを実行するヘルパークラス:

@pytest.mark.cli
def test_setup_check(cli_runner):
    result = cli_runner.run_digest_setup("check")
    result.assert_success()
    result.assert_json_status("not_configured")

CLI専用フィクスチャ

フィクスチャ 説明
cli_temp_dir 一時ディレクトリ
cli_plugin_root 最小構造のプラグインルート
cli_runner CLIRunner インスタンス
configured_cli_env 設定済み環境(config.json、テンプレート等)
configured_cli_runner 設定済み環境のCLIRunner

実行方法

# CLI統合テストのみ実行
pytest scripts/test/cli_integration_tests/ -m cli -v

# 特定のCLIテストのみ
pytest scripts/test/cli_integration_tests/test_digest_setup_cli.py -v

Tools Tests [v4.1.0+]

開発支援ツールのテスト。

tools_tests/
├── test_check_footer.py       # Digestフッター検証
├── test_link_checker.py       # ドキュメントリンクチェック
├── test_validate_json.py      # JSON検証ツール
└── test_bandit_integration.py # セキュリティスキャン統合 (v5.0.0+)

Encoding Tests [v4.2.0+]

Windows環境でのUTF-8エンコーディングテスト。stdin 入力とログ出力の2系統。

interfaces_tests/
├── test_encoding.py          # stdin日本語入力の文字化け防止テスト
└── …                         # 他は省略(本節の対象のみ)
infrastructure_tests/
└── test_logging_config.py    # TestHandlerEncodingSafety: ログハンドラのcp932安全性

テスト内容

テスト名 検証内容
test_save_provisional_digest_japanese_input_no_garble 日本語JSONの文字化け防止
test_source_file_name_preserved source_fileフィールドの日本語保持
TestHandlerEncodingSafety::test_emdash_* cp932疑似コンソールでem-dash「—」ログがUnicodeEncodeErrorにならず内容が到達する
TestHandlerEncodingSafety::test_stringio_stream_still_works bufferを持たないstream(StringIO等)でsetup_loggingが壊れない

背景

stdin 系: Windows環境でsubprocess経由でstdinに日本語を渡す際、UTF-8エンコーディングが正しく設定されていないと文字化け(???パターン)が発生する。このテストは io.TextIOWrapper によるstdin UTF-8ラッパーの動作を検証する。

ログ出力系: リダイレクト・パイプ環境の sys.stdout は cp932 となり、em-dash (U+2014) 等 cp932 に無い文字のログが UnicodeEncodeError--- Logging error ---)になる。setup_logging()_utf8_safe_stream() がハンドラーの stream を UTF-8 で包むことを検証する。

テスト設計の注意: pytest の capture マネージャは fixture→call のフェーズ境界で sys.stdout を自分の capture オブジェクトへ再代入する。疑似コンソール(cp932 stream)の差し替えは fixture ではなくテスト本体(call フェーズ) で行うこと。また caplog は stream encoding を通らないため、この種のバグを検出できない。

実行方法

# エンコーディングテストのみ
pytest scripts/test/interfaces_tests/test_encoding.py -v
pytest "scripts/test/infrastructure_tests/test_logging_config.py::TestHandlerEncodingSafety" -v

Bandit Security Scan Integration [v5.0.0+]

セキュリティスキャン統合テスト。

テストファイル

ファイル テスト数 対象
tools_tests/test_bandit_integration.py 6 Bandit統合

テスト内容

クラス 検証内容
TestBanditExecution Banditのインストール・実行確認
TestBanditConfiguration .bandit 設定ファイル検証
TestSecurityQuality HIGH/MEDIUM severity 脆弱性がないことを確認

実行方法

# セキュリティテストのみ
pytest scripts/test/tools_tests/test_bandit_integration.py -v

# 手動セキュリティスキャン
make security

Persistent Configuration Directory [v5.2.0+]

永続化設定ディレクトリ(~/.claude/plugins/.episodicrag/)のテスト。

背景

Claude Code のプラグイン自動更新により .gitignore 内の config.json が消失する問題を解決するため、 marketplaces/ 外の永続化ディレクトリを導入。

テストファイル

ファイル 対象 テスト数
infrastructure_tests/config/test_persistent_path.py get_persistent_config_dir() 7
config_tests/test_config.py DigestConfig(永続化統合) 30+
config_tests/test_config_builder.py DigestConfigBuilder 15+

実行方法

# 永続化パステストのみ
pytest scripts/test/infrastructure_tests/config/test_persistent_path.py -v

# Config層全体(永続化統合テスト含む)
pytest scripts/test/config_tests/ -v

テスト環境

  • TempPluginEnvironment が自動的に get_persistent_config_dir() をモック
  • 環境変数 EPISODICRAG_CONFIG_DIR でカスタムパス指定可能(テスト用)

Debugging Tips

Running Specific Tests

# 単一テストクラス
pytest scripts/test/integration_tests/test_e2e_workflow.py::TestE2ELoopDetectionToShadow -v

# 単一テストメソッド
pytest scripts/test/integration_tests/test_e2e_workflow.py::TestE2ELoopDetectionToShadow::test_new_loops_detected -v

# 出力付きで実行
pytest -s --tb=short

Fixture Inspection

# 利用可能なフィクスチャを表示
pytest --fixtures

# カスタムフィクスチャのみ表示
pytest --fixtures scripts/test/conftest.py

Hypothesis Profiles

# デフォルト: 100 examples
settings.register_profile("default", max_examples=100)

# CI用: 500 examples
settings.register_profile("ci", max_examples=500, verbosity=Verbosity.verbose)

# 高速チェック: 20 examples
settings.register_profile("quick", max_examples=20)

使用方法:

HYPOTHESIS_PROFILE=ci pytest scripts/test/ -m property

Performance Targets

以下は開発機での参考目標値であり、CI が保証する値ではありません。壁時計の絶対値は 実行環境(ランナーの負荷・並列度・OS)の関数なので、常設ゲートの合否条件には使いません。

  • Unit test suite: <5秒
  • Integration suite: <30秒
  • Full test suite: <2分

性能テストが検査するのは「バグ起因の極端な遅延」(上限系 elapsed < N)までです。 マシン性能そのものを測る絶対スループット下限は v5.8.3 で撤去され、結果の正当性アサート + 数値の print に置き換わりました(値は失敗条件ではなく観測情報)。


Continuous Integration

GitHub Actions

CI codecov

  • テスト実行: PR作成時・mainマージ時に自動実行
  • カバレッジレポート: Codecov Dashboard

二層運用(v5.8.3〜)

CI は「決定論的に検査できるもの」と「環境に依存するもの」を別 job に分けています。

job pytest マーカー 役割
test(メイン) -m "not slow and not performance"(+ coverage) 決定論ゲート。常設の合否判定。壁時計に依存しない検査だけを走らせる
performance -m "slow or performance"--no-cov 環境依存検査の受け皿。main push / PR で実行し、結果を artifact に保存
  • 常設ゲートを決定論に閉じることで、共有ランナーの混雑が「性能回帰」として赤くなる事象を防ぎます
  • 両 job のマーカーは補集合の関係にあり、全テストはどちらか一方でちょうど 1 回実行されます
  • TEST_COUNT バッジの件数はメイン job の選択分slow / performance を除いた数)です

ローカル実行

CI と役割が違います——開発機は負荷が安定しているので、ローカルでは全実行に価値がありますmake test は全実行のまま)。CI の再現が要るときだけマーカーを指定してください。

# 全テスト(ローカルの既定。壁時計テストも含む)
pytest scripts/test/ -v

# CI メイン job の再現(決定論ゲート)
pytest scripts/test/ -m "not slow and not performance" --cov-fail-under=80

# CI performance job の再現(壁時計テストのみ)
pytest scripts/test/ -m "slow or performance" --no-cov

# カバレッジ付き
pytest scripts/test/ --cov=. --cov-report=term-missing --cov-report=html

# HTMLレポート確認
open htmlcov/index.html  # macOS
start htmlcov/index.html # Windows

Known Gaps

将来の改善候補

  1. 8レベル完全カスケードテスト - 現在はWeekly→Monthlyの2レベルまで

Note: 以下は実装済み

  • エラー回復テスト → test_stateful_workflow.py::ErrorRecoveryStateMachine
  • 境界条件テスト → test_threshold_boundaries.py
  • 並行アクセステスト → test_concurrent_access.py

EpisodicRAG by Weave | GitHub