Claude-skill-registry-data lorairo-test-generator
Generate pytest unit, integration, and GUI tests for LoRAIro with proper fixtures, mocks, and 75%+ coverage using pytest-qt for PySide6 testing
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry-data
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry-data "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/lorairo-test-generator" ~/.claude/skills/majiayu000-claude-skill-registry-data-lorairo-test-generator && rm -rf "$T"
manifest:
data/lorairo-test-generator/SKILL.mdsource content
LoRAIro Test Generator Skill
このSkillは、LoRAIroプロジェクトにおけるpytest+pytest-qtを使ったテスト生成のベストプラクティスを提供します。
使用タイミング
- 新機能実装後のテスト作成
- 既存コードのテストカバレッジ向上
- リファクタリング後の回帰テスト作成
- GUI コンポーネントのテスト実装
LoRAIroのテスト戦略
テストカテゴリ (pytest markers)
# Unit tests: ビジネスロジック、単一関数/クラス @pytest.mark.unit def test_calculate_score(): assert calculate_score(10, 20) == 0.5 # Integration tests: 複数コンポーネント統合 @pytest.mark.integration def test_repository_service_integration(): service = ImageProcessingService(repository) result = service.process_batch(images) assert len(result) > 0 # GUI tests: PySide6 ウィジェット @pytest.mark.gui def test_widget_interaction(qtbot): widget = ThumbnailWidget() qtbot.addWidget(widget) assert widget.isVisible()
実行コマンド
# 全テスト uv run pytest # カテゴリ別 uv run pytest -m unit uv run pytest -m integration uv run pytest -m gui # カバレッジ付き uv run pytest --cov=src --cov-report=html
1. Unit Test パターン
Repository Test
import pytest from src.lorairo.database.db_core import create_test_engine from src.lorairo.database.db_repository import ImageRepository from src.lorairo.database.schema import Image from sqlalchemy.orm import scoped_session, sessionmaker @pytest.fixture def test_db_engine(): """テスト用DBエンジン""" return create_test_engine() @pytest.fixture def test_repository(test_db_engine): """テスト用リポジトリ""" session_factory = scoped_session(sessionmaker(bind=test_db_engine)) repo = ImageRepository(session_factory) yield repo session_factory.remove() @pytest.mark.unit def test_add_image(test_repository): """画像追加テスト""" image = Image(path="/test/image.jpg", phash="abc123") result = test_repository.add(image) assert result.id is not None assert result.path == "/test/image.jpg" assert result.phash == "abc123" @pytest.mark.unit def test_get_by_id(test_repository): """ID検索テスト""" # Arrange image = Image(path="/test/img.jpg", phash="xyz") added = test_repository.add(image) # Act result = test_repository.get_by_id(added.id) # Assert assert result is not None assert result.id == added.id
Service Test (Mock使用)
from unittest.mock import Mock, patch from src.lorairo.services.image_processing_service import ImageProcessingService @pytest.fixture def mock_repository(): """モックリポジトリ""" repo = Mock(spec=ImageRepository) repo.get_all.return_value = [ Image(id=1, path="/img1.jpg"), Image(id=2, path="/img2.jpg"), ] return repo @pytest.mark.unit def test_process_batch(mock_repository): """バッチ処理テスト""" service = ImageProcessingService(mock_repository) result = service.process_batch(["/img1.jpg", "/img2.jpg"]) assert len(result) == 2 mock_repository.add.assert_called()
2. Integration Test パターン
@pytest.mark.integration def test_full_workflow(test_repository): """完全ワークフローテスト""" # 1. データ準備 images = [ Image(path=f"/img{i}.jpg", phash=f"hash{i}") for i in range(5) ] # 2. 追加 added = test_repository.batch_add(images) assert len(added) == 5 # 3. 検索 criteria = SearchCriteria(min_score=0.5) results = test_repository.search(criteria) assert isinstance(results, list) # 4. 更新 results[0].score = 0.9 updated = test_repository.update(results[0]) assert updated.score == 0.9 # 5. 削除 deleted = test_repository.delete(results[0].id) assert deleted is True
3. GUI Test パターン (pytest-qt)
Widget Test
from PySide6.QtWidgets import QApplication from PySide6.QtCore import Qt from src.lorairo.gui.widgets.thumbnail_widget import ThumbnailWidget @pytest.fixture def app(qtbot): """Qt application""" return QApplication.instance() or QApplication([]) @pytest.fixture def thumbnail_widget(qtbot): """Thumbnail widget fixture""" widget = ThumbnailWidget() qtbot.addWidget(widget) return widget @pytest.mark.gui def test_widget_initialization(thumbnail_widget): """Widget初期化テスト""" assert thumbnail_widget.isVisible() assert thumbnail_widget.windowTitle() == "Thumbnails" @pytest.mark.gui def test_signal_emission(qtbot, thumbnail_widget): """Signal発火テスト""" with qtbot.waitSignal( thumbnail_widget.image_selected, timeout=1000 ) as blocker: # Signal発火アクション thumbnail_widget.select_image(0) # Signal引数確認 assert blocker.args[0] == "/path/to/image.jpg" @pytest.mark.gui def test_button_click(qtbot, thumbnail_widget): """ボタンクリックテスト""" # ボタンクリックシミュレーション qtbot.mouseClick( thumbnail_widget._ui.loadButton, Qt.LeftButton ) # 結果確認 assert thumbnail_widget._images_loaded is True @pytest.mark.gui def test_text_input(qtbot, thumbnail_widget): """テキスト入力テスト""" # テキスト入力 qtbot.keyClicks(thumbnail_widget._ui.searchField, "test query") # 入力値確認 assert thumbnail_widget._ui.searchField.text() == "test query"
Widget間通信テスト
@pytest.mark.gui def test_widget_communication(qtbot): """Widget間通信テスト""" thumbnail = ThumbnailWidget() details = ImageDetailsWidget() qtbot.addWidget(thumbnail) qtbot.addWidget(details) # 直接接続 details.connect_to_thumbnail_widget(thumbnail) # Signal発火とSlot実行確認 with qtbot.waitSignal(thumbnail.image_metadata_selected): thumbnail.select_image(0) # 結果確認 assert details.current_image_path == "/img.jpg"
4. Fixture パターン
共通Fixture
# conftest.py import pytest from pathlib import Path @pytest.fixture(scope="session") def test_data_dir(): """テストデータディレクトリ""" return Path(__file__).parent / "resources" @pytest.fixture def sample_image(test_data_dir): """サンプル画像""" return test_data_dir / "sample.jpg" @pytest.fixture def mock_config(): """モック設定""" return { "database_dir": "/tmp/test_db", "max_workers": 4, } @pytest.fixture(autouse=True) def reset_state(): """各テスト前後で状態リセット""" # Setup yield # Teardown # 必要なクリーンアップ処理
パラメータ化Fixture
@pytest.fixture(params=[1, 5, 10]) def batch_size(request): """異なるバッチサイズでテスト""" return request.param def test_batch_processing(batch_size): """バッチサイズごとのテスト""" result = process_batch(range(batch_size)) assert len(result) == batch_size
ベストプラクティス
DO ✅
- AAA pattern: Arrange, Act, Assert で構造化
- 1 test, 1 assertion: 可能な限り単一のアサート
- Fixture活用: setup/teardownは fixture で
- pytest markers: 適切なマーカー付与
- カバレッジ75%+: 最低75%のカバレッジ維持
DON'T ❌
- 依存順序: テスト間の依存関係禁止
- 外部依存: 外部API呼び出しはモック
- ハードコードパス: テストデータは
tests/resources/ - print デバッグ: logger や assert メッセージ使用
- 長時間テスト: ユニットテストは1秒以内
テストカバレッジ要件
# カバレッジレポート生成 uv run pytest --cov=src --cov-report=html # カバレッジ確認 # htmlcov/index.html をブラウザで開く # 最低カバレッジ: 75% # 目標: ユニット 90%+, 統合 80%+, GUI 70%+
ファイル配置
tests/ ├── conftest.py # 共通fixture ├── resources/ # テストデータ │ ├── sample.jpg │ └── test_config.toml ├── database/ # DB tests │ ├── test_db_repository.py │ └── test_db_manager.py ├── services/ # Service tests │ └── test_image_processing_service.py └── gui/ # GUI tests ├── widgets/ │ └── test_thumbnail_widget.py └── window/ └── test_main_window.py
参考リソース
- 既存テスト:
ディレクトリtests/ - pytest公式: https://docs.pytest.org/
- pytest-qt: https://pytest-qt.readthedocs.io/
- テストリソース:
tests/resources/