第 6 章:AI 辅助测试与质量保障
让 AI 成为你的 QA 团队 — 从单元测试到安全审计
🎯 本章学习目标
- 掌握测试金字塔每一层的 AI 生成策略
- 学会让 AI 生成高覆盖率的单元测试(边界、异常、并发)
- 建立 AI 辅助的 Code Review 门禁体系
- 能够用 AI 进行安全漏洞扫描和修复
- 掌握模糊测试(Fuzz Testing)的提示词设计
6.1 测试金字塔与 AI 策略
🔺 E2E 测试(少量)
AI 策略:生成 Playwright/Cypress 脚本
🔸 集成测试(适量)
AI 策略:生成 API 测试 + 数据库测试
🟦 单元测试(大量)
AI 策略:自动生成 → 人工补充 → 覆盖分析
6.2 AI 生成单元测试的核心策略
六大测试维度
😊 正常路径
典型输入,验证正确输出
典型输入,验证正确输出
🔢 边界值
0, -1, MAX_INT, 空字符串, 空列表
0, -1, MAX_INT, 空字符串, 空列表
💥 异常场景
网络断开, 文件不存在, 权限不足
网络断开, 文件不存在, 权限不足
🔄 并发场景
竞态条件, 死锁, 数据一致性
竞态条件, 死锁, 数据一致性
📐 类型边界
None, 错误类型, 超大对象
None, 错误类型, 超大对象
⏱️ 超时场景
慢 IO, 阻塞, 资源耗尽
慢 IO, 阻塞, 资源耗尽
6.3 Code Review 质量门禁
AI 可以充当第一道Code Review防线。建立五道门禁:
1
安全门禁(阻断级)
SQL 注入、命令注入、路径遍历、XSS、硬编码密钥 → 发现立即阻断
2
正确性门禁(阻断级)
未处理的异常、资源泄漏、整数溢出、除零错误
3
性能门禁(警告级)
N+1 查询、循环内 IO、不必要的对象创建、大内存分配
4
可读性门禁(建议级)
命名不当、过长函数、缺少注释、重复代码
5
测试门禁(指标级)
覆盖率 < 80%、缺少边界测试、缺少集成测试
6.4 安全审计的 AI 方法
安全性是 NAS 系统的核心关注点。AI 可以从以下角度进行代码审计:
| 漏洞类型 | 检测要点 | NAS 场景示例 |
|---|---|---|
| 路径遍历 | 用户输入用于构造文件路径 | ../../etc/passwd 访问敏感文件 |
| 命令注入 | 用户输入拼接到 shell 命令 | mount 参数中的恶意字符 |
| 权限绕过 | 缺少所有权检查 | 用户 A 访问用户 B 的私有文件 |
| 信息泄漏 | 错误消息暴露内部结构 | API 返回完整堆栈跟踪 |
| 资源耗尽 | 无限制的输入导致 OOM | 上传超大文件撑满磁盘 |
📋 本章提示词模板
模板 6.1:全覆盖单元测试生成器
📋
请为以下代码生成完整的 pytest 单元测试:
## 源代码
```python
[粘贴源代码]
```
## 测试要求
对每个公开函数/方法,生成以下类别的测试用例:
### 1. 正常路径(Happy Path)
- 至少 2 个典型输入,验证正确输出
### 2. 边界值测试
- 最小值、最大值
- 空值(None、""、[]、{})
- 零值
- 负值(如果适用)
- 单元素 / 大批量
### 3. 异常场景
- 无效输入类型
- 超出范围的参数
- Mock 外部依赖,测试异常处理路径
- 超时场景
### 4. 并发测试(如果涉及共享状态)
- 使用 pytest-asyncio 或 threading
- 测试竞态条件
## 输出格式
- 使用 pytest 框架
- 每个测试函数命名:test_[函数名]_[场景描述]
- 使用 @pytest.mark.parametrize 减少重复
- 使用 conftest.py 管理 fixtures
- 使用 pytest-mock 进行 Mock
- 目标覆盖率 > 85%
模板 6.2:安全审计提示词
📋
你是一位应用安全专家。请对以下代码进行安全审计。
## 审计范围
[粘贴代码或指定文件路径]
## 审计清单
请逐项检查并给出发现:
### 注入类
- [ ] SQL/NoSQL 注入
- [ ] 命令注入(os.system, subprocess, eval, exec)
- [ ] LDAP/XML/XPath 注入
### 文件安全
- [ ] 路径遍历(../ 绕过)
- [ ] 文件上传限制(类型、大小、内容)
- [ ] 符号链接攻击
- [ ] 临时文件竞争(TOCTOU)
### 认证与授权
- [ ] 密码/密钥硬编码
- [ ] 认证绕过
- [ ] 权限检查遗漏
- [ ] 会话管理缺陷
### 数据处理
- [ ] 敏感数据明文存储
- [ ] 日志中包含敏感信息
- [ ] 加密算法使用不当
- [ ] 随机数不安全(如使用 random 而非 secrets)
### 输出格式
每个发现包含:
- 严重程度:Critical / High / Medium / Low
- 位置:文件:行号
- 描述:漏洞原理
- 攻击场景:如何被利用
- 修复方案:具体的代码修复
模板 6.3:模糊测试(Fuzz Test)生成器
📋
请为以下函数生成基于 hypothesis 库的模糊测试:
## 目标函数
```python
[粘贴函数签名和 docstring]
```
## 要求
1. 使用 hypothesis 的 strategies 生成多样化输入
2. 测试不变量(invariant)——无论输入是什么,某些属性必须成立
3. 测试往返属性(round-trip)——如果适用
4. 测试与参考实现的等价性(如果存在参考实现)
5. 设置合理的 max_examples(至少 500)
6. 对发现的失败案例,用 @example 装饰器固化为回归测试
🛠️ 实战演练:为文件共享服务生成测试
目标:测试 SMB 共享管理模块
源代码(待测模块)
import re
import subprocess
from pathlib import Path
from dataclasses import dataclass
@dataclass
class SMBShare:
name: str
path: str
comment: str = ""
read_only: bool = False
valid_users: list[str] = None
guest_ok: bool = False
class SMBShareManager:
def validate_share_name(self, name: str) -> bool:
"""验证共享名合法性:不超过15字符,不含特殊字符"""
if not name or len(name) > 15:
return False
return bool(re.match(r'^[a-zA-Z0-9_-]+$', name))
def validate_path(self, path: str) -> bool:
"""验证路径合法性并防止路径遍历"""
p = Path(path).resolve()
return p.exists() and p.is_dir() and '..' not in path
def generate_smb_conf(self, share: SMBShare) -> str:
"""生成 smb.conf 共享配置段"""
if not self.validate_share_name(share.name):
raise ValueError(f"非法的共享名: {share.name}")
lines = [f"[{share.name}]", f" path = {share.path}"]
if share.comment:
lines.append(f" comment = {share.comment}")
lines.append(f" read only = {'yes' if share.read_only else 'no'}")
lines.append(f" guest ok = {'yes' if share.guest_ok else 'no'}")
if share.valid_users:
lines.append(f" valid users = {' '.join(share.valid_users)}")
return '\n'.join(lines)
AI 生成的测试
import pytest
from pathlib import Path
from smb_manager import SMBShareManager, SMBShare
class TestSMBShareManager:
@pytest.fixture
def manager(self):
return SMBShareManager()
@pytest.fixture
def tmp_share_dir(self, tmp_path):
d = tmp_path / "shares" / "documents"
d.mkdir(parents=True)
return d
# ─── 正常路径 ───
def test_generate_basic_share(self, manager, tmp_share_dir):
share = SMBShare(name="docs", path=str(tmp_share_dir))
conf = manager.generate_smb_conf(share)
assert "[docs]" in conf
assert str(tmp_share_dir) in conf
assert "read only = no" in conf
def test_generate_readonly_share(self, manager, tmp_share_dir):
share = SMBShare(name="archive", path=str(tmp_share_dir), read_only=True)
conf = manager.generate_smb_conf(share)
assert "read only = yes" in conf
# ─── 边界值 ───
@pytest.mark.parametrize("name,expected", [
("", False), ("a" * 16, False), # 超长
("a", True), ("a" * 15, True), # 极限长度
("valid_name", True),
])
def test_validate_share_name_boundary(self, manager, name, expected):
assert manager.validate_share_name(name) == expected
def test_validate_share_name_special_chars(self, manager):
assert not manager.validate_share_name("name with space")
assert not manager.validate_share_name("../../../etc")
assert not manager.validate_share_name("name