test-quality-inspector について
弱いアサーションや不足するケース、意図と逆のテストを点検するスキルです。エンジニアや QA がテストの抜けを見つけ、改善案を検討できます。
主な機能
- 01弱いアサーションの特定
- 02負のケースや検証テストの不足を指摘
- 03テスト名と実際の動作を照合
- 04具体的なテスト改善案の提示
活用シーン
- 01マージ前のテストレビュー
- 02登録・ログインテストの不足確認
- 03テストが不具合を検出できるか確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ bobmatnyc/ claude-mpm-skills --skill test-quality-inspectorインストール後、Claude Code で/test-quality-inspectorを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metadata.jsonreferences/assertion-quality.mdinspection-checklist.mdred-flags.md詳細
テスト検査レポートの例
シナリオ:ユーザー登録機能
エンジニアのテストスイート
# test_user_registration.py
def test_user_creation():
"""Test user creation"""
user = create_user("[email protected]", "password123")
assert user
def test_login():
"""Test login"""
user = create_user("[email protected]", "password123")
result = login("[email protected]", "password123")
assert result
def test_duplicate_email():
"""Test duplicate email"""
create_user("[email protected]", "password123")
user2 = create_user("[email protected]", "password456")
assert user2
QA 検査レポート
テスト 1:test_user_creation()
主張されている意図: ユーザー作成をテストする 実際にテストしている内容: オブジェクトのインスタンス化のみ
発見された問題
🔴 重大:価値のないアサーション
assert user # Line 5
- データが破損していても成功します
- メールアドレスが間違っていても成功します
- パスワードハッシュが無効でも成功します
- データベースへの保存に失敗しても成功します
頭の中でデバッグ: でたらめなデータを持つユーザーオブジェクトでも、このテストに合格します。
🟠 高:バリデーションテストの不足
- 無効なメールアドレス形式のテストがありません
- 脆弱なパスワードのテストがありません
- メールアドレスの重複テストがありません(別のテストはありますが、壊れています)
- メールアドレスへの SQL インジェクションのテストがありません
🟠 高:永続化の検証なし
- ユーザーがデータベースに保存されることをテストで検証していません
- ユーザーを取得できることをテストで検証していません
- トランザクションがロールバックされる可能性があります
🟡 中:曖昧なテスト名
- 「test_user_creation」では、何が起きるべきか説明できていません
- 改善案:「test_user_creation_with_valid_data_persists_to_database」
改善案
def test_user_creation_with_valid_data_persists_to_database():
"""Creating a user with valid email and password should:
1. Create user object with correct attributes
2. Save to database
3. Hash password (not store plaintext)
4. Set default role and active status
"""
email = "[email protected]"
password = "SecurePass123!"
user = create_user(email, password)
# Verify user object
assert user.id is not None, "User should have an ID after creation"
assert user.email == email, "Email should match input"
assert user.is_active is True, "New users should be active by default"
assert user.role == "user", "New users should have 'user' role"
assert user.created_at is not None, "Created timestamp should be set"
# Verify password is hashed, not plaintext
assert user.password_hash != password, "Password should be hashed"
assert len(user.password_hash) > 50, "Password hash should be substantial"
# Verify persistence
retrieved_user = User.get_by_email(email)
assert retrieved_user is not None, "User should be retrievable from database"
assert retrieved_user.id == user.id, "Retrieved user should match created user"
def test_user_creation_with_invalid_email_format_raises_validation_error():
"""Creating a user with malformed email should raise ValidationError"""
invalid_emails = [
"not-an-email",
"@example.com",
"test@",
"test [email protected]",
"",
]
for invalid_email in invalid_emails:
with pytest.raises(ValidationError) as exc:
create_user(invalid_email, "password123")
assert "email" in str(exc.value).lower()
assert "invalid" in str(exc.value).lower()
def test_user_creation_with_weak_password_raises_validation_error():
"""Creating a user with weak password should raise ValidationError"""
weak_passwords = [
"123", # Too short
"password", # No numbers
"12345678", # No letters
"", # Empty
]
for weak_password in weak_passwords:
with pytest.raises(ValidationError) as exc:
create_user("[email protected]", weak_password)
assert "password" in str(exc.value).lower()
リスクレベル: 🔴 重大 対応: ❌ ブロック — 中核機能がテストされていません 修正見積もり時間: 30 分
テスト 2:test_login()
主張されている意図: ログインをテストする 実際にテストしている内容: 関数呼び出しの完了
発見された問題
🔴 重大:価値のないアサーション
assert result # Line 11
- 真偽値として真となる値であれば何でも合格します
- セッション/トークンを検証していません
- ユーザーの認証状態を検証していません
🔴 重大:否定的テストの不足
- パスワードが間違っている場合のテストがありません
- 存在しないユーザーのテストがありません
- アカウントがロックされている場合のテストがありません
- 認証情報の期限切れのテストがありません
🟠 高:セッションの検証なし
- 認証トークンを検証していません
- セッションの有効期限を検証していません
- セッション内のユーザーコンテキストを検証していません
🟡 中:テストが前のテストに依存
- このテスト内でユーザーを作成しています
- フィクスチャまたはセットアップを使用するべきです
- テストは独立しているべきです
改善案
@pytest.fixture
def registered_user():
"""Fixture providing a registered user for login tests"""
user = create_user("[email protected]", "SecurePass123!")
yield user
# Cleanup if needed
User.delete(user.id)
def test_login_with_valid_credentials_returns_authenticated_session(registered_user):
"""Logging in with correct email and password should:
1. Return authentication token/session
2. Set authenticated state
3. Include user context
4. Set appropriate expiry
"""
session = login(registered_user.email, "SecurePass123!")
assert session is not None, "Login should return session"
assert session.is_authenticated is True, "Session should be authenticated"
assert session.user_id == registered_user.id, "Session should contain user ID"
assert session.token is not None, "Session should have authentication token"
assert session.expires_at > datetime.now(), "Session should have future expiry"
assert (session.expires_at - datetime.now()).seconds >= 3600, "Session should last at least 1 hour"
def test_login_with_wrong_password_raises_authentication_error(registered_user):
"""Logging in with incorrect password should raise AuthenticationError"""
with pytest.raises(AuthenticationError) as exc:
login(registered_user.email, "WrongPassword")
assert "Invalid credentials" in str(exc.value)
assert "password" in str(exc.value).lower()
def test_login_with_nonexistent_email_raises_authentication_error():
"""Logging in with non-existent email should raise AuthenticationError"""
with pytest.raises(AuthenticationError) as exc:
login("[email protected]", "password")
assert "Invalid credentials" in str(exc.value)
# Note: Don't reveal if email exists (security)
def test_login_with_locked_account_raises_account_locked_error(registered_user):
"""Logging in to locked account should raise AccountLockedError"""
lock_account(registered_user.id)
with pytest.raises(AccountLockedError) as exc:
login(registered_user.email, "SecurePass123!")
assert registered_user.email in str(exc.value)
def test_login_with_empty_password_raises_validation_error(registered_user):
"""Logging in with empty password should raise ValidationError"""
with pytest.raises(ValidationError) as exc:
login(registered_user.email, "")
assert "password" in str(exc.value).lower()
assert "required" in str(exc.value).lower()
リスクレベル: 🔴 重大 対応: ❌ ブロック — 認証が実際にはテストされていません 修正見積もり時間: 45 分
テスト 3:test_duplicate_email()
主張されている意図: メールアドレスの重複処理をテストする 実際にテストしている内容: 2 人目のユーザー作成が成功する(誤り!)
発見された問題
🔴 重大:テストの想定が逆
user2 = create_user("[email protected]", "password456")
assert user2 # Line 17
- このテストでは、重複するユーザーの作成が成功することを期待しています
- エラーが発生して失敗することを期待するべきです
- 失敗するべきときにテストが合格します
- これは必要な内容とは正反対のテストです
🔴 重大:誤った安心感
- 本番環境のバグ:重複するメールアドレスが許可されています
- テストでは重複防止を検証すると主張しています
- 実際には重複が機能することを検証しています
- QA がカバーできていると思い込み、承認してしまう可能性があります
🟡 中:他のテストと同じメールアドレスの問題
- エラーを期待するように修正する場合、テスト 1 の改善点もすべて必要です
修正案
def test_create_user_with_duplicate_email_raises_integrity_error():
"""Creating a user with an email that already exists should:
1. Raise IntegrityError or ValidationError
2. Not create duplicate user in database
3. Preserve existing user data
"""
email = "[email protected]"
# Create first user
user1 = create_user(email, "FirstPassword123!")
initial_count = User.count()
# Attempt to create duplicate
with pytest.raises((IntegrityError, ValidationError)) as exc:
create_user(email, "SecondPassword456!")
assert "email" in str(exc.value).lower()
assert "duplicate" in str(exc.value).lower() or "exists" in str(exc.value).lower()
# Verify no new user created
assert User.count() == initial_count, "User count should not increase"
# Verify original user unchanged
original_user = User.get_by_email(email)
assert original_user.id == user1.id, "Original user should be intact"
assert original_user.verify_password("FirstPassword123!"), "Original password should work"
assert not original_user.verify_password("SecondPassword456!"), "New password should not work"
リスクレベル: 🔴 重大 対応: ❌ ブロック — 要件と正反対の内容を検証するテストです 修正見積もり時間: 20 分
サマリーレポート
総合評価
テストスイートの品質: 🔴 不合格
重大な問題: 3 件
- テスト 1:ユーザー作成を実際にはテストしていません
- テスト 2:認証を実際にはテストしていません
- テスト 3:要件と正反対の内容をテストしています
テスト総数: 3 有効なテスト: 0 カバレッジ: 高(主張上) 保護: なし(実際)
リスク評価
本番環境のリスク: 🔴 極めて高い
現在のテストスイートは、以下に対してまったく保護を提供しません。
- ユーザー作成時のデータ破損
- 認証のバイパス
- 重複メールアドレスによる登録
- パスワードのセキュリティ問題
- データベースの整合性の問題
信頼度: 0% — テストに合格しても何の意味もありません
必要な対応
即時対応(マージをブロック)
- 3 つすべてのテストを、適切なアサーションを使って書き直す
- 否定的なテストケースを追加する(12 件以上のテストが必要)
- テストで意図的なバグを検出できることを確認する
- テストユーザー管理用のフィクスチャを追加する
フォローアップ(完了に必須)
- エッジケースのテストを追加する(15 件以上の追加テスト)
- 登録フロー全体を対象とする統合テストを追加する
- セキュリティテストを追加する(SQL インジェクション、XSS など)
- 登録エンドポイントのパフォーマンステストを追加する
推定スケジュール
- 重大な問題の修正:2-3 hours
- テストスイートの完成:1 day
- レビューと反復:0.5 days
合計: 十分なテストカバレッジを確保するには、1.5-2 days
推奨事項
❌ マージをブロック
この PR は承認しないでください。テストは誤った安心感を与え、重大なバグを見逃しています。
根拠:
- 機能が完全に壊れていても、すべてのテストが成功します
- メールアドレス重複のテストでは、要件とは逆の動作を検証しています
- 実際の動作は何も検証されていません
次のステップ:
- エンジニアが上記の例に従ってテストを書き直す
- QA が書き直されたテストを再検査する
- QA が意図的にバグを仕込んだ場合にテストが検出できることを確認する
- その後にのみマージを承認する
エンジニアへの教訓
何が問題だったか
- コードを書いてからテストを書いた - コードが実行できることを確認するだけのテストになりました
- アサーションが弱かった - 「assert x」では何も証明できません
- 頭の中でデバッグしなかった - テストでバグを検出できるか確認しませんでした
- ネガティブテストがなかった - 正常系のみをテストしました
- 重複テストを誤解していた - テストで逆の動作を検証していました
改善方法
- 先にテストを書く(TDD) - こうした問題を防げます
- 具体的なアサーションを使う - 正確な値を検証します
- 頭の中でデバッグする - コードを壊して、テストが失敗することを確認します
- 失敗を明示的にテストする - 成功ケースごとに失敗ケースもテストします
- テスト名を注意深く読む - テストすると主張している内容をテストします
TDD ならこの問題を防げたはずです
テストを先に書いていれば:
# Write this FIRST (it will fail):
def test_user_creation_with_valid_data_persists_to_database():
user = create_user("[email protected]", "password")
assert user.email == "[email protected]" # Will fail until create_user works
...
# Then implement create_user to make it pass
TDD の完全なワークフローについては、テスト駆動開発のスキルを参照してください(包括的な TDD ガイダンスはスキルライブラリで利用できます)。
承認
QA 検査担当者: [Your name] 日付: [Date] ステータス: ❌ 却下 理由: テストによる保護がまったくなく、書き直しが必要です 再検査: 必要
これが徹底したテスト検査です。本番環境で発覚するより、今この問題を見つけるほうが得策です。