关于 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
- 任何真值都会通过
- 未验证会话/令牌
- 未验证用户身份验证状态
🔴 严重:缺少反向测试
- 没有测试错误密码
- 没有测试不存在的用户
- 没有测试已锁定的账户
- 没有测试已过期的凭据
🟠 高:没有验证会话
- 未验证身份验证令牌
- 未验证会话过期时间
- 未验证会话中的用户上下文
🟡 中:测试依赖于前一个测试
- 在此测试中创建用户
- 应使用 fixture 或 setup
- 测试应相互独立
建议的改进
@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()
**声称的意图:**测试重复电子邮件的处理 **实际测试:**第二个用户创建成功(错误!)
发现的问题
🔴 严重:测试方向相反
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%——测试通过毫无意义
必需操作
立即执行(阻止合并)
- 使用正确的断言重写全部三个测试
- 添加反向测试用例(需要 12 个以上测试)
- 验证测试能捕获故意引入的缺陷
- 添加用于管理测试用户的 fixture
后续工作(完成审核的必要条件)
- 添加边界情况测试(至少额外 15 项)
- 为完整注册流程添加集成测试
- 添加安全测试(SQL 注入、XSS 等)
- 为注册端点添加性能测试
预估时间
- 修复关键问题:2-3 小时
- 完成测试套件:1 天
- 审查和迭代:0.5 天
总计: 1.5-2 天,以完善测试覆盖率
建议
❌ 阻止合并
不要批准此 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 检查员: [你的姓名] 日期: [日期] 状态: ❌ 已拒绝 原因: 测试毫无保障作用,必须重写 是否需要重新检查: 是
这才是彻底检查测试的方式。现在发现这些问题,总比等到上线后再发现要好。